public synchronized List<BaseType> getTypes() {
        return typesContainer.getTypes();
    }

package com.google.security.zynamics.binnavi.disassembly.types;

import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.security.zynamics.binnavi.Database.Exceptions.CouldntDeleteException;
import com.google.security.zynamics.binnavi.Database.Exceptions.CouldntLoadDataException;
import com.google.security.zynamics.binnavi.Database.Exceptions.CouldntSaveDataException;
import com.google.security.zynamics.binnavi.disassembly.INaviOperandTreeNode;
import com.google.security.zynamics.binnavi.yfileswrap.disassembly.types.TypeDependenceGraph;
import com.google.security.zynamics.binnavi.yfileswrap.disassembly.types.TypeDependenceGraph.DependenceResult;
import com.google.security.zynamics.zylib.disassembly.IAddress;
import com.google.security.zynamics.zylib.general.ListenerProvider;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * The type manager class holds a collection of all existing base types, members and type
 * substitutions and allows clients to retrieve, edit, delete or create new ones.
 *
 *  In order to have types, members and type substitutions backed by the database, the type manager
 * is the one central authority to instantiate objects of those types. This also means that any
 * changes to instances of BaseType, TypeMember or TypeSubstitutions must be signaled to the type
 * manager. This way, arbitrary clients can be notified about changes in the type system represented
 * by the type manager.
 *
 *  The type system induces a hierarchy between two types whereas we define the pointer level to be
 * the number of pointer indirections to reach the underlying value type. So for each two types t0
 * and t1: t0 < t1 iff the pointer level of t0 is strictly smaller than that of t1.
 *
 *  For example, assume that t0 is "int" and t1 is "int*", then t0 < t1. Here, t0 has a pointer
 * level of zero and t1 has a pointer level of one. We also call t0 the parent of t1.
 *
 *  Usage:
 *
 * TypeManager manager = new TypeManager(backend);
 */
public class TypeManager {

    private final TypesContainer typesContainer;

    private final ListenerProvider<TypeChangedListener> typeListeners = new ListenerProvider<>();

    private final ListenerProvider<TypeSubstitutionChangedListener> substitutionListeners = new ListenerProvider<>();

    private final TypeManagerBackend backend;

    /**
     * Initializes the type system by loading all {@link RawBaseType raw types} and
     * {@link RawTypeMember raw members} via the backend. Corresponding {@link BaseType base type} and
     * {@link TypeMember type member} instances are created from the raw types. These instances are
     * owned exclusively by this TypeManager instance.
     *
     * @param backend The (database) backend which is used to persistently store the type system.
     * @throws CouldntLoadDataException if the {@link TypeManagerBackend backend} could not load the
     *         raw types / members from the storage.
     */
    public TypeManager(final TypeManagerBackend backend) throws CouldntLoadDataException {
        this.backend = Preconditions.checkNotNull(backend, "IE02774: Backend can not be null.");
        typesContainer = new TypesContainer(backend.loadRawBaseTypes(), backend.loadRawTypeMembers());
    }

    private static String buildArrayName(final BaseType elementType, final int numberElements) {
        return String.format("%s[%d]", elementType.getName(), numberElements);
    }

    private static boolean canDeletePointerType(final BaseType baseType) {
        return baseType.pointedToBy() == null;
    }

    private static List<Integer> membersToIds(final List<TypeMember> members) {
        final List<Integer> result = Lists.newArrayList();
        for (final TypeMember member : members) {
            result.add(member.getId());
        }
        return result;
    }

    private List<TypeMember> idsToMembers(final Integer[] memberIds) {
        final List<TypeMember> members = Lists.newArrayList();
        for (final int id : memberIds) {
            members.add(typesContainer.getTypeMemberById(id));
        }
        return members;
    }

    /**
     * Clears all members from a set of types that have a base type which was deleted.
     *
     * @param deletedType The type that was deleted from the type system and which can not be
     *        referenced by members anymore.
     * @param affectedTypes The set of types that possibly contains one or more members that are of
     *        the deleted type.
     */
    private void clearMembers(final BaseType deletedType, final Set<BaseType> affectedTypes) {
        // Note: we don't delete members explicitly from the database since foreign key constraints will
        notifyMembersDeleted(typesContainer.clearMembers(deletedType, affectedTypes));
    }

    /**
     * Removes all type substitutions with the given base type from the backend and notifies
     * listeners.
     *
     * @param deletedType The base type that was deleted.
     * @throws CouldntDeleteException Thrown if the substitutions could not be deleted from the
     *         database.
     */
    private void clearTypeSubstitutions(final BaseType deletedType) throws CouldntDeleteException {
        final Set<TypeSubstitution> substitutions = typesContainer.deleteSubstitutionsByType(deletedType);
        if (!substitutions.isEmpty()) {
            for (final TypeSubstitution substitution : substitutions) {
                // TODO(jannewger): this operation could be batched.
                backend.deleteTypeSubstitution(substitution);
            }
            notifySubstitutionsDeleted(substitutions);
        }
    }

    // TODO(jannewger): pass this information to the constructor.
    private int getDefaultPointerSize() {
        return 32;
    }

    /**
     * Creates a new array {@link TypeMember member} via the backend and updates the corresponding
     * base type. No listeners are notified.
     */
    private TypeMember createArrayMember(final BaseType containingType, final BaseType memberType, final String memberName, final int memberNumberOfElements) throws CouldntSaveDataException {
        if (!typesContainer.willTypeCreateCyclicReference(containingType, memberType)) {
            final TypeMember member = backend.createArrayMember(containingType, memberType, memberName, memberNumberOfElements);
            typesContainer.addMember(member);
            return member;
        } else {
            throw new IllegalStateException("Error: cannot create cyclic member declaration.");
        }
    }

    /**
     * Creates a new base type via the back end and updates all internal data structures. No listeners
     * are notified.
     *
     * @param name The Name of the type to be created.
     * @param size The size of the type to be created in bits.
     * @param signed A {@link Boolean flag} indicating if the type is signed.
     * @param childPointer A {@link BaseType type} to which this type points.
     * @return The generated {@link BaseType type}.
     *
     * @throws CouldntSaveDataException If the changes could not be saved to the database.
     */
    private BaseType instantiateType(final String name, final int size, final boolean signed, final BaseType childPointer, final BaseTypeCategory category) throws CouldntSaveDataException {
        final BaseType baseType = backend.createType(name, size, signed, childPointer == null ? null : childPointer.getId(), category);
        if (childPointer != null) {
            BaseType.appendToPointerHierarchy(childPointer, baseType);
        }
        typesContainer.addBaseType(baseType);
        return baseType;
    }

    private void notifyMemberAdded(final TypeMember member) {
        for (final TypeChangedListener listener : typeListeners) {
            listener.memberAdded(member);
        }
    }

    private void notifyMemberDeleted(final TypeMember member) {
        for (final TypeChangedListener listener : typeListeners) {
            listener.memberDeleted(member);
        }
    }

    private void notifyMembersDeleted(final List<TypeMember> membersToDelete) {
        // TODO(jannewger): this could be batched into a single event.
        for (final TypeMember member : membersToDelete) {
            notifyMemberDeleted(member);
        }
    }

    private void notifyMembersMoved(final Set<BaseType> affectedTypes) {
        for (final TypeChangedListener listener : typeListeners) {
            listener.membersMoved(affectedTypes);
        }
    }

    private void notifyMemberUpdated(final TypeMember member) {
        for (final TypeChangedListener listener : typeListeners) {
            listener.memberUpdated(member);
        }
    }

    private void notifySubstitutionAdded(final TypeSubstitution substitution) {
        for (final TypeSubstitutionChangedListener listener : substitutionListeners) {
            listener.substitutionsAdded(Sets.newHashSet(substitution));
        }
    }

    /**
     * Notify listeners that a single type substitution was changed.
     *
     * @param substitution The type substitution that changed (e.g. its base type).
     */
    private void notifySubstitutionChanged(final TypeSubstitution substitution) {
        for (final TypeSubstitutionChangedListener listener : substitutionListeners) {
            listener.substitutionsChanged(Sets.newHashSet(substitution));
        }
    }

    /**
     * Trigger a substitution changed notification for all type substitutionsByType corresponding to
     * the given set of base types.
     *
     * @param baseTypes The base types that changed.
     */
    private void notifySubstitutionsChanged(final Set<BaseType> baseTypes) {
        final Set<TypeSubstitution> changedSubstitutions = typesContainer.getAffectedTypeSubstitutions(baseTypes);
        for (final TypeSubstitutionChangedListener listener : substitutionListeners) {
            listener.substitutionsChanged(changedSubstitutions);
        }
    }

    private void notifySubstitutionsDeleted(final Set<TypeSubstitution> deletedSubstitutions) {
        for (final TypeSubstitutionChangedListener listener : substitutionListeners) {
            listener.substitutionsDeleted(deletedSubstitutions);
        }
    }

    private void notifyTypeAdded(final BaseType baseType) {
        for (final TypeChangedListener listener : typeListeners) {
            listener.typeAdded(baseType);
        }
    }

    private synchronized void notifyTypeDeleted(final BaseType deletedType) {
        for (final TypeChangedListener listener : typeListeners) {
            listener.typeDeleted(deletedType);
        }
    }

    private synchronized void notifyTypesUpdated(final ImmutableSet<BaseType> baseType) {
        for (final TypeChangedListener listener : typeListeners) {
            listener.typesUpdated(baseType);
        }
    }

    public synchronized void addListener(final TypeChangedListener listener) {
        typeListeners.addListener(listener);
    }

    public synchronized void addListener(final TypeSubstitutionChangedListener listener) {
        substitutionListeners.addListener(listener);
    }

    private static Integer determineAppendOffset(final BaseType baseType) {
        Preconditions.checkArgument(BaseTypeCategory.isOffsetCategory(baseType.getCategory()), "Error: Can only determine append offset if base type is of offset category.");
        if (!baseType.hasMembers()) {
            return 0;
        }
        final TypeMember lastMember = baseType.getLastMember();
        return lastMember.getBitOffset().get() + lastMember.getBitSize();
    }

    private static Integer determineAppendIndex(final BaseType baseType) {
        Preconditions.checkArgument(baseType.getCategory() == BaseTypeCategory.FUNCTION_PROTOTYPE, "Error: Can only determine append index if base type is of index category.");
        if (baseType.hasMembers()) {
            final TypeMember lastMember = baseType.getLastMember();
            return lastMember.getArgumentIndex().get() + 1;
        } else {
            return 0;
        }
    }

    /**
     * Append a member to the end of the list of members.
     *
     * @param containingType The type which should contain the new member.
     * @param memberType The base type of the member itself.
     * @param memberName The name of the member.
     * @return The member that was created or null if adding the member would have created a recursive
     *         declaration.
     * @throws CouldntSaveDataException Thrown if the member could not be saved to the database.
     */
    public synchronized TypeMember appendMember(final BaseType containingType, final BaseType memberType, final String memberName) throws CouldntSaveDataException {
        Preconditions.checkNotNull(containingType, "IE02775: Containing type can not be null.");
        Preconditions.checkNotNull(memberType, "IE02776: Base type can not be null.");
        Preconditions.checkNotNull(memberName, "IE02777: Member name can not be null.");
        switch(containingType.getCategory()) {
            case STRUCT:
                return createStructureMember(containingType, memberType, memberName, determineAppendOffset(containingType));
            case UNION:
                return createUnionMember(containingType, memberType, memberName);
            case FUNCTION_PROTOTYPE:
                return createFunctionPrototypeMember(containingType, memberType, memberName, determineAppendIndex(containingType));
            default:
                throw new IllegalStateException("Error: cannot insert member into non-compound type.");
        }
    }

    public synchronized TypeMember createFunctionPrototypeMember(final BaseType containingType, final BaseType memberType, final String memberName, int memberArgumentIndex) throws CouldntSaveDataException {
        Preconditions.checkNotNull(containingType, "Error: containing type argument can not be null");
        Preconditions.checkNotNull(memberType, "Error: member type can not be null");
        Preconditions.checkArgument(containingType.getCategory() == BaseTypeCategory.FUNCTION_PROTOTYPE, "Error: the base type category is not of type function prototype");
        Preconditions.checkNotNull(memberName, "Error: name argument can not be null");
        Preconditions.checkArgument(!memberName.isEmpty(), "Error: name argument can not be empty");
        Preconditions.checkArgument(memberArgumentIndex >= 0, "Error: argument index argument can not be smaller than zero");
        if (typesContainer.willTypeCreateCyclicReference(containingType, memberType)) {
            return null;
        }
        // TODO(timkornau): include a method to make sure there are no two arguments at the same index.
        final TypeMember member = backend.createFunctionPrototypeMember(containingType, memberType, memberName, memberArgumentIndex);
        notifyMemberCreation(member, typesContainer.addMember(member));
        return member;
    }

    private synchronized void notifyMemberCreation(final TypeMember member, final ImmutableSet<BaseType> affectedTypes) {
        notifyMemberAdded(member);
        notifySubstitutionsChanged(affectedTypes);
    }

    private synchronized void notifyMemberUpdated(final TypeMember member, final ImmutableSet<BaseType> affectedTypes) {
        notifyMemberUpdated(member);
        notifySubstitutionsChanged(affectedTypes);
    }

    public synchronized TypeMember createStructureMember(final BaseType containingType, final BaseType memberType, final String memberName, int memberOffset) throws CouldntSaveDataException {
        Preconditions.checkNotNull(containingType, "Error: containing type argument can not be null");
        Preconditions.checkNotNull(memberType, "Error: member type can not be null");
        Preconditions.checkNotNull(memberName, "Error: name argument can not be null");
        Preconditions.checkArgument(!memberName.isEmpty(), "Error: name argument can not be empty");
        Preconditions.checkArgument(memberOffset >= 0, "Error: argument index argument can not be smaller than zero");
        if (typesContainer.willTypeCreateCyclicReference(containingType, memberType)) {
            return null;
        }
        // We need to determine the original type sizes before doing any changes to the type system!
        final ImmutableMap<BaseType, Integer> originalTypeSizes = captureTypeSizesState(typesContainer.getAffectedTypes(containingType));
        // We need to check if there is space in the struct for our new member.
        final ImmutableList<TypeMember> subsequentMembers = containingType.getSubsequentMembersInclusive(memberOffset);
        if (!subsequentMembers.isEmpty()) {
            final int moveDelta = (memberOffset + memberType.getBitSize()) - subsequentMembers.get(0).getBitOffset().get();
            if (moveDelta > 0) {
                for (final TypeMember member : subsequentMembers) {
                    final int newOffset = member.getBitOffset().get() + moveDelta;
                    backend.updateStructureMember(member, member.getBaseType(), member.getName(), newOffset);
                    member.setOffset(Optional.of(newOffset));
                    notifyMemberUpdated(member);
                }
            }
        }
        final TypeMember member = backend.createStructureMember(containingType, memberType, memberName, memberOffset);
        final ImmutableSet<BaseType> affectedTypes = typesContainer.addMember(member);
        notifyMemberCreation(member, affectedTypes);
        final Set<BaseType> inconsistentTypes = Sets.newHashSet(affectedTypes);
        inconsistentTypes.remove(containingType);
        ensureConsistencyAfterTypeUpdate(affectedTypes, inconsistentTypes, originalTypeSizes);
        return member;
    }

    public synchronized TypeMember createUnionMember(final BaseType containingType, final BaseType memberType, final String memberName) throws CouldntSaveDataException {
        Preconditions.checkArgument(containingType.getCategory() == BaseTypeCategory.UNION, "Error: can not create a union member in a non union compound type.");
        return createStructureMember(containingType, memberType, memberName, 0);
    }

    /**
     * Creates a new array type and writes it to the database.
     *
     * @param elementType The base type of the array elements.
     * @param numberElements The number of elements in the array.
     * @throws CouldntSaveDataException Thrown if the array type could not be saved to the database.
     */
    public synchronized BaseType createArray(final BaseType elementType, final int numberElements) throws CouldntSaveDataException {
        Preconditions.checkNotNull(elementType, "Error: element type argument can not be null.");
        final String arrayName = buildArrayName(elementType, numberElements);
        final BaseType arrayType = instantiateType(arrayName, elementType.getBitSize() * numberElements, false, null, BaseTypeCategory.ARRAY);
        createArrayMember(arrayType, elementType, "array_elements", numberElements);
        notifyTypeAdded(arrayType);
        return arrayType;
    }

    /**
     * Creates a new union type and writes it to the backend.
     *
     * @param name The name of the union type.
     * @return The created base type instance.
     * @throws CouldntSaveDataException Thrown if the type couldn't be stored to the backend.
     */
    public synchronized BaseType createUnion(final String name) throws CouldntSaveDataException {
        Preconditions.checkNotNull(name, "Error: type name can not be null.");
        Preconditions.checkArgument(!name.isEmpty(), "Error: type name can not be empty.");
        final BaseType unionType = instantiateType(name, 0, /* size */
        false, /* signed */
        null, BaseTypeCategory.UNION);
        notifyTypeAdded(unionType);
        return unionType;
    }

    /**
     * Creates a new prototype type and writes it to the backend.
     *
     * @return The created {@link BaseType base type} instance.
     * @throws CouldntSaveDataException Thrown if the type could not be stored to the backend.
     */
    public synchronized BaseType createPrototype() throws CouldntSaveDataException {
        final BaseType prototypeType = instantiateType(null, /* name */
        0, /* size */
        false, /* signed */
        null, /* child pointer */
        BaseTypeCategory.FUNCTION_PROTOTYPE);
        notifyTypeAdded(prototypeType);
        return prototypeType;
    }

    /**
     * Creates a new base atomic base type instance.
     *
     * @param name The name of the new base type.
     * @param size The size of the base type in bits.
     * @param signed Specifies whether the type can represent signed numbers.
     * @return The newly created base types.
     * @throws CouldntSaveDataException Thrown if the type could not be written to the backend.
     */
    public synchronized BaseType createAtomicType(final String name, final int size, final boolean signed) throws CouldntSaveDataException {
        Preconditions.checkNotNull(name, "IE02778: Type name can not be null.");
        Preconditions.checkArgument(size >= 0, "Size can not be negative.");
        final BaseType newType = instantiateType(name, size, signed, null, BaseTypeCategory.ATOMIC);
        notifyTypeAdded(newType);
        return newType;
    }

    /**
     * Creates a new pointer type relative to the given base type and inserts it into the pointer
     * hierarchy. A new pointer type is only created if the given base type does not already have a
     * parent pointer type.
     *
     * @param baseType The base type for which to create a new parent pointer type.
     * @return The newly created pointer type or the next pointer type in the existing hierarchy.
     * @throws CouldntSaveDataException Thrown if the new pointer type could not be saved to the
     *         backend.
     */
    public synchronized BaseType createPointerType(final BaseType baseType) throws CouldntSaveDataException {
        Preconditions.checkNotNull(baseType, "IE02781: Base type can not be null.");
        if (baseType.pointedToBy() != null) {
            return baseType.pointedToBy();
        } else {
            final String newTypeName = BaseType.getPointerTypeName(baseType, baseType.getPointerLevel() + 1);
            final BaseType newType = instantiateType(newTypeName, getDefaultPointerSize(), false, baseType, BaseTypeCategory.POINTER);
            notifyTypeAdded(newType);
            return newType;
        }
    }

    /**
     * Associates a base type with the given operand, creates a new type substitution instance, stores
     * it in the database and assigns it to the given operand tree node. Finally, listeners are
     * notified about the newly added type substitution.
     *
     *  If baseType and offset cannot unambiguously reference the desired member (e.g. if a (nested)
     * union exists in baseType) a memberPath must be supplied to resolve this unambiguity.
     *
     * @param node The operand tree node that should be associated with the given base type.
     * @param baseType The base type that is associated with the given node.
     * @param memberPath A sequence of members starting at baseType that contains all nested members
     *        until the final member that corresponds to the actual type of the expression. Can be
     *        empty if base type and offset unambiguously identify the desired member.
     * @param position The zero-based index position of the operand within its instruction.
     * @param offset Specifies an additional offset relative to the beginning of the base type so the
     *        type substitution can point to a specific member.
     * @param address The address of the instruction that contains the operand tree node that receives
     *        the type substitution.
     * @return The newly created, database backed type substitution.
     * @throws CouldntSaveDataException Thrown if the type substitution couldn't be stored in the
     *         database.
     */
    public synchronized TypeSubstitution createTypeSubstitution(final INaviOperandTreeNode node, final BaseType baseType, final List<TypeMember> memberPath, final int position, final int offset, final IAddress address) throws CouldntSaveDataException {
        Preconditions.checkNotNull(node, "IE02782: Operand tree node can not be null.");
        Preconditions.checkNotNull(baseType, "IE02783: Base type can not be null.");
        Preconditions.checkArgument(offset >= 0, "Offset can not be negative.");
        Preconditions.checkNotNull(address, "IE02784: Address can not be null.");
        final TypeSubstitution substitution = backend.createTypeSubstitution(node, baseType, membersToIds(memberPath), position, offset, address);
        typesContainer.addTypeSubstitution(substitution);
        node.setTypeSubstitution(substitution);
        notifySubstitutionAdded(substitution);
        return substitution;
    }

    /**
     * Works in the same way as the overloaded method but uses an empty member path.
     */
    public synchronized TypeSubstitution createTypeSubstitution(final INaviOperandTreeNode node, final BaseType baseType, final int position, final int offset, final IAddress address) throws CouldntSaveDataException {
        return createTypeSubstitution(node, baseType, new ArrayList<TypeMember>(), position, offset, address);
    }

    /**
     * Deletes the given member from its containing base type.
     *
     * @param member The member to delete from its containing base type.
     * @throws CouldntDeleteException Thrown if the member couldn't be deleted from the backend.
     * @throws CouldntSaveDataException Thrown if changes to the type system couldn't be saved to the
     *         backend.
     */
    public synchronized void deleteMember(final TypeMember member) throws CouldntDeleteException, CouldntSaveDataException {
        Preconditions.checkNotNull(member, "IE02785: Member can not be null.");
        backend.deleteMember(member);
        final BaseType containingType = member.getParentType();
        boolean sizeChanged = member == containingType.getLastMember();
        final ImmutableMap<BaseType, Integer> originalTypeSizes = (sizeChanged) ? captureTypeSizesState(typesContainer.getAffectedTypes(containingType)) : ImmutableMap.<BaseType, Integer>of();
        final ImmutableSet<BaseType> affectedTypes = typesContainer.deleteTypeMember(member);
        notifySubstitutionsChanged(affectedTypes);
        notifyTypesUpdated(affectedTypes);
        notifyMemberDeleted(member);
        if (sizeChanged) {
            ensureConsistencyAfterTypeUpdate(affectedTypes, Sets.newHashSet(affectedTypes), originalTypeSizes);
        }
    }

    /**
     * Deletes a type from the collection of known types. The corresponding members and type
     * substitutions are deleted from the database as well.
     *
     * @param baseType The base type to be removed.
     * @throws CouldntDeleteException Thrown if the type could not be deleted from the database.
     */
    public synchronized boolean deleteType(final BaseType baseType) throws CouldntDeleteException {
        Preconditions.checkNotNull(baseType, "IE02786: Base type can not be null.");
        if (!canDeletePointerType(baseType)) {
            return false;
        }
        final Set<BaseType> affectedTypes = typesContainer.getAffectedTypes(baseType);
        clearMembers(baseType, affectedTypes);
        clearTypeSubstitutions(baseType);
        typesContainer.deleteBaseType(baseType);
        backend.deleteType(baseType);
        notifyTypeDeleted(baseType);
        return true;
    }

    /**
     * Creates a new empty structure type and stores it in the backend.
     *
     * @param name The name of the new type.
     * @return Returns the new type instance
     *
     * @throws CouldntSaveDataException Thrown if the type could not be written to the backend.
     */
    public synchronized BaseType createStructure(final String name) throws CouldntSaveDataException {
        Preconditions.checkNotNull(name, "Name can not be null.");
        final BaseType structType = instantiateType(name, 0, /* size */
        false, null, BaseTypeCategory.STRUCT);
        notifyTypeAdded(structType);
        return structType;
    }

    /**
     * Removes the type substitution from the given tree node and deletes the corresponding database
     * record.
     *
     * @param node The tree node whose type substitution should be removed.
     * @throws CouldntDeleteException Thrown if the type substitution couldn't be deleted.
     */
    public synchronized void deleteTypeSubstitution(final INaviOperandTreeNode node) throws CouldntDeleteException {
        Preconditions.checkNotNull(node, "IE02787: Operand tree node can not be null.");
        final TypeSubstitution substitution = node.getTypeSubstitution();
        typesContainer.deleteTypeSubstitution(substitution);
        backend.deleteTypeSubstitution(substitution);
        node.setTypeSubstitution(null);
        notifySubstitutionsDeleted(Collections.singleton(substitution));
    }

    /**
     * Returns the base type instance that corresponds to the given base type id from the database.
     *
     * @param typeId The type id from the database.
     * @return The base type instance corresponding to the given database type id.
     */
    public synchronized BaseType getBaseType(final int typeId) {
        return typesContainer.getBaseTypeById(typeId);
    }

    /**
     * Returns an unmodifiable list of all types.
     *
     * @return The list of all types.
     */

    /**
     * Converts the given raw type substitution to a TypeSubstitution instance and assigns it to the
     * given operand tree node. This function does not use the database back end.
     *
     * @param node The operand tree node for which to initialize the type substitution.
     * @param rawSubstitution The raw type substitution that is converted to an actual type
     *        substitution.
     */
    public synchronized void initializeTypeSubstitution(final INaviOperandTreeNode node, final RawTypeSubstitution rawSubstitution) {
        Preconditions.checkNotNull(node, "IE02420: Operand tree node can not be null.");
        Preconditions.checkNotNull(rawSubstitution, "IE02421: Raw type substitution can not be null.");
        final BaseType baseType = typesContainer.getBaseTypeById(rawSubstitution.getBaseTypeId());
        final TypeSubstitution substitution = new TypeSubstitution(node, baseType, rawSubstitution.getExpressionId(), rawSubstitution.getPosition(), rawSubstitution.getOffset(), rawSubstitution.getAddress());
        node.setTypeSubstitution(substitution);
        typesContainer.addTypeSubstitution(substitution);
        notifySubstitutionAdded(substitution);
    }

    /**
     * Creates a new member and inserts it right after an existing member in the corresponding
     * compound type. Subsequent members are moved towards the end of the compound type by the amount
     * required by the new member.
     *
     * @param existingMember The existing member after which the new member should be inserted.
     * @param memberType The base type of the new member.
     * @param memberName The name of the new member.
     * @return The new member that was inserted.
     * @throws CouldntSaveDataException Thrown if the new member could not be saved in the database.
     */
    public synchronized TypeMember insertMemberAfter(final TypeMember existingMember, final BaseType memberType, final String memberName) throws CouldntSaveDataException {
        Preconditions.checkNotNull(existingMember, "Error: existing member can not be null.");
        switch(existingMember.getParentType().getCategory()) {
            case STRUCT:
                return createStructureMember(existingMember.getParentType(), memberType, memberName, existingMember.getBitSize() + existingMember.getBitOffset().get());
            case UNION:
                return createUnionMember(existingMember.getParentType(), memberType, memberName);
            case FUNCTION_PROTOTYPE:
                return createFunctionPrototypeMember(existingMember.getParentType(), memberType, memberName, existingMember.getArgumentIndex().get() + 1);
            default:
                throw new IllegalStateException("Error: cannot insert member into non-compound type.");
        }
    }

    /**
     * Tests whether baseType is (transitively) contained within superType. A type A is said to be
     * contained within B iff B has a member of type A, or if there exists a chain of nested types
     * within B leading to at least one type that has a member of type A.
     *
     * @param superType The type that is checked if it includes baseType.
     * @param baseType The type for which to check if it is contained within superType.
     * @return True iff baseType is contained within superType.
     */
    public synchronized boolean isContainedIn(final BaseType superType, final BaseType baseType) {
        // TODO(jannewger): we should get rid off TypesContainer; it adds an unnecessary layer of
        // indirection to the implementation of the type manager. Also see comment in insertMemberAfter.
        Preconditions.checkNotNull(superType, "Error: Super type can not be null.");
        Preconditions.checkNotNull(baseType, "Error: Base type can not be null.");
        return typesContainer.isTypeContainedIn(superType, baseType);
    }

    /**
     * Tests whether a type with the given name exists.
     *
     * @param name The name of the base type.
     * @return True iff a base type with the given name exists.
     */
    public synchronized boolean isTypeExisting(final String name) {
        Preconditions.checkNotNull(name, "Error: Name can not be null.");
        return typesContainer.doesTypeNameExist(name);
    }

    /**
     * Loads an existing base type from the backend and creates a corresponding {@link BaseType
     * instance}.
     *
     * @param baseTypeId The backend id of the base type to load.
     * @throws CouldntLoadDataException Thrown if the base type could not be loaded by the backend.
     */
    public synchronized void loadAndInitializeBaseType(final int baseTypeId) throws CouldntLoadDataException {
        final BaseType baseType = backend.loadRawBaseType(baseTypeId);
        typesContainer.addBaseType(baseType);
        notifyTypeAdded(baseType);
    }

    /**
     * Loads an existing type member from the backend and creates a corresponding {@link TypeMember
     * instance}.
     *
     * @param rawMemberId The backend id of the member to load.
     * @throws CouldntLoadDataException Thrown if the member could not be loaded by the backend.
     */
    public synchronized void loadAndInitializeTypeMember(final int rawMemberId) throws CouldntLoadDataException {
        final RawTypeMember typeMember = backend.loadRawTypeMember(rawMemberId);
        final ImmutableSet<BaseType> affectedTypes = typesContainer.addMember(typeMember);
        notifyMemberAdded(typesContainer.getTypeMemberById(rawMemberId));
        notifyTypesUpdated(affectedTypes);
        notifySubstitutionsChanged(affectedTypes);
    }

    /**
     * Updates an existing {@link BaseType base type} instance by loading the corresponding data via
     * the backend. Does not alter the database. Notifies listeners about base types and type
     * substitutions that are affected by the updated base type.
     *
     * @param baseTypeId The id of the type that should be updated.
     * @throws CouldntLoadDataException Thrown if the backend could not load the type data.
     */
    public synchronized void loadAndUpdateBaseType(final int baseTypeId) throws CouldntLoadDataException {
        final BaseType newBaseType = backend.loadRawBaseType(baseTypeId);
        final BaseType oldBaseType = typesContainer.getBaseTypeById(baseTypeId);
        final ImmutableSet<BaseType> affectedTypes = typesContainer.updateBaseType(oldBaseType, newBaseType.getName(), newBaseType.isSigned(), newBaseType.getBitSize());
        notifyTypesUpdated(affectedTypes);
        notifySubstitutionsChanged(affectedTypes);
    }

    /**
     * Updates an existing {@link TypeMember member} instance by loading the member properties from
     * the backend. Does not alter the database. Notifies listeners about the changed member, affected
     * base types and type substitutions.
     *
     * @param typeMemberId The id of the {@link TypeMember type member} to load.
     * @throws CouldntLoadDataException Thrown if the data could not be loaded from the backend.
     */
    public synchronized void loadAndUpdateTypeMember(final int typeMemberId) throws CouldntLoadDataException {
        final TypeMember typeMember = typesContainer.getTypeMemberById(typeMemberId);
        final RawTypeMember rawTypeMember = backend.loadRawTypeMember(typeMemberId);
        final BaseType baseType = typesContainer.getBaseTypeById(rawTypeMember.getBaseTypeId());
        final ImmutableSet<BaseType> affectedTypes = typesContainer.updateTypeMember(typeMember, baseType, rawTypeMember.getName(), rawTypeMember.getOffset(), rawTypeMember.getNumberOfElements(), rawTypeMember.getArgumentIndex());
        notifyMemberUpdated(typeMember);
        notifySubstitutionsChanged(affectedTypes);
        notifyTypesUpdated(affectedTypes);
    }

    /**
     * Moves a set of members to a new offset within a parent. All members must be part of the same
     * base type, i.e. parentType. The relative distances between the given members are preserved.
     * Succeeding or preceding member's offsets are changed accordingly.
     *
     * @param parentType The parent type that contains the given members.
     * @param members The members that move to a new offset within their parent type.
     * @param delta The delta by which the members should be moved within parentType.
     * @throws CouldntSaveDataException Thrown if the member positions could not be written to the
     *         backend.
     */
    public synchronized void moveMembers(final BaseType parentType, final List<TypeMember> members, final int delta) throws CouldntSaveDataException {
        Preconditions.checkNotNull(parentType, "Error: parent type can not be null.");
        Preconditions.checkNotNull(members, "Error: members can not be null.");
        Preconditions.checkArgument(delta != 0, "Move delta can not be zero.");
        final MemberMoveResult result = parentType.moveMembers(Sets.newTreeSet(members), delta);
        final Set<BaseType> affectedTypes = typesContainer.getAffectedTypes(parentType);
        backend.updateMemberOffsets(membersToIds(members), delta, membersToIds(result.getImplicitlyMoved()), result.getImplicitlyMovedDelta());
        notifyMembersMoved(affectedTypes);
    }

    /**
     * Removes the {@link BaseType base type} instance corresponding to the given base type id from
     * the type manager without modifying the backend.
     *
     * @param baseTypeId The backend if of the base type that should be removed from the type manager.
     */
    public synchronized void removeBaseTypeInstance(final int baseTypeId) {
        final BaseType deletedType = typesContainer.getBaseTypeById(baseTypeId);
        final Set<BaseType> affectedTypes = typesContainer.deleteBaseTypeById(baseTypeId);
        clearMembers(deletedType, affectedTypes);
        notifySubstitutionsDeleted(typesContainer.deleteSubstitutionsByType(deletedType));
        notifyTypeDeleted(deletedType);
    }

    public synchronized void removeListener(final TypeChangedListener listener) {
        Preconditions.checkNotNull(listener, "IE02790: Listener argument can not be null.");
        typeListeners.removeListener(listener);
    }

    public synchronized void removeListener(final TypeSubstitutionChangedListener listener) {
        Preconditions.checkNotNull(listener, "Error: Listener argument can not be null.");
        substitutionListeners.removeListener(listener);
    }

    /**
     * Removes the {@link TypeMember member} instance corresponding to the given member id from the
     * type manager without modifying the backend.
     *
     * @param typeMemberId The backend id of the member that should be removed from the type manager.
     */
    public synchronized void removeMemberInstance(final int typeMemberId) {
        final TypeMember typeMember = typesContainer.getTypeMemberById(typeMemberId);
        final ImmutableSet<BaseType> affectedTypes = typesContainer.deleteTypeMemberById(typeMemberId);
        notifySubstitutionsChanged(affectedTypes);
        notifyTypesUpdated(affectedTypes);
        notifyMemberDeleted(typeMember);
    }

    /**
     * Removes the given {@link TypeSubstitution type substitution} from the type manager without
     * modifying the backend.
     *
     * @param substitution The {@link TypeSubstitution} to be deleted.
     */
    public synchronized void removeTypeSubstitutionInstance(final TypeSubstitution substitution) {
        Preconditions.checkNotNull(substitution, "Error: substitution argument can not be null");
        typesContainer.deleteTypeSubstitution(substitution);
    }

    /**
     * Marks the given base type as being a stack frame. This property has no immediate database
     * record, and any changes made to it are thus not synchronized to the database (or other running
     * instances of BinNavi). When the database is loaded it is set iff a function has marked this
     * base type as the associated stack frame type.
     *
     * @param baseType The {@link BaseType} to mark as being a stack frame.
     */
    public synchronized void setStackFrame(final BaseType baseType) {
        Preconditions.checkNotNull(baseType, "Error: baseType argument can not be null");
        baseType.setIsStackFrame(true);
    }

    /**
     * Updates an existing array type in the database.
     *
     * @param arrayType The existing array type.
     * @param elementType The new base type of the array elements.
     * @param numberOfElements The new number of elements.
     * @throws CouldntSaveDataException Thrown if the array type could not be updated in the backend.
     */
    public synchronized void updateArray(final BaseType arrayType, final BaseType elementType, final int numberOfElements) throws CouldntSaveDataException {
        Preconditions.checkNotNull(arrayType, "IE02791: Base type can not be null.");
        Preconditions.checkArgument(arrayType.getCategory() == BaseTypeCategory.ARRAY, "Base type must be an array.");
        Preconditions.checkNotNull(elementType, "IE02792: Element type can not be null.");
        Preconditions.checkArgument(numberOfElements > 0, "Number of elements must be above zero.");
        final TypeMember arrayMember = arrayType.iterator().next();
        typesContainer.updateTypeMember(arrayMember, elementType, arrayMember.getName(), arrayMember.getBitOffset(), Optional.of(numberOfElements), arrayMember.getArgumentIndex());
        final int newArraySize = arrayMember.getNumberOfElements().get() * arrayMember.getBitSize();
        final String newArrayName = buildArrayName(elementType, numberOfElements);
        final ImmutableSet<BaseType> affectedTypes = typesContainer.updateBaseType(arrayType, newArrayName, arrayType.isSigned(), newArraySize);
        backend.updateArrayMember(arrayMember, elementType, numberOfElements);
        backend.updateType(arrayType, newArrayName, arrayType.getBitSize(), arrayType.isSigned());
        notifyTypesUpdated(affectedTypes);
    }

    public synchronized void updateStructureMember(final TypeMember updatedMember, final BaseType newMemberBaseType, final String newMemberName, int newMemberOffset) throws CouldntSaveDataException {
        Preconditions.checkNotNull(updatedMember, "Error: updated member argument can not be null.");
        Preconditions.checkArgument(updatedMember.getParentType().getCategory() == BaseTypeCategory.STRUCT, "Error: updated member argument must be a member of a struct base type.");
        Preconditions.checkNotNull(newMemberBaseType, "Error: new member base type argument can not be null.");
        Preconditions.checkNotNull(newMemberName, "Error: new member name argument can not be null.");
        Preconditions.checkArgument(!newMemberName.isEmpty(), "Error: new member name argument can not be empty.");
        Preconditions.checkArgument(newMemberOffset >= 0, "Error: new member offset argument must be larger or equal to zero");
        final ImmutableMap<BaseType, Integer> originalTypeSizes = captureTypeSizesState(typesContainer.getAffectedTypes(updatedMember.getParentType()));
        final int memberSizeDelta = newMemberBaseType.getBitSize() - updatedMember.getBaseType().getBitSize();
        backend.updateStructureMember(updatedMember, newMemberBaseType, newMemberName, newMemberOffset);
        final ImmutableSet<BaseType> affectedTypes = typesContainer.updateTypeMember(updatedMember, newMemberBaseType, newMemberName, Optional.of(newMemberOffset), updatedMember.getNumberOfElements(), updatedMember.getArgumentIndex());
        notifyMemberUpdated(updatedMember, affectedTypes);
        for (final TypeMember member : updatedMember.getParentType().getSubsequentMembers(updatedMember)) {
            backend.updateStructureMember(member, member.getBaseType(), member.getName(), member.getBitOffset().get() + memberSizeDelta);
        }
        ensureConsistencyAfterTypeUpdate(affectedTypes, Sets.newHashSet(affectedTypes), originalTypeSizes);
    }

    public synchronized void updateUnionMember(final TypeMember updatedMember, final BaseType newMemberBaseType, final String newMemberName) throws CouldntSaveDataException {
        updateStructureMember(updatedMember, newMemberBaseType, newMemberName, 0);
    }

    public synchronized void updateFunctionPrototypeMember(final TypeMember updatedMember, final BaseType newMemberBaseType, final String newMemberName, int newMemberArgumentIndex) throws CouldntSaveDataException {
        Preconditions.checkNotNull(updatedMember, "Error: updated member argument can not be null.");
        Preconditions.checkArgument(updatedMember.getParentType().getCategory() == BaseTypeCategory.STRUCT, "Error: updated member argument must be a member of a struct base type.");
        Preconditions.checkNotNull(newMemberBaseType, "Error: new member base type argument can not be null.");
        Preconditions.checkNotNull(newMemberName, "Error: new member name argument can not be null.");
        Preconditions.checkArgument(!newMemberName.isEmpty(), "Error: new member name argument can not be empty.");
        Preconditions.checkArgument(newMemberArgumentIndex >= 0, "Error: new member argument index argument must be larger or equal to zero");
        backend.updateFunctionPrototypeMember(updatedMember, newMemberBaseType, newMemberName, newMemberArgumentIndex);
        final ImmutableSet<BaseType> affectedTypes = typesContainer.updateTypeMember(updatedMember, newMemberBaseType, newMemberName, updatedMember.getBitOffset(), updatedMember.getNumberOfElements(), Optional.of(newMemberArgumentIndex));
        notifyMemberUpdated(updatedMember, affectedTypes);
    }

    private static ImmutableMap<BaseType, Integer> captureTypeSizesState(final Set<BaseType> baseTypes) {
        final Builder<BaseType, Integer> builder = ImmutableMap.<BaseType, Integer>builder();
        for (BaseType baseType : baseTypes) {
            if (baseType.getCategory() != BaseTypeCategory.FUNCTION_PROTOTYPE) {
                builder.put(baseType, baseType.getBitSize());
            }
        }
        return builder.build();
    }

    /**
     * Updates an existing base type with the new properties. Note: the pointer aspect of a base type
     * can not be changed after the type has been created in order to avoid the many side effects such
     * a change would cause.
     *
     * @param baseType The base type that should be updated.
     * @param name The new name of the base type.
     * @param size The new size in bits of the base type.
     * @param isSigned Specifies whether the updated base type is signed.
     * @throws CouldntSaveDataException Thrown if the base type could not be updated in the backend.
     */
    public synchronized void updateType(final BaseType baseType, final String name, final int size, final boolean isSigned) throws CouldntSaveDataException {
        Preconditions.checkNotNull(baseType, "IE02422: Base type argument can not be null.");
        Preconditions.checkNotNull(name, "IE02621: Name argument can not be null.");
        Preconditions.checkArgument(size >= 0, "Size argument can not be negative.");
        final boolean sizeChanged = baseType.getBitSize() != size;
        final ImmutableMap<BaseType, Integer> originalTypeSizes = (sizeChanged) ? captureTypeSizesState(typesContainer.getAffectedTypes(baseType)) : ImmutableMap.<BaseType, Integer>of();
        backend.updateType(baseType, name, size, isSigned);
        final ImmutableSet<BaseType> affectedTypes = typesContainer.updateBaseType(baseType, name, isSigned, size);
        notifyTypesUpdated(affectedTypes);
        notifySubstitutionsChanged(affectedTypes);
        if (sizeChanged) {
            ensureConsistencyAfterTypeUpdate(affectedTypes, Sets.newHashSet(affectedTypes), originalTypeSizes);
        }
    }

    /**
     * Renames an existing base type and stores the changes to the backend.
     *
     * @param baseType The base type to rename.
     * @param newName The new name for the base type.
     * @throws CouldntSaveDataException Thrown if the changes couldn't be written to the backend.
     */
    public synchronized void renameType(final BaseType baseType, final String newName) throws CouldntSaveDataException {
        updateType(baseType, newName, baseType.getBitSize(), baseType.isSigned());
    }

    /**
     * Updates a type substitution only in the local storage not in the database.
     *
     * @param node The {@link INaviOperandTreeNode} to which the {@link TypeSubstitution type
     *        substitution} is associated.
     * @param baseTypeId The id of the {@link BaseType base type} which is the new {@link BaseType}.
     * @param memberPathIds The set of ids that describe a member path.
     * @param offset The new offset of the {@link TypeSubstitution type substitution}.
     */
    public synchronized void updateTypeSubstitution(final INaviOperandTreeNode node, final int baseTypeId, final Integer[] memberPathIds, final int offset) {
        Preconditions.checkNotNull(node, "Error: node argument can not be null.");
        Preconditions.checkNotNull(memberPathIds, "Error: member path ids can not be null.");
        final BaseType baseType = typesContainer.getBaseTypeById(baseTypeId);
        final TypeSubstitution typeSubstitution = node.getTypeSubstitution();
        typesContainer.updateTypeSubstitution(typeSubstitution, baseType, idsToMembers(memberPathIds), offset);
        notifySubstitutionChanged(typeSubstitution);
    }

    /**
     * Updates the given type substitution in the back end and notifies listeners about the changed
     * substitution.
     *
     * @param node The operand tree node whose substitution should be updated.
     * @param substitution The substitution to update.
     * @param baseType The new base type for the substitution.
     * @param memberPath The sequence of members that unambiguously references a member.
     * @param offset The new offset for the substitution.
     * @throws CouldntSaveDataException Thrown if the type substitution could not be updated.
     */
    public synchronized void updateTypeSubstitution(final INaviOperandTreeNode node, final TypeSubstitution substitution, final BaseType baseType, final List<TypeMember> memberPath, final int offset) throws CouldntSaveDataException {
        Preconditions.checkNotNull(node, "IE02799: Operand tree node can not be null.");
        Preconditions.checkNotNull(substitution, "IE02800: Type subustitution can not be null.");
        Preconditions.checkNotNull(baseType, "IE02801: Base type can not be null.");
        typesContainer.updateTypeSubstitution(substitution, baseType, memberPath, offset);
        backend.updateSubstitution(substitution, baseType, membersToIds(memberPath), offset);
        notifySubstitutionChanged(substitution);
    }

    /**
     * Recurses over a set of types whose member offsets need to be fixed due to a size change of a
     * single base type. Since the order in which member offsets are fixed is important, the
     * implementation ensures that immediately affected types (i.e. types that have member of the
     * changed base type) are changed before implicitly affected types.
     */
    // TODO(jannewger): oldSizes and affectedTypes could be merged into one Map.
    private void adjustMemberOffsets(final BaseType structTypeToFix, final Set<BaseType> inconsistentTypes, final Map<BaseType, Integer> oldSizes, final Set<BaseType> affectedTypes) throws CouldntSaveDataException {
        if (!inconsistentTypes.contains(structTypeToFix)) {
            return;
        }
        // Find the first member that requires an update; all subsequent members require offset updates
        // as well.
        final List<TypeMember> affectedMembers = determineMembersToUpdate(structTypeToFix, affectedTypes);
        int sizeDelta = 0;
        for (final TypeMember member : affectedMembers) {
            final BaseType memberBaseType = member.getBaseType();
            if (inconsistentTypes.contains(memberBaseType)) {
                // If we encounter a type whose size was changed but that is not yet fixed, recurse.
                adjustMemberOffsets(memberBaseType, inconsistentTypes, oldSizes, affectedTypes);
            }
            if (sizeDelta != 0) {
                Integer newOffset = member.getBitOffset().get() + sizeDelta;
                // TODO(jannewger): collect all updateMember calls and send them out in one batch (once we
                // have the global database lock).
                backend.updateStructureMember(member, member.getBaseType(), member.getName(), newOffset);
                notifyMemberUpdated(member);
                member.setOffset(Optional.of(newOffset));
            }
            // If the size of the member type changed we need to update the size delta as well since that
            // affects subsequent member offsets.
            if (oldSizes.containsKey(memberBaseType)) {
                sizeDelta += memberBaseType.getBitSize() - oldSizes.get(memberBaseType);
            }
        }
        inconsistentTypes.remove(structTypeToFix);
    }

    private static ImmutableList<TypeMember> determineMembersToUpdate(final BaseType baseType, final Set<BaseType> affectedTypes) {
        final ImmutableList.Builder<TypeMember> builder = ImmutableList.<TypeMember>builder();
        boolean includeMember = false;
        for (TypeMember member : baseType) {
            if (affectedTypes.contains(member.getBaseType())) {
                // All members after the first one whose base type is contained in affectedTypes need
                // to be updated.
                if (!includeMember) {
                    includeMember = true;
                }
            }
            if (includeMember) {
                builder.add(member);
            }
        }
        return builder.build();
    }

    /**
     * Transitively propagates effects of a size change of a single base type through the type system.
     *
     * @param affectedTypes The set of types that depend on the updated type.
     * @param inconsistentTypes The set of types that are still inconsistent due to the size change.
     * @param oldSizes A mapping of all affected types to their sizes before updatedType was changed.
     *
     * @throws CouldntSaveDataException Thrown if the transitive effects couldn't be written to the
     *         database.
     */
    private void ensureConsistencyAfterTypeUpdate(final ImmutableSet<BaseType> affectedTypes, final Set<BaseType> inconsistentTypes, final Map<BaseType, Integer> oldSizes) throws CouldntSaveDataException {
        while (!inconsistentTypes.isEmpty()) {
            final BaseType baseType = inconsistentTypes.iterator().next();
            if (baseType.getCategory() == BaseTypeCategory.STRUCT) {
                adjustMemberOffsets(baseType, inconsistentTypes, oldSizes, affectedTypes);
            } else {
                inconsistentTypes.remove(baseType);
            }
        }
    }

    private final class TypesContainer {

        private final Set<BaseType> types = new LinkedHashSet<>();

        private final Map<String, BaseType> typesByName = new HashMap<>();

        private final Map<Integer, BaseType> typesById = new HashMap<>();

        private final ArrayList<BaseType> stableTypeList = new ArrayList<>();

        private final HashMultimap<BaseType, TypeSubstitution> substitutionsByType = HashMultimap.create();

        private final Map<Integer, TypeMember> memberById = new HashMap<>();

        private final TypeDependenceGraph dependenceGraph;

        public TypesContainer(final List<RawBaseType> rawBaseTypes, final List<RawTypeMember> rawMembers) {
            dependenceGraph = initializeTypeSystem(rawBaseTypes, rawMembers);
        }

        public void addBaseType(final BaseType baseType) {
            Preconditions.checkNotNull(baseType, "Error: baseType argument can not be null");
            types.add(baseType);
            typesById.put(baseType.getId(), baseType);
            typesByName.put(baseType.getName(), baseType);
            stableTypeList.clear();
            dependenceGraph.addType(baseType);
        }

        public ImmutableSet<BaseType> addMember(final RawTypeMember rawTypeMember) {
            if (rawTypeMember.getOffset().isPresent()) {
                return addMember(TypeMember.createStructureMember(rawTypeMember.getId(), typesById.get(rawTypeMember.getParentId()), typesById.get(rawTypeMember.getBaseTypeId()), rawTypeMember.getName(), rawTypeMember.getOffset().get()));
            }
            if (rawTypeMember.getNumberOfElements().isPresent()) {
                return addMember(TypeMember.createArrayMember(rawTypeMember.getId(), typesById.get(rawTypeMember.getParentId()), typesById.get(rawTypeMember.getBaseTypeId()), rawTypeMember.getName(), rawTypeMember.getNumberOfElements().get()));
            }
            if (rawTypeMember.getArgumentIndex().isPresent()) {
                return addMember(TypeMember.createFunctionPrototypeMember(rawTypeMember.getId(), typesById.get(rawTypeMember.getParentId()), typesById.get(rawTypeMember.getBaseTypeId()), rawTypeMember.getName(), rawTypeMember.getArgumentIndex().get()));
            }
            throw new IllegalStateException("Error: member can only be added to compound types.");
        }

        public ImmutableSet<BaseType> addMember(final TypeMember member) {
            Preconditions.checkNotNull(member, "Error: typeMember argument can not be null");
            final DependenceResult result = dependenceGraph.addMember(member.getParentType(), member.getBaseType());
            if (!result.isValid()) {
                throw new IllegalStateException("Error: member would create cyclic reference.");
            }
            memberById.put(member.getId(), member);
            member.getParentType().addMember(member);
            return result.getAffectedTypes();
        }

        public void addTypeSubstitution(final TypeSubstitution substitution) {
            substitutionsByType.put(substitution.getBaseType(), substitution);
        }

        public List<TypeMember> clearMembers(final BaseType deletedType, final Set<BaseType> affectedTypes) {
            final List<TypeMember> deletedMembers = Lists.newArrayList();
            for (final BaseType currentType : affectedTypes) {
                final List<TypeMember> membersToDelete = Lists.newArrayList();
                for (final TypeMember member : currentType) {
                    if (member.getBaseType() == deletedType) {
                        membersToDelete.add(member);
                    }
                }
                if (membersToDelete.size() > 0) {
                    for (final TypeMember typeMembert : membersToDelete) {
                        deleteTypeMember(typeMembert);
                    }
                    deletedMembers.addAll(membersToDelete);
                }
            }
            return deletedMembers;
        }

        public Set<BaseType> deleteBaseType(final BaseType baseType) {
            Preconditions.checkNotNull(baseType, "Error: baseType argument can not be null");
            types.remove(baseType);
            typesById.remove(baseType.getId());
            typesByName.remove(baseType.getName());
            substitutionsByType.removeAll(baseType);
            stableTypeList.clear();
            return dependenceGraph.deleteType(baseType);
        }

        public Set<BaseType> deleteBaseTypeById(final int baseTypeId) {
            return deleteBaseType(typesById.get(baseTypeId));
        }

        public Set<TypeSubstitution> deleteSubstitutionsByType(final BaseType deletedType) {
            return substitutionsByType.removeAll(deletedType);
        }

        public ImmutableSet<BaseType> deleteTypeMember(final TypeMember typeMember) {
            Preconditions.checkNotNull(typeMember, "Error: typeMember argument can not be null");
            typeMember.getParentType().deleteMember(typeMember);
            memberById.remove(typeMember.getId());
            return dependenceGraph.deleteMember(typeMember);
        }

        public ImmutableSet<BaseType> deleteTypeMemberById(final int typeMemberId) {
            return deleteTypeMember(memberById.get(typeMemberId));
        }

        public void deleteTypeSubstitution(final TypeSubstitution typeSubstitution) {
            Preconditions.checkNotNull(typeSubstitution, "Error: typeSubstitution argument can not be null");
            substitutionsByType.remove(typeSubstitution.getBaseType(), typeSubstitution);
        }

        public boolean doesTypeNameExist(final String name) {
            return typesByName.containsKey(name);
        }

        private Set<TypeSubstitution> getAffectedTypeSubstitutions(final Set<BaseType> baseTypes) {
            final Set<BaseType> affectedTypes = Sets.intersection(baseTypes, substitutionsByType.keySet());
            final HashSet<TypeSubstitution> typeSubstitutions = Sets.newHashSet();
            for (final BaseType baseType : affectedTypes) {
                typeSubstitutions.addAll(substitutionsByType.get(baseType));
            }
            return typeSubstitutions;
        }

        public ImmutableSet<BaseType> getAffectedTypes(final BaseType baseType) {
            return dependenceGraph.determineDependentTypes(baseType);
        }

        public BaseType getBaseTypeById(final int baseTypeId) {
            return typesById.get(baseTypeId);
        }

        public TypeMember getTypeMemberById(final int typeMemberId) {
            return memberById.get(typeMemberId);
        }

        /**
         * Returns a stable {@link List list} of {@link BaseType base types}. The set which holds the
         * actual state of the stored {@link BaseType base types} can not be used by index which the GUI
         * requires to work efficiently.
         *
         * @return Stable {@link List list} of {@link BaseType base types}.
         */
        public List<BaseType> getTypes() {
            if (stableTypeList.isEmpty()) {
                stableTypeList.addAll(types);
            }
            return Collections.unmodifiableList(stableTypeList);
        }

        /**
         * Initializes the type system with the data passed as arguments. The function assumes that the
         * data is consistent. This especially is true for cyclic references within the source data.
         * Passing in inconsistent data will result in an exception.
         *
         * @param rawBaseTypes The {@link List list} of {@link RawBaseType raw base types} from which to
         *        create the {@link BaseType base types} for the type system.
         * @param rawMembers The {@link List list} of {@link RawTypeMember raw type members} from which
         *        to create the {@link TypeMember type members} for the type system.
         *
         * @return A {@link TypeDependenceGraph type dependence graph}
         */
        private TypeDependenceGraph initializeTypeSystem(final List<RawBaseType> rawBaseTypes, final List<RawTypeMember> rawMembers) {
            // 1) We add all base types without members first since members could reference types that
            // aren't converted to BaseType, yet.
            ImmutableList.Builder<BaseType> baseTypes = ImmutableList.<BaseType>builder();
            for (final RawBaseType rawType : rawBaseTypes) {
                final BaseType newType = new BaseType(rawType.getId(), rawType.getName(), rawType.getSize(), rawType.isSigned(), rawType.getCategory());
                types.add(newType);
                typesById.put(newType.getId(), newType);
                typesByName.put(newType.getName(), newType);
                stableTypeList.clear();
                baseTypes.add(newType);
            }
            // 2) Associate all base types with their members.
            ImmutableList.Builder<TypeMember> typeMembers = ImmutableList.<TypeMember>builder();
            for (final RawTypeMember rawMember : rawMembers) {
                final BaseType parentType = typesById.get(rawMember.getParentId());
                TypeMember newMember = null;
                if (rawMember.getOffset().isPresent()) {
                    newMember = TypeMember.createStructureMember(rawMember.getId(), parentType, typesById.get(rawMember.getBaseTypeId()), rawMember.getName(), rawMember.getOffset().get());
                } else if (rawMember.getArgumentIndex().isPresent()) {
                    newMember = TypeMember.createFunctionPrototypeMember(rawMember.getId(), parentType, typesById.get(rawMember.getBaseTypeId()), rawMember.getName(), rawMember.getArgumentIndex().get());
                } else if (rawMember.getNumberOfElements().isPresent()) {
                    newMember = TypeMember.createArrayMember(rawMember.getId(), parentType, typesById.get(rawMember.getBaseTypeId()), rawMember.getName(), rawMember.getNumberOfElements().get());
                } else {
                    throw new IllegalStateException("Error: can not associate the raw member to a compound type.");
                }
                typeMembers.add(newMember);
                memberById.put(newMember.getId(), newMember);
                newMember.getParentType().addMember(newMember);
            }
            // 3) Link pointer types, i.e. create pointer hierarchy.
            for (final RawBaseType rawType : rawBaseTypes) {
                final Integer pointerId = rawType.getPointerId();
                if (pointerId != null) {
                    final BaseType child = typesById.get(pointerId);
                    final BaseType parent = typesById.get(rawType.getId());
                    BaseType.appendToPointerHierarchy(child, parent);
                }
            }
            return new TypeDependenceGraph(baseTypes.build(), typeMembers.build());
        }

        public boolean isTypeContainedIn(final BaseType superType, final BaseType baseType) {
            return dependenceGraph.isTypeContainedIn(superType, baseType);
        }

        public ImmutableSet<BaseType> updateBaseType(final BaseType baseType, final String newName, final boolean signed, final int size) {
            Preconditions.checkNotNull(baseType, "Error: baseType argument can not be null.");
            Preconditions.checkNotNull(newName, "Error: newName argument can not be null.");
            if (!baseType.getName().equals(newName)) {
                typesByName.remove(baseType.getName());
                typesByName.put(newName, baseType);
                baseType.setName(newName);
            }
            // It is crucial that the size is only updated if this is a non-compound type since sizes for
            // compound types are determined implicitly.
            if (baseType.getCategory() == BaseTypeCategory.ATOMIC || baseType.getCategory() == BaseTypeCategory.POINTER && baseType.getBitSize() != size) {
                baseType.setSize(size);
            }
            baseType.setSigned(signed);
            return dependenceGraph.updateType(baseType);
        }

        public ImmutableSet<BaseType> updateTypeMember(final TypeMember typeMember, final BaseType newMemberBaseType, final String newMemberName, final Optional<Integer> newMemberOffset, final Optional<Integer> newMemberNumberOfElements, final Optional<Integer> newMemberArgumentIndex) {
            Preconditions.checkNotNull(typeMember, "Error: typeMember argument can not be null.");
            Preconditions.checkNotNull(newMemberName, "Error: memberName argument can not be null.");
            Preconditions.checkNotNull(newMemberBaseType, "Error: baseType argument can not be null.");
            Preconditions.checkNotNull(newMemberNumberOfElements, "Error: number of elements argument can not be null.");
            Preconditions.checkNotNull(newMemberOffset, "Error: offset argument can not be null.");
            Preconditions.checkNotNull(newMemberArgumentIndex, "Error: argument index argument can not be null.");
            Preconditions.checkNotNull(newMemberBaseType, "Error: basetype argument can not be null.");
            typeMember.setBaseType(newMemberBaseType);
            typeMember.setName(newMemberName);
            typeMember.setNumberOfElements(newMemberNumberOfElements);
            typeMember.setOffset(newMemberOffset);
            typeMember.setArgumentIndex(newMemberArgumentIndex);
            return dependenceGraph.updateMember(typeMember.getParentType(), typeMember.getBaseType(), newMemberBaseType).getAffectedTypes();
        }

        public void updateTypeSubstitution(final TypeSubstitution typeSubstitution, final BaseType baseType, final List<TypeMember> memberPath, final int offset) {
            Preconditions.checkNotNull(typeSubstitution, "Error: TypeSubstitution argument can not be null.");
            Preconditions.checkNotNull(baseType, "Error: BaseType argument can not be null.");
            Preconditions.checkNotNull(memberPath, "Error: Member path can not be null.");
            substitutionsByType.remove(typeSubstitution.getBaseType(), typeSubstitution);
            typeSubstitution.setBaseType(baseType);
            typeSubstitution.setOffset(offset);
            typeSubstitution.setMemberPath(memberPath);
            substitutionsByType.put(baseType, typeSubstitution);
        }

        public boolean willTypeCreateCyclicReference(final BaseType containingType, final BaseType memberType) {
            return dependenceGraph.willCreateCycle(containingType, memberType);
        }
    }
}