de.iteratec.iteraplan.elasticeam.emfimpl.EMFMetamodel.java Source code

Java tutorial

Introduction

Here is the source code for de.iteratec.iteraplan.elasticeam.emfimpl.EMFMetamodel.java

Source

/*
 * iteraplan is an IT Governance web application developed by iteratec, GmbH
 * Copyright (C) 2004 - 2014 iteratec, GmbH
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by
 * the Free Software Foundation with the addition of the following permission
 * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
 * WORK IN WHICH THE COPYRIGHT IS OWNED BY ITERATEC, ITERATEC DISCLAIMS THE
 * WARRANTY OF NON INFRINGEMENT  OF THIRD PARTY RIGHTS.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301 USA.
 *
 * You can contact iteratec GmbH headquarters at Inselkammerstr. 4
 * 82008 Munich - Unterhaching, Germany, or at email address info@iteratec.de.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "iteraplan" logo. If the display of the logo is not reasonably
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Powered by iteraplan".
 */
package de.iteratec.iteraplan.elasticeam.emfimpl;

import java.awt.Color;
import java.util.List;
import java.util.Map.Entry;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;

import org.apache.commons.collections.ListUtils;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EEnum;
import org.eclipse.emf.ecore.EEnumLiteral;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcoreFactory;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.util.EcoreUtil;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;

import de.iteratec.iteraplan.elasticeam.ElasticeamContext;
import de.iteratec.iteraplan.elasticeam.exception.MetamodelException;
import de.iteratec.iteraplan.elasticeam.metamodel.DataTypeExpression;
import de.iteratec.iteraplan.elasticeam.metamodel.EditableMetamodel;
import de.iteratec.iteraplan.elasticeam.metamodel.EnumerationExpression;
import de.iteratec.iteraplan.elasticeam.metamodel.EnumerationLiteralExpression;
import de.iteratec.iteraplan.elasticeam.metamodel.EnumerationPropertyExpression;
import de.iteratec.iteraplan.elasticeam.metamodel.FeatureExpression;
import de.iteratec.iteraplan.elasticeam.metamodel.FeaturePermissions;
import de.iteratec.iteraplan.elasticeam.metamodel.MixedOrPropertyExpression;
import de.iteratec.iteraplan.elasticeam.metamodel.MixinTypeExpression;
import de.iteratec.iteraplan.elasticeam.metamodel.NamedExpression.NameChangeEvent;
import de.iteratec.iteraplan.elasticeam.metamodel.PrimitivePropertyExpression;
import de.iteratec.iteraplan.elasticeam.metamodel.PrimitiveTypeExpression;
import de.iteratec.iteraplan.elasticeam.metamodel.PropertyExpression;
import de.iteratec.iteraplan.elasticeam.metamodel.RelationshipEndExpression;
import de.iteratec.iteraplan.elasticeam.metamodel.RelationshipExpression;
import de.iteratec.iteraplan.elasticeam.metamodel.RelationshipTypeExpression;
import de.iteratec.iteraplan.elasticeam.metamodel.SubstantialTypeExpression;
import de.iteratec.iteraplan.elasticeam.metamodel.TypeExpression;
import de.iteratec.iteraplan.elasticeam.metamodel.UniversalTypeExpression;
import de.iteratec.iteraplan.elasticeam.metamodel.UniversalTypePermissions;
import de.iteratec.iteraplan.elasticeam.metamodel.builtin.BuiltinPrimitiveType;
import de.iteratec.iteraplan.elasticeam.metamodel.builtin.MixinTypeNamed;
import de.iteratec.iteraplan.elasticeam.util.ElasticeamContextUtil;
import de.iteratec.iteraplan.elasticeam.util.I18nMap;
import de.iteratec.iteraplan.model.user.Role;

@SuppressWarnings("PMD.TooManyMethods")
public class EMFMetamodel implements EditableMetamodel, Observer {

    private static final String ANNOTATIONURI = "urn:IM2L";
    private static final String DISCRIMINATORKEY = "type";

    /**
     * Annotate an {@link EDataType} to flag as {@link EnumerationExpression}
     * 
     * @param eDataType
     *    the {@link EDataType} to annotate
     */
    public static void annotateEnumerationType(EDataType eDataType) {
        EcoreUtil.setAnnotation(eDataType, ANNOTATIONURI, DISCRIMINATORKEY,
                EnumerationExpression.class.getSimpleName());
    }

    /**
     * Annotate an {@link EDataType} to flag as {@link PrimitiveTypeExpression}
     * 
     * @param eDataType
     *    the {@link EDataType} to annotate
     */
    public static void annotatePrimitiveType(EDataType eDataType) {
        EcoreUtil.setAnnotation(eDataType, ANNOTATIONURI, DISCRIMINATORKEY,
                PrimitiveTypeExpression.class.getSimpleName());
    }

    /**
     * Annotate an {@link EClass} to flag as {@link RelationshipTypeExpression}
     * 
     * @param eClass
     *    the {@link EClass} to annotate
     */
    public static void annotateRelationshipType(EClass eClass) {
        EcoreUtil.setAnnotation(eClass, ANNOTATIONURI, DISCRIMINATORKEY,
                RelationshipTypeExpression.class.getSimpleName());
    }

    /**
     * Annotate an {@link EClass} to flag as {@link SubstantialTypeExpression}
     * 
     * @param eClass
     *    the {@link EClass} to annotate
     */
    public static void annotateSubstantialType(EClass eClass) {
        EcoreUtil.setAnnotation(eClass, ANNOTATIONURI, DISCRIMINATORKEY,
                SubstantialTypeExpression.class.getSimpleName());
    }

    /**
     * Checks the annotations of an {@link EDataType} to check whether it is representing an {@link EnumerationExpression}
     * 
     * @param eDataType
     *    the {@link EDataType} to check
     * @return
     *    true, if the {@link EDataType} is representing an {@link EnumerationExpression}
     */
    public static boolean isEnumerationType(EDataType eDataType) {
        return EnumerationExpression.class.getSimpleName()
                .equals(EcoreUtil.getAnnotation(eDataType, ANNOTATIONURI, DISCRIMINATORKEY));
    }

    /**
     * Checks the annotations of an {@link EDataType} to check whether it is representing a {@link PrimitiveTypeExpression}
     * 
     * @param eDataType
     *    the {@link EDataType} to check
     * @return
     *    true, if the {@link EDataType} is representing a {@link PrimitiveTypeExpression}
     */
    public static boolean isPrimitiveType(EDataType eDataType) {
        return PrimitiveTypeExpression.class.getSimpleName()
                .equals(EcoreUtil.getAnnotation(eDataType, ANNOTATIONURI, DISCRIMINATORKEY));
    }

    /**
     * Checks the {@link EClass}' annotations to check whether it is representing an {@link RelationshipTypeExpression} or not (e.g. {@link SubstantialTypeExpression})
     * @param eClass
     *    the {@link EClass} to check
     * @return
     *    true, if the {@link EClass} is representing a {@link RelationshipTypeExpression}
     */
    public static boolean isRelationshipType(EClass eClass) {
        return RelationshipTypeExpression.class.getSimpleName()
                .equals(EcoreUtil.getAnnotation(eClass, ANNOTATIONURI, DISCRIMINATORKEY));
    }

    /**
     * Checks the {@link EClass}' annotations to check whether it is representing an {@link SubstantialTypeExpression} or not (e.g. {@link RelationshipTypeExpression})
     * @param eClass
     *    the {@link EClass} to check
     * @return
     *    true, if the {@link EClass} is representing a {@link SubstantialTypeExpression}
     */
    public final static boolean isSubstantialType(EClass eClass) {
        return SubstantialTypeExpression.class.getSimpleName()
                .equals(EcoreUtil.getAnnotation(eClass, ANNOTATIONURI, DISCRIMINATORKEY));
    }

    private EPackage wrapped;
    private final BiMap<EAttribute, BiMap<EClass, PropertyExpression<?>>> properties;
    private final BiMap<EDataType, PrimitiveTypeExpression> primitiveTypes;

    private final BiMap<EEnum, EnumerationExpression> enumerations;
    private final BiMap<EEnumLiteral, BiMap<EEnum, EnumerationLiteralExpression>> literals;

    private final BiMap<EReference, BiMap<EClass, RelationshipEndExpression>> relationshipEnds;

    private final BiMap<EClass, UniversalTypeExpression> universalTypes;

    private final I18nMap<TypeExpression> types;

    private final Multimap<RelationshipExpression, RelationshipEndExpression> relationship2relationshipEnds;

    private final Multimap<EnumerationExpression, EnumerationPropertyExpression> enumeration2properties;

    private final Multimap<PrimitiveTypeExpression, PrimitivePropertyExpression> primitiveType2properties;

    private final ListMultimap<Role, TypeExpression> readableTypes;

    private final ListMultimap<Role, FeatureExpression<?>> readableFeatures;

    private boolean disableAccessControl = false;

    /**
     * Default constructor.
     */
    public EMFMetamodel(String persistentName) {
        this();
        this.wrapped = EcoreFactory.eINSTANCE.createEPackage();
        this.wrapped.setName(persistentName);
        addDefaultPrimitiveTypes();
    }

    /**
     * Creates a metamodel which does not not perform access control. 
     */
    public EMFMetamodel(String persistentName, boolean disableAccessControl) {
        this(persistentName);
        this.disableAccessControl = disableAccessControl;
    }

    private EMFMetamodel() {
        this.properties = HashBiMap.create();
        this.primitiveTypes = HashBiMap.create();
        this.enumerations = HashBiMap.create();
        this.literals = HashBiMap.create();
        this.relationshipEnds = HashBiMap.create();
        this.universalTypes = HashBiMap.create();
        this.enumeration2properties = ArrayListMultimap.create();
        this.relationship2relationshipEnds = ArrayListMultimap.create();
        this.primitiveType2properties = ArrayListMultimap.create();
        this.types = I18nMap.create();
        this.readableFeatures = ArrayListMultimap.create();
        this.readableTypes = ArrayListMultimap.create();
    }

    /**{@inheritDoc}**/
    public void addMixin(SubstantialTypeExpression substantialType, MixinTypeExpression mixin) {
        for (PropertyExpression<?> pe : mixin.getProperties()) {
            if (pe instanceof EnumerationPropertyExpression) {
                EMFEnumerationProperty prop = createPropertyImpl(this.universalTypes.inverse().get(substantialType),
                        pe.getPersistentName(), pe.getLowerBound(), pe.getUpperBound(),
                        this.enumerations.inverse().get(((EnumerationPropertyExpression) pe).getType()), mixin);
                //TODO
                //NamedImpl.copy(pe, prop);

                prop.addObserver(substantialType);
                prop.setName(pe.getPersistentName());
            }
            if (pe instanceof PrimitivePropertyExpression) {
                EMFPrimitiveProperty prop = createPropertyImpl(this.universalTypes.inverse().get(substantialType),
                        pe.getPersistentName(), pe.getLowerBound(), pe.getUpperBound(),
                        getEDataType(((PrimitivePropertyExpression) pe).getType().getEncapsulatedType()), mixin);
                prop.addObserver(substantialType);
                prop.setName(pe.getPersistentName());
                //TODO
                //NamedImpl.copy(pe, prop);
            }
        }
        for (Entry<RelationshipEndExpression, RelationshipEndExpression> entry : mixin.getRelationshipEndPairs()
                .entrySet()) {
            // needs to be changed, in case of mixins representing relationships connecting different types 
            createRelationship(mixin.getPersistentName() + "_" + substantialType.getPersistentName(),
                    substantialType, entry.getKey().getPersistentName(), entry.getKey().getLowerBound(),
                    entry.getKey().getUpperBound(), substantialType, entry.getValue().getPersistentName(),
                    entry.getValue().getLowerBound(), entry.getValue().getUpperBound());
        }
    }

    /**{@inheritDoc}**/
    public final EnumerationExpression createEnumeration(String persistentName) {
        if (this.wrapped.getEClassifier(persistentName) != null) {
            throw new MetamodelException(MetamodelException.GENERAL_ERROR,
                    "Cannot create enumeration with non unique persistent name " + persistentName + ".");
        }
        EEnum eEnum = EcoreFactory.eINSTANCE.createEEnum();
        eEnum.setName(persistentName);
        annotateEnumerationType(eEnum);
        this.wrapped.getEClassifiers().add(eEnum);
        EMFEnumeration enumeration = new EMFEnumeration(eEnum, this);
        this.enumerations.put(eEnum, enumeration);
        addNullLiteral(eEnum);
        enumeration.addObserver(this);
        return enumeration;
    }

    /**
     * After creation of new EEnum (for a new EMFEnumeration), create a default literal that represents "null"/"unspecified"
     * @param eEnum
     */
    public static final void addNullLiteral(EEnum eEnum) {
        EEnumLiteral nullLiteral = EcoreFactory.eINSTANCE.createEEnumLiteral();
        nullLiteral.setValue(0);
        nullLiteral.setName(EMFEnumerationLiteral.NOT_SPECIFIED);
        nullLiteral.setLiteral(EMFEnumerationLiteral.NOT_SPECIFIED);
        eEnum.getELiterals().add(nullLiteral);
    }

    /**{@inheritDoc}**/
    public final EnumerationLiteralExpression createEnumerationLiteral(EnumerationExpression enumeration,
            String persistentName, Color defaultLiteralColor) {
        EEnum eEnum = this.enumerations.inverse().get(enumeration);
        if (eEnum == null) {
            throw new MetamodelException(MetamodelException.GENERAL_ERROR,
                    "Cannot create enumeration literal for non-canonic enumeration expression.");
        }
        if (eEnum.getEEnumLiteral(persistentName) != null) {
            throw new MetamodelException(MetamodelException.GENERAL_ERROR,
                    "Cannot create enumeration literal with non unique persistent name " + persistentName + ".");
        }
        EEnumLiteral eEnumLiteral = EcoreFactory.eINSTANCE.createEEnumLiteral();
        eEnumLiteral.setName(persistentName);
        eEnumLiteral.setValue(eEnum.getELiterals().size());
        eEnum.getELiterals().add(eEnumLiteral);
        EMFEnumerationLiteral literal = new EMFEnumerationLiteral(eEnumLiteral, this, defaultLiteralColor);
        BiMap<EEnum, EnumerationLiteralExpression> value = HashBiMap.create();
        value.put(eEnum, literal);
        this.literals.put(eEnumLiteral, value);
        return literal;
    }

    public final PropertyExpression<?> createProperty(UniversalTypeExpression owner, String persistentName,
            int lowerBound, int upperBound, DataTypeExpression type) {
        if (PrimitiveTypeExpression.class.isInstance(type)) {
            return createProperty(owner, persistentName, lowerBound, upperBound, (PrimitiveTypeExpression) type);
        } else if (EnumerationExpression.class.isInstance(type)) {
            return createProperty(owner, persistentName, lowerBound, upperBound, (EnumerationExpression) type);
        } else {
            throw new MetamodelException(MetamodelException.GENERAL_ERROR, "Unknown data type expression: " + type);
        }
    }

    /**{@inheritDoc}**/
    public final EnumerationPropertyExpression createProperty(UniversalTypeExpression owner, String persistentName,
            int lowerBound, int upperBound, EnumerationExpression type) {
        EClass eClass = this.universalTypes.inverse().get(owner);
        if (eClass == null) {
            throw new MetamodelException(MetamodelException.GENERAL_ERROR,
                    "Cannot add canonic property to non-canonic universal type.");
        }
        if (eClass.getEStructuralFeature(persistentName) != null) {
            throw new MetamodelException(MetamodelException.GENERAL_ERROR,
                    "Cannot add property with duplicate persistent name " + persistentName + ".");
        }
        EEnum eEnum = this.enumerations.inverse().get(type);
        //    if (eEnum == null) {
        //      throw new MetamodelException(MetamodelException.GENERAL_ERROR, "Cannot add canonic property to non-canonic type.");
        //    }

        EMFEnumerationProperty property = createPropertyImpl(eClass, persistentName, lowerBound, upperBound, eEnum);
        property.addObserver(owner);
        property.setName(persistentName);

        return property;
    }

    /**{@inheritDoc}**/
    public final PrimitivePropertyExpression createProperty(UniversalTypeExpression owner, String persistentName,
            int lowerBound, int upperBound, PrimitiveTypeExpression type) {
        EClass eClass = this.universalTypes.inverse().get(owner);
        if (eClass == null) {
            throw new MetamodelException(MetamodelException.GENERAL_ERROR,
                    "Cannot add canonic property to non-canonic universal type.");
        }
        if (eClass.getEStructuralFeature(persistentName) != null) {
            throw new MetamodelException(MetamodelException.GENERAL_ERROR,
                    "Cannot add property with duplicate persistent name " + persistentName + ".");
        }

        EDataType dataType = getEDataType(type.getEncapsulatedType());

        if (dataType == null || !this.primitiveTypes.containsKey(dataType)) {
            throw new MetamodelException(MetamodelException.GENERAL_ERROR,
                    "Can not create a property whose primitive type does not exist in the metamodel.");
        }

        EMFPrimitiveProperty property = createPropertyImpl(eClass, persistentName, lowerBound, upperBound,
                dataType);
        property.addObserver(owner);
        property.setName(persistentName);

        return property;
    }

    /**{@inheritDoc}**/
    public MixedOrPropertyExpression createMixedOrProperty(UniversalTypeExpression owner, String persistentName,
            int lowerBound, int upperBound, Set<PrimitiveTypeExpression> admissibleDataTypes,
            boolean matchEnumerations) {
        EClass eClass = this.universalTypes.inverse().get(owner);
        if (eClass == null) {
            throw new MetamodelException(MetamodelException.GENERAL_ERROR,
                    "Cannot add canonic property to non-canonic universal type.");
        }
        if (eClass.getEStructuralFeature(persistentName) != null) {
            throw new MetamodelException(MetamodelException.GENERAL_ERROR,
                    "Cannot add property with duplicate persistent name " + persistentName + ".");
        }

        if (admissibleDataTypes == null || admissibleDataTypes.isEmpty()) {
            throw new MetamodelException(MetamodelException.GENERAL_ERROR,
                    "A mixed OR property must have at least one primitive type.");
        }

        EDataType mainEDataType = null;
        Set<EDataType> allEDataTypes = Sets.newHashSet();
        for (PrimitiveTypeExpression type : admissibleDataTypes) {
            EDataType dataType = getEDataType(type.getEncapsulatedType());

            if (dataType == null || !this.primitiveTypes.containsKey(dataType)) {
                throw new MetamodelException(MetamodelException.GENERAL_ERROR,
                        "Can not create a property whose primitive type does not exist in the metamodel.");
            }
            if (mainEDataType == null) {
                mainEDataType = dataType;
            }
            allEDataTypes.add(dataType);
        }

        EAttribute eAttribute = EcoreFactory.eINSTANCE.createEAttribute();
        //EcoreUtil.setAnnotation(eAttribute, ANNOTATIONURI, DISCRIMINATORKEY, "mixedOrProperty");
        eAttribute.setName(persistentName);
        eAttribute.setLowerBound(lowerBound);
        eAttribute.setUpperBound(upperBound);
        eAttribute.setEType(mainEDataType);
        eClass.getEStructuralFeatures().add(eAttribute);

        EMFMixedOrProperty result = new EMFMixedOrProperty(eAttribute, this, allEDataTypes, matchEnumerations);

        BiMap<EClass, PropertyExpression<?>> value = HashBiMap.create();
        value.put(eClass, result);
        this.properties.put(eAttribute, value);

        for (PrimitiveTypeExpression primitiveType : result.getTypes()) {
            this.primitiveType2properties.put(primitiveType, result);
        }

        result.addObserver(owner);
        result.setName(persistentName);

        return result;
    }

    /**{@inheritDoc}**/
    public final RelationshipExpression createRelationship(String persistentName, UniversalTypeExpression holder0,
            String end0Name, int end0lower, int end0upper, UniversalTypeExpression holder1, String end1Name,
            int end1lower, int end1upper) {
        return createRelationship(persistentName, holder0, end0Name, end0lower, end0upper, holder1, end1Name,
                end1lower, end1upper, true);
    }

    /**{@inheritDoc}**/
    public RelationshipExpression createRelationship(String persistentName, UniversalTypeExpression type,
            String end0Name, int end0lower, int end0upper, String end1Name, int end1lower, int end1upper,
            boolean acycic) {
        return createRelationship(persistentName, type, end0Name, end0lower, end0upper, type, end1Name, end1lower,
                end1upper, acycic);
    }

    final RelationshipExpression createRelationship(String persistentName, UniversalTypeExpression holder0,
            String end0Name, int end0lower, int end0upper, UniversalTypeExpression holder1, String end1Name,
            int end1lower, int end1upper, boolean acyclic) {
        EClass eClass0 = this.universalTypes.inverse().get(holder0);
        if (eClass0 == null) {
            throw new MetamodelException(MetamodelException.GENERAL_ERROR,
                    "Cannot add canonic relationship to non-canonic universal type.");
        }
        EClass eClass1 = this.universalTypes.inverse().get(holder1);
        if (eClass1 == null) {
            throw new MetamodelException(MetamodelException.GENERAL_ERROR,
                    "Cannot add canonic relationship to non-canonic universal type.");
        }
        if (isRelationshipType(eClass0) && isRelationshipType(eClass1)) {
            throw new MetamodelException(MetamodelException.GENERAL_ERROR,
                    "Cannot add relationship between two relationship types.");
        }
        if (eClass0.getEStructuralFeature(end0Name) != null) {
            throw new MetamodelException(MetamodelException.GENERAL_ERROR,
                    "Cannot add relationship end with duplicate persistent name " + end0Name + ".");
        }
        if (eClass1.getEStructuralFeature(end1Name) != null) {
            throw new MetamodelException(MetamodelException.GENERAL_ERROR,
                    "Cannot add relationship end with duplicate persistent name " + end1Name + ".");
        }
        EReference eReference0 = EcoreFactory.eINSTANCE.createEReference();
        eReference0.setName(end0Name);
        eReference0.setLowerBound(end0lower);
        eReference0.setUpperBound(end0upper);
        eReference0.setEType(eClass1);
        eClass0.getEStructuralFeatures().add(eReference0);
        EReference eReference1 = EcoreFactory.eINSTANCE.createEReference();
        eReference1.setName(end1Name);
        eReference1.setLowerBound(end1lower);
        eReference1.setUpperBound(end1upper);
        eReference1.setEType(eClass0);
        eClass1.getEStructuralFeatures().add(eReference1);
        eReference0.setEOpposite(eReference1);
        //note: the emf opposite is not reflexive and has thus to be explicitly set in both directions
        eReference1.setEOpposite(eReference0);
        EMFRelationshipEnd relationshipEnd0 = new EMFRelationshipEnd(eReference0, this);
        BiMap<EClass, RelationshipEndExpression> value0 = HashBiMap.create();
        value0.put(eClass0, relationshipEnd0);
        this.relationshipEnds.put(eReference0, value0);
        EMFRelationshipEnd relationshipEnd1 = new EMFRelationshipEnd(eReference1, this);
        BiMap<EClass, RelationshipEndExpression> value1 = HashBiMap.create();
        value1.put(eClass1, relationshipEnd1);
        this.relationshipEnds.put(eReference1, value1);
        EMFRelationship result = new EMFRelationship(persistentName, eReference0, eReference1, this, acyclic);
        this.relationship2relationshipEnds.put(result, relationshipEnd0);
        this.relationship2relationshipEnds.put(result, relationshipEnd1);

        relationshipEnd0.addObserver(holder0);
        relationshipEnd0.setName(end0Name);

        relationshipEnd1.addObserver(holder1);
        relationshipEnd1.setName(end1Name);

        return result;
    }

    /**{@inheritDoc}**/
    public final RelationshipTypeExpression createRelationshipType(String persistentName) {
        if (this.wrapped.getEClassifier(persistentName) != null) {
            throw new MetamodelException(MetamodelException.GENERAL_ERROR,
                    "Cannot create relationship type with duplicate persistent name " + persistentName + ".");
        }
        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eClass.setName(persistentName);
        annotateRelationshipType(eClass);
        this.wrapped.getEClassifiers().add(eClass);
        EMFRelationshipType result = new EMFRelationshipType(eClass, this);
        this.universalTypes.put(eClass, result);

        EMFPrimitiveProperty idProp = createIDPropertyImpl(eClass);
        idProp.addObserver(result);
        idProp.setName(UniversalTypeExpression.ID_PROPERTY.getPersistentName());

        result.addObserver(this);
        result.setName(persistentName);

        return result;
    }

    /**{@inheritDoc}**/
    public final SubstantialTypeExpression createSubstantialType(String persistentName) {
        if (this.wrapped.getEClassifier(persistentName) != null) {
            throw new MetamodelException(MetamodelException.GENERAL_ERROR,
                    "Cannot create substantial type with duplicate persistent name " + persistentName + ".");
        }
        EClass eClass = EcoreFactory.eINSTANCE.createEClass();
        eClass.setName(persistentName);
        annotateSubstantialType(eClass);
        this.wrapped.getEClassifiers().add(eClass);
        EMFSubstantialType result = new EMFSubstantialType(eClass, this);
        this.universalTypes.put(eClass, result);

        EMFPrimitiveProperty idProp = createIDPropertyImpl(eClass);
        idProp.addObserver(result);
        idProp.setName(UniversalTypeExpression.ID_PROPERTY.getPersistentName());

        addMixin(result, MixinTypeNamed.INSTANCE);
        result.addObserver(this);
        return result;
    }

    /**{@inheritDoc}**/
    public final void deleteEnumeration(EnumerationExpression enumeration) {
        EEnum eEnum = this.enumerations.inverse().remove(enumeration);
        if (eEnum == null) {
            throw new MetamodelException(MetamodelException.GENERAL_ERROR,
                    "Cannot delete non-canonic enumeration.");
        }
        Set<EAttribute> eAtts2Delete = Sets.newHashSet();
        Set<EnumerationPropertyExpression> propertiesToDelete = Sets.newHashSet();
        for (EnumerationPropertyExpression property : this.enumeration2properties.removeAll(enumeration)) {
            eAtts2Delete.addAll(findAllConnectedEAttributes(property));
            propertiesToDelete.add(property);
        }
        for (EnumerationPropertyExpression property : propertiesToDelete) {
            ((EMFUniversalType) property.getHolder()).removeFeature(property);
        }
        for (EAttribute eAtt : eAtts2Delete) {
            eAtt.getEContainingClass().getEStructuralFeatures().remove(eAtt);
            this.properties.remove(eAtt);
        }
        for (EEnumLiteral eEnumLiteral : eEnum.getELiterals()) {
            this.literals.inverse().remove(eEnumLiteral);
        }
        this.wrapped.getEClassifiers().remove(eEnum);
    }

    /**{@inheritDoc}**/
    public final void deleteEnumerationLiteral(EnumerationLiteralExpression literal) {
        List<EEnumLiteral> eEnumLiterals2Delete = Lists.newArrayList();
        for (Entry<EEnumLiteral, BiMap<EEnum, EnumerationLiteralExpression>> entry : this.literals.entrySet()) {
            Entry<EEnum, EnumerationLiteralExpression> eEnum2Literal = entry.getValue().entrySet().iterator()
                    .next();
            if (literal.equals(eEnum2Literal.getValue())) {
                eEnumLiterals2Delete.add(entry.getKey());
            }
        }
        if (eEnumLiterals2Delete.isEmpty()) {
            return;
        }
        for (EEnumLiteral eEnumLiteral : eEnumLiterals2Delete) {
            eEnumLiteral.getEEnum().getELiterals().remove(eEnumLiteral);
        }
        if (literal instanceof EMFEnumeration) {
            ((EMFEnumeration) literal).removeLiteral(literal);
        }
    }

    /**{@inheritDoc}**/
    public final void deleteProperty(PropertyExpression<?> property) {
        canDeleteFeature(property);
        Set<EAttribute> eAtts2Delete = findAllConnectedEAttributes(property);
        if (eAtts2Delete.isEmpty()) {
            throw new MetamodelException(MetamodelException.GENERAL_ERROR, "Cannot delete non-canonic property.");
        }
        ((EMFUniversalType) property.getHolder()).removeFeature(property);
        if (property instanceof EnumerationPropertyExpression) {
            EnumerationExpression enumeration = this.enumerations.get(eAtts2Delete.iterator().next().getEType());
            this.enumeration2properties.remove(enumeration, property);
        } else if (property instanceof PrimitivePropertyExpression) {
            PrimitiveTypeExpression primitiveType = this.primitiveTypes
                    .get(eAtts2Delete.iterator().next().getEType());
            this.primitiveType2properties.remove(primitiveType, property);
        }
        for (EAttribute toDelete : eAtts2Delete) {
            this.properties.remove(toDelete);
            toDelete.getEContainingClass().getEStructuralFeatures().remove(toDelete);
        }
    }

    /**{@inheritDoc}**/
    public final void deleteRelationship(RelationshipExpression relationship) {
        for (RelationshipEndExpression relEnd : relationship.getRelationshipEnds()) {
            canDeleteFeature(relEnd);
        }
        if (!this.relationship2relationshipEnds.containsKey(relationship)) {
            throw new MetamodelException(MetamodelException.GENERAL_ERROR,
                    "Cannot delete non-canonic relationship.");
        }
        for (RelationshipEndExpression relationshipEnd : this.relationship2relationshipEnds
                .removeAll(relationship)) {
            if (EMFRelationshipEnd.class.isInstance(relationshipEnd)) {
                ((EMFUniversalType) relationshipEnd.getHolder()).removeFeature(relationshipEnd);
            }
            Set<EReference> eRefs2Delete = findAllConnectedEReferences(relationshipEnd);
            for (EReference eReference : eRefs2Delete) {
                eReference.getEContainingClass().getEStructuralFeatures().remove(eReference);
                this.relationshipEnds.remove(eReference);
            }
            if (EMFRelationshipEnd.class.isInstance(relationshipEnd)) {
                ((EMFRelationshipEnd) relationshipEnd).deleteObserver(relationshipEnd.getType());
            }
        }
    }

    /**{@inheritDoc}**/
    public final void deleteRelationshipType(RelationshipTypeExpression relationshipType) {
        for (RelationshipEndExpression relEnd : relationshipType.getRelationshipEnds()) {
            deleteRelationship(relEnd.getRelationship());
        }
        EClass toDelete = this.universalTypes.inverse().remove(relationshipType);
        if (toDelete == null) {
            throw new MetamodelException(MetamodelException.GENERAL_ERROR,
                    "Cannot delete non-canonic relationship type.");
        }
        for (EAttribute att2delete : toDelete.getEAttributes()) {
            this.properties.inverse().remove(att2delete);
        }
        for (EReference ref2delete : toDelete.getEReferences()) {
            this.relationshipEnds.inverse().remove(ref2delete);
            EReference oppositeRef2delete = ref2delete.getEOpposite();
            if (oppositeRef2delete != null) {
                oppositeRef2delete.getEContainingClass().getEStructuralFeatures().remove(oppositeRef2delete);
                this.relationshipEnds.inverse().remove(oppositeRef2delete);
            }
        }
        this.wrapped.getEClassifiers().remove(toDelete);
        this.types.remove(relationshipType);
    }

    /**{@inheritDoc}**/
    public final void deleteSubstantialType(SubstantialTypeExpression substantialType) {
        EClass toDelete = this.universalTypes.inverse().remove(substantialType);
        if (toDelete == null) {
            throw new MetamodelException(MetamodelException.GENERAL_ERROR,
                    "Cannot delete non-canonic substantial type.");
        }
        for (EAttribute att2delete : toDelete.getEAttributes()) {
            this.properties.inverse().remove(att2delete);
        }
        for (EReference ref2delete : toDelete.getEReferences()) {
            this.relationshipEnds.inverse().remove(ref2delete);
            EReference oppositeRef2delete = ref2delete.getEOpposite();
            if (oppositeRef2delete != null) {
                oppositeRef2delete.getEContainingClass().getEStructuralFeatures().remove(oppositeRef2delete);
                this.relationshipEnds.inverse().remove(oppositeRef2delete);
            }
        }
        this.wrapped.getEClassifiers().remove(toDelete);
        this.types.remove(substantialType);
    }

    /**
     * Searches for the {@link PropertyExpression} that is holding (wrapping) the {@link EAttribute};
     * if not present, a new {@link PropertyExpression} is being instantiated before returning it
     * @param eAttribute
     *    the {@link EAttribute} to search for
     * @return
     *    the {@link PropertyExpression} wrapping the {@link EAttribute}
     */
    public final PropertyExpression<?> encapsulate(EAttribute eAttribute) {
        if (!this.properties.containsKey(eAttribute)) {
            BiMap<EClass, PropertyExpression<?>> value = HashBiMap.create();
            if (eAttribute.getEType() instanceof EEnum) {
                value.put(eAttribute.getEContainingClass(), new EMFEnumerationProperty(eAttribute, this));
            } else {
                value.put(eAttribute.getEContainingClass(), new EMFPrimitiveProperty(eAttribute, this));
            }
            this.properties.put(eAttribute, value);
        }
        return filterFeature(this.properties.get(eAttribute).get(eAttribute.getEContainingClass()));
    }

    /**
     * Searches for the {@link UniversalTypeExpression} that is holding (wrapping) the {@link EClass};
     * if not present, a new {@link UniversalTypeExpression} is being instantiated before returning it
     * @param eClass
     *    the {@link EClass} to search for
     * @return
     *    the {@link UniversalTypeExpression} wrapping the {@link EClass}
     */
    public final UniversalTypeExpression encapsulate(EClass eClass) {
        if (!this.universalTypes.containsKey(eClass)) {
            if (isSubstantialType(eClass)) {
                this.universalTypes.forcePut(eClass, new EMFSubstantialType(eClass, this));
            }
            if (isRelationshipType(eClass)) {
                this.universalTypes.put(eClass, new EMFRelationshipType(eClass, this));
            }
        }
        return this.universalTypes.get(eClass);
    }

    /**
     * Searches for the {@link EnumerationExpression} that is holding (wrapping) the {@link EEnum};
     * if not present, a new {@link EnumerationExpression} is being instantiated before returning it
     * @param eEnum
     *    the {@link EEnum} to search for
     * @return
     *    the {@link EnumerationExpression} wrapping the {@link EEnum}
     */
    public final EnumerationExpression encapsulate(EEnum eEnum) {
        if (!this.enumerations.containsKey(eEnum)) {
            this.enumerations.forcePut(eEnum, new EMFEnumeration(eEnum, this));
        }
        return this.enumerations.get(eEnum);
    }

    /**
     * Searches for the {@link EnumerationLiteralExpression} that is holding (wrapping) the {@link EEnumLiteral};
     * if not present, a new {@link EnumerationLiteralExpression} is being instantiated before returning it
     * @param eEnumLiteral
     *    the {@link EEnumLiteral} to search for
     * @return
     *    the {@link EnumerationLiteralExpression} wrapping the {@link EEnumLiteral}
     */
    public final EnumerationLiteralExpression encapsulate(EEnumLiteral eEnumLiteral) {
        if (!this.literals.containsKey(eEnumLiteral)) {
            BiMap<EEnum, EnumerationLiteralExpression> value = HashBiMap.create();
            //TODO is fine? do we need to keep some extra information, or store the color data in the eEnumLiteral, e.g. as an annotation?
            value.put(eEnumLiteral.getEEnum(), new EMFEnumerationLiteral(eEnumLiteral, this, null));
            this.literals.put(eEnumLiteral, value);
        }
        return this.literals.get(eEnumLiteral).get(eEnumLiteral.getEEnum());
    }

    /**
     * Searches for the {@link RelationshipEndExpression} that is holding (wrapping) the {@link EReference};
     * if not present, a new {@link RelationshipEndExpression} is being instantiated before returning it
     * @param eReference
     *    the {@link EReference} to search for
     * @return
     *    the {@link RelationshipEndExpression} wrapping the {@link EReference}
     */
    public final RelationshipEndExpression encapsulate(EReference eReference) {
        if (!this.relationshipEnds.containsKey(eReference)) {
            BiMap<EClass, RelationshipEndExpression> value = HashBiMap.create();
            value.put(eReference.getEContainingClass(), new EMFRelationshipEnd(eReference, this));
            this.relationshipEnds.put(eReference, value);
        }
        return filterFeature(this.relationshipEnds.get(eReference).get(eReference.getEContainingClass()));
    }

    /**
     * Searches for the {@link FeatureExpression} that is holding (wrapping) the {@link EStructuralFeature};
     * if not present, a new {@link FeatureExpression} is being instantiated before returning it
     * @param eFeature
     *    the {@link EStructuralFeature} to search for
     * @return
     *    the {@link FeatureExpression} wrapping the {@link EStructuralFeature}
     */
    public final FeatureExpression<?> encapsulate(EStructuralFeature eFeature) {
        if (EReference.class.isInstance(eFeature)) {
            return encapsulate((EReference) eFeature);
        } else if (EAttribute.class.isInstance(eFeature)) {
            return encapsulate((EAttribute) eFeature);
        } else {
            return null;
        }
    }

    @Override
    public final boolean equals(Object obj) {
        if (obj == null || obj.getClass() != this.getClass()) {
            return false;
        } else {
            EMFMetamodel otherMetamodel = (EMFMetamodel) obj;
            return wrapped.getName().equals(otherMetamodel.wrapped.getName())
                    && wrapped.getNsPrefix().equals(otherMetamodel.wrapped.getNsPrefix())
                    && wrapped.getNsURI().equals(otherMetamodel.wrapped.getNsURI());
        }
    }

    /**{@inheritDoc}**/
    public DataTypeExpression findDataTypeByName(String name) {
        TypeExpression result = findTypeByName(name);
        if (DataTypeExpression.class.isInstance(result)) {
            return (DataTypeExpression) result;
        }
        return null;
    }

    /**{@inheritDoc}**/
    public DataTypeExpression findDataTypeByPersistentName(String persistentName) {
        TypeExpression dataType = findTypeByPersistentName(persistentName);
        if (DataTypeExpression.class.isInstance(dataType)) {
            return (DataTypeExpression) dataType;
        }
        return null;
    }

    /**{@inheritDoc}**/
    public final TypeExpression findTypeByName(ElasticeamContext ctx, String name) {
        return findTypeByName(name);
    }

    /**{@inheritDoc}**/
    public final TypeExpression findTypeByName(String name) {
        return filterType(this.types.get(ElasticeamContextUtil.getCurrentContext().getLocale(), name));
    }

    /**{@inheritDoc}**/
    public final TypeExpression findTypeByPersistentName(String persistentName) {
        TypeExpression result = null;
        EClassifier eClassifier = this.wrapped.getEClassifier(persistentName);
        if (eClassifier instanceof EEnum) {
            result = encapsulate((EEnum) eClassifier);
        } else if (eClassifier instanceof EDataType) {
            //Note: This statement must be after the EEnum check, since the EEnum is a subclass of EDataType
            result = encapsulate((EDataType) eClassifier);
        } else if (eClassifier instanceof EClass) {
            result = encapsulate((EClass) eClassifier);
        }
        return filterType(result);
    }

    /**{@inheritDoc}**/
    public UniversalTypeExpression findUniversalTypeByName(String name) {
        TypeExpression result = findTypeByName(name);
        if (result instanceof UniversalTypeExpression) {
            return (UniversalTypeExpression) result;
        } else {
            return null;
        }
    }

    /**{@inheritDoc}**/
    public UniversalTypeExpression findUniversalTypeByPersistentName(String persistentName) {
        TypeExpression candidateType = findTypeByPersistentName(persistentName);
        if (candidateType instanceof UniversalTypeExpression) {
            return (UniversalTypeExpression) candidateType;
        } else {
            return null;
        }
    }

    public final List<DataTypeExpression> getDataTypes() {
        List<DataTypeExpression> result = Lists.newArrayList();
        result.addAll(getPrimitiveTypes());
        result.addAll(getEnumerationTypes());
        return result;
    }

    public final List<DataTypeExpression> getDataTypes(ElasticeamContext ctx) {
        return getDataTypes();
    }

    /**{@inheritDoc}**/
    public final List<EnumerationExpression> getEnumerationTypes() {
        List<EnumerationExpression> result = Lists.newArrayList();
        for (EClassifier eClassifier : getEPackage().getEClassifiers()) {
            if (eClassifier instanceof EDataType && isEnumerationType((EDataType) eClassifier)) {
                result.add(encapsulate((EEnum) eClassifier));
            }
        }
        return filterTypes(result);
    }

    /**{@inheritDoc}**/
    public final List<EnumerationExpression> getEnumerationTypes(ElasticeamContext ctx) {
        return getEnumerationTypes();
    }

    public EPackage getEPackage() {
        return this.wrapped;
    }

    /**{@inheritDoc}**/
    public final String getName() {
        return this.wrapped.getName();
    }

    public final List<PrimitiveTypeExpression> getPrimitiveTypes() {
        List<PrimitiveTypeExpression> result = Lists.newArrayList();
        for (EClassifier eClassifier : getEPackage().getEClassifiers()) {
            if (eClassifier instanceof EDataType && isPrimitiveType((EDataType) eClassifier)) {
                result.add(encapsulate((EDataType) eClassifier));
            }
        }
        return filterTypes(result);
    }

    public final List<PrimitiveTypeExpression> getPrimitiveTypes(ElasticeamContext ctx) {
        return getPrimitiveTypes();
    }

    /**{@inheritDoc}**/
    public List<RelationshipExpression> getRelationships() {
        return filterRelationships(Lists.newLinkedList(this.relationship2relationshipEnds.keySet()));
    }

    /**{@inheritDoc}**/
    public List<RelationshipExpression> getRelationships(ElasticeamContext ctx) {
        return getRelationships();
    }

    /**{@inheritDoc}**/
    public final List<RelationshipTypeExpression> getRelationshipTypes() {
        List<RelationshipTypeExpression> result = Lists.newArrayList();
        for (EClassifier eClassifier : getEPackage().getEClassifiers()) {
            if (eClassifier instanceof EClass && isRelationshipType((EClass) eClassifier)) {
                result.add((RelationshipTypeExpression) encapsulate((EClass) eClassifier));
            }
        }
        return filterTypes(result);
    }

    /**{@inheritDoc}**/
    public final List<RelationshipTypeExpression> getRelationshipTypes(ElasticeamContext ctx) {
        return getRelationshipTypes();
    }

    /**{@inheritDoc}**/
    public final List<SubstantialTypeExpression> getSubstantialTypes() {
        List<SubstantialTypeExpression> result = Lists.newArrayList();
        for (EClassifier eClassifier : getEPackage().getEClassifiers()) {
            if (eClassifier instanceof EClass && isSubstantialType((EClass) eClassifier)) {
                result.add((SubstantialTypeExpression) encapsulate((EClass) eClassifier));
            }
        }
        return filterTypes(result);
    }

    /**{@inheritDoc}**/
    public final List<SubstantialTypeExpression> getSubstantialTypes(ElasticeamContext ctx) {
        return getSubstantialTypes();
    }

    /**{@inheritDoc}**/
    public final List<TypeExpression> getTypes() {
        List<TypeExpression> result = Lists.newArrayList();
        for (EClassifier eClassifier : getEPackage().getEClassifiers()) {
            if (eClassifier instanceof EClass) {
                result.add(encapsulate((EClass) eClassifier));
            } else if (eClassifier instanceof EEnum) {
                result.add(encapsulate((EEnum) eClassifier));
            } else if (eClassifier instanceof EDataType) {
                result.add(encapsulate((EDataType) eClassifier));
            }
        }
        return filterTypes(result);
    }

    /**{@inheritDoc}**/
    public final List<TypeExpression> getTypes(ElasticeamContext ctx) {
        return getTypes();
    }

    public final List<UniversalTypeExpression> getUniversalTypes() {
        List<UniversalTypeExpression> result = Lists.newArrayList();
        result.addAll(getSubstantialTypes());
        result.addAll(getRelationshipTypes());
        return result;
    }

    public final List<UniversalTypeExpression> getUniversalTypes(ElasticeamContext ctx) {
        return getUniversalTypes();
    }

    /**{@inheritDoc}**/
    public final void grantPermission(PropertyExpression<?> on, Role to, FeaturePermissions type) {
        if (type == FeaturePermissions.READ) {
            this.readableFeatures.put(to, on);
        }
    }

    /**{@inheritDoc}**/
    public final void grantPermission(UniversalTypeExpression on, Role to, UniversalTypePermissions type) {
        if (type == UniversalTypePermissions.READ) {
            this.readableTypes.put(to, on);
        }
    }

    @Override
    public final int hashCode() {
        return (getClass().hashCode() + wrapped.getName() + wrapped.getNsURI() + wrapped.getNsPrefix()).hashCode();
    }

    public final PrimitiveTypeExpression initPrimitiveType(BuiltinPrimitiveType primitiveType) {
        if (primitiveType == null) {
            throw new MetamodelException(MetamodelException.GENERAL_ERROR,
                    "A primitive type must have its encapsulated class specified.");
        }
        for (EDataType existing : primitiveTypes.keySet()) {
            if (primitiveType.getEncapsulatedType().equals(existing.getInstanceClass())) {
                throw new MetamodelException(MetamodelException.GENERAL_ERROR,
                        "Cannot create a primitive type for class " + primitiveType.getEncapsulatedType().getName()
                                + ". A primitive type for this class is already defied.");
            }
        }

        EDataType dataType = EcoreFactory.eINSTANCE.createEDataType();
        dataType.setName(primitiveType.getPersistentName());
        dataType.setInstanceClass(primitiveType.getEncapsulatedType());

        annotatePrimitiveType(dataType);

        this.wrapped.getEClassifiers().add(dataType);
        this.primitiveTypes.put(dataType, primitiveType);

        return primitiveType;
    }

    /**{@inheritDoc}**/
    public void removeMixin(SubstantialTypeExpression substantialType, MixinTypeExpression mixin) {
        // TODO Auto-generated method stub

    }

    /**
     * Get the {@link EAttribute} that has been instantiated to represent the {@link PropertyExpression}
     * 
     * @param property
     *    the {@link PropertyExpression} to search for
     * @param eClass
     *    the {@link EClass} representing the porperty's holder ({@link UniversalTypeExpression})
     * @return
     *    the {@link EAttribute} representing the {@link PropertyExpression}
     */
    public final EAttribute unwrap(PropertyExpression<?> property, EClass eClass) {
        EAttribute result = null;
        for (Entry<EAttribute, BiMap<EClass, PropertyExpression<?>>> entrySet : this.properties.entrySet()) {
            Entry<EClass, PropertyExpression<?>> entry = entrySet.getValue().entrySet().iterator().next();
            if (eClass.getName().equals(entry.getKey().getName()) && property.equals(entry.getValue())) {
                result = entrySet.getKey();
                break;
            }
        }
        if (result == null) {
            throw new MetamodelException(MetamodelException.GENERAL_ERROR,
                    "Could not find mapped EAttribute for PropertyExpression '" + property.getPersistentName()
                            + "' in EClass '" + eClass.getName() + "'");
        }
        return result;
    }

    /**
     * Get the {@link EReference} that has been instantiated to represent the {@link RelationshipEndExpression}
     * 
     * @param relationshipEnd
     *    the {@link RelationshipEndExpression} to search for
     * @param eClass
     *    the {@link EClass} representing the relationshipEnd's holder ({@link UniversalTypeExpression})
     * @return
     *    the {@link EReference} representing the {@link RelationshipEndExpression}
     */
    public final EReference unwrap(RelationshipEndExpression relationshipEnd, EClass eClass) {
        EReference result = null;
        for (Entry<EReference, BiMap<EClass, RelationshipEndExpression>> entrySet : this.relationshipEnds
                .entrySet()) {
            Entry<EClass, RelationshipEndExpression> entry = entrySet.getValue().entrySet().iterator().next();
            if (eClass.getName().equals(entry.getKey().getName()) && relationshipEnd.equals(entry.getValue())) {
                result = entrySet.getKey();
                break;
            }
        }
        if (result == null) {
            throw new MetamodelException(MetamodelException.GENERAL_ERROR,
                    "Could not find mapped EReference for RelationshipEndExpression '"
                            + relationshipEnd.getPersistentName() + "' in EClass '" + eClass.getName() + "'");
        }
        return result;
    }

    /**{@inheritDoc}**/
    public final void update(Observable arg0, Object arg1) {
        if (arg0 instanceof TypeExpression && arg1 instanceof NameChangeEvent) {
            this.types.set(((NameChangeEvent) arg1).getLocale(), ((NameChangeEvent) arg1).getName(),
                    (TypeExpression) arg0);
        }
    }

    private void addDefaultPrimitiveTypes() {
        for (PrimitiveTypeExpression builtinType : BuiltinPrimitiveType.BUILTIN_PRIMITIVE_TYPES) {
            initPrimitiveType((BuiltinPrimitiveType) builtinType);
        }
    }

    private void canDeleteFeature(FeatureExpression<?> feature) {
        if (!(feature.getHolder() != null && feature.getHolder() == feature.getOrigin())) {
            throw new MetamodelException(MetamodelException.GENERAL_ERROR,
                    "Can not delete builtin feature " + feature);
        }
    }

    private EMFPrimitiveProperty createIDPropertyImpl(EClass eClass) {
        EMFPrimitiveProperty idPoperty = createPropertyImpl(eClass, "id", 1, 1,
                getEDataType(BuiltinPrimitiveType.INTEGER.getEncapsulatedType()),
                new MixinTypeExpression[] { null });
        idPoperty.getWrapped().setID(true);
        return idPoperty;
    }

    private EMFPrimitiveProperty createPropertyImpl(EClass eClass, String persistentName, int lowerBound,
            int upperBound, EDataType eDataType, MixinTypeExpression... mixin) {
        EAttribute eAttribute = EcoreFactory.eINSTANCE.createEAttribute();
        eAttribute.setName(persistentName);
        eAttribute.setLowerBound(lowerBound);
        eAttribute.setUpperBound(upperBound);
        eAttribute.setEType(eDataType);
        eClass.getEStructuralFeatures().add(eAttribute);
        EMFPrimitiveProperty result = (mixin == null || mixin.length == 0)
                ? new EMFPrimitiveProperty(eAttribute, this)
                : new EMFPrimitiveProperty(eAttribute, mixin[0], this);
        BiMap<EClass, PropertyExpression<?>> value = HashBiMap.create();
        value.put(eClass, result);
        this.properties.put(eAttribute, value);
        this.primitiveType2properties.put(this.primitiveTypes.get(eDataType), result);
        return result;
    }

    private EMFEnumerationProperty createPropertyImpl(EClass eClass, String persistentName, int lowerBound,
            int upperBound, EEnum eEnum, MixinTypeExpression... mixin) {
        EAttribute eAttribute = EcoreFactory.eINSTANCE.createEAttribute();
        eAttribute.setName(persistentName);
        eAttribute.setLowerBound(lowerBound);
        eAttribute.setUpperBound(
                upperBound == FeatureExpression.UNLIMITED ? EStructuralFeature.UNBOUNDED_MULTIPLICITY : upperBound);
        eAttribute.setEType(eEnum);
        eClass.getEStructuralFeatures().add(eAttribute);
        EMFEnumerationProperty result = (mixin == null || mixin.length == 0)
                ? new EMFEnumerationProperty(eAttribute, this)
                : new EMFEnumerationProperty(eAttribute, mixin[0], this);
        BiMap<EClass, PropertyExpression<?>> value = HashBiMap.create();
        value.put(eClass, result);
        this.properties.put(eAttribute, value);
        this.enumeration2properties.put(this.enumerations.get(eEnum), result);

        return result;
    }

    private <T extends TypeExpression> T filterType(T type) {
        if (disableAccessControl) {
            return type;
        }
        if (ElasticeamContextUtil.getCurrentContext().isSupervisor()) {
            return type;
        }
        if (type instanceof DataTypeExpression) {
            return type;
        }

        for (Role role : ElasticeamContextUtil.getCurrentContext().getRoles()) {
            if (this.readableTypes.get(role).contains(type)) {
                if (type instanceof EMFRelationshipType) {
                    EMFRelationshipType rte = (EMFRelationshipType) type;
                    for (EReference eRef : rte.getWrapped().getEAllReferences()) {
                        if (eRef.getLowerBound() > 0
                                && rte.findRelationshipEndByPersistentName(eRef.getName()) == null) {
                            return null;
                        }
                    }
                }
                return type;
            }
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    private <T extends TypeExpression> List<T> filterTypes(List<T> candidateTypes) {
        if (disableAccessControl) {
            return candidateTypes;
        }
        if (ElasticeamContextUtil.getCurrentContext().isSupervisor()) {
            return candidateTypes;
        }
        Set<T> result = Sets.newLinkedHashSet();
        for (Role role : ElasticeamContextUtil.getCurrentContext().getRoles()) {
            if (this.readableTypes.containsKey(role)) {
                result.addAll(ListUtils.intersection(this.readableTypes.get(role), candidateTypes));
            }
        }
        for (T type : candidateTypes) {
            if (type instanceof DataTypeExpression) {
                result.add(type);
            }
        }
        Set<RelationshipTypeExpression> invisibleRTEs = Sets.newHashSet();
        for (T type : result) {
            if (type instanceof EMFRelationshipType) {
                EMFRelationshipType rte = (EMFRelationshipType) type;
                for (EReference eRef : rte.getWrapped().getEAllReferences()) {
                    if (eRef.getLowerBound() > 0
                            && rte.findRelationshipEndByPersistentName(eRef.getName()) == null) {
                        // required relEnd is invisible for current user => rte is invisible, too
                        invisibleRTEs.add(rte);
                    }
                }
            }
        }
        result.removeAll(invisibleRTEs);

        return Lists.newArrayList(result);
    }

    /**
     * returns a {@link Set} of {@link EAttribute}s that are wrapped by {@link PropertyExpression}s 
     * that are equal to the provided {@link PropertyExpression}
     */
    private Set<EAttribute> findAllConnectedEAttributes(PropertyExpression<?> property) {
        Set<EAttribute> eAttributes = Sets.newHashSet();
        for (Entry<EAttribute, BiMap<EClass, PropertyExpression<?>>> entrySet : this.properties.entrySet()) {
            Entry<EClass, PropertyExpression<?>> entry = entrySet.getValue().entrySet().iterator().next();
            if (property.equals(entry.getValue())) {
                eAttributes.add(entrySet.getKey());
            }
        }
        return eAttributes;
    }

    /**
     * returns a {@link Set} of {@link EReference}s that are wrapped by {@link RelationshipEndExpression}s 
     * that are equal to the provided {@link RelationshipEndExpression}
     */
    private Set<EReference> findAllConnectedEReferences(RelationshipEndExpression relationshipEnd) {
        Set<EReference> eReferences = Sets.newHashSet();
        for (Entry<EReference, BiMap<EClass, RelationshipEndExpression>> entrySet : this.relationshipEnds
                .entrySet()) {
            Entry<EClass, RelationshipEndExpression> entry = entrySet.getValue().entrySet().iterator().next();
            if (relationshipEnd.equals(entry.getValue())) {
                eReferences.add(entrySet.getKey());
            }
        }
        return eReferences;
    }

    private EDataType getEDataType(Class<?> type) {
        EDataType eDataType = null;
        for (EClassifier eClassifier : EcorePackage.eINSTANCE.getEClassifiers()) {
            if (eClassifier instanceof EDataType && type.equals(((EDataType) eClassifier).getInstanceClass())) {
                eDataType = (EDataType) eClassifier;
            }
        }
        for (EClassifier eClassifier : this.wrapped.getEClassifiers()) {
            if (eClassifier instanceof EDataType && type.equals(((EDataType) eClassifier).getInstanceClass())) {
                eDataType = (EDataType) eClassifier;
            }
        }
        return eDataType;
    }

    /**
     * Searches for the {@link PrimitiveTypeExpression} that is holding (wrapping) the {@link EDataType};
     * if not present, a new {@link PrimitiveTypeExpression} is being instantiated before returning it
     * @param eDataType
     *    the {@link EDataType} to search for
     * @return
     *    the {@link PrimitiveTypeExpression} wrapping the {@link EDataType}
     */
    final PrimitiveTypeExpression encapsulate(EDataType eDataType) {
        if (!this.primitiveTypes.containsKey(eDataType)) {
            throw new MetamodelException(MetamodelException.GENERAL_ERROR,
                    "Missing primitive type for eDataType " + eDataType);
        }
        return this.primitiveTypes.get(eDataType);
    }

    /**
     * Get the {@link RelationshipExpression} that is connected to the {@link RelationshipEndExpression}
     * 
     * @param relationshipEnd
     *    the {@link RelationshipEndExpression} to check
     * @return
     *    the {@link RelationshipExpression} that is connected to the {@link RelationshipEndExpression}
     */
    final RelationshipExpression getRelationship(RelationshipEndExpression relationshipEnd) {
        for (Entry<RelationshipExpression, RelationshipEndExpression> entry : relationship2relationshipEnds
                .entries()) {
            if (entry.getValue().equals(relationshipEnd)) {
                return entry.getKey();
            }
        }
        return null;
    }

    final List<RelationshipExpression> filterRelationships(List<RelationshipExpression> relationships) {
        if (disableAccessControl) {
            return relationships;
        }
        if (ElasticeamContextUtil.getCurrentContext().isSupervisor()) {
            return relationships;
        }
        List<RelationshipExpression> result = Lists.newArrayList();
        for (RelationshipExpression rel : relationships) {
            if (filterRelationship(rel) != null) {
                result.add(rel);
            }
        }
        return result;
    }

    final RelationshipExpression filterRelationship(RelationshipExpression rel) {
        List<RelationshipEndExpression> relEnds = rel.getRelationshipEnds();
        if (relEnds.size() == filterFeatures(relEnds).size()) {
            return rel;
        }
        return null;
    }

    final <F extends FeatureExpression<?>> List<F> filterFeatures(List<F> features) {
        if (disableAccessControl) {
            return features;
        }
        if (ElasticeamContextUtil.getCurrentContext().isSupervisor()) {
            return features;
        }
        Set<F> result = Sets.newHashSet();
        // FIXME change once we have feature-level permissions
        for (F feature : features) {
            if (feature instanceof PropertyExpression && filterType(feature.getHolder()) != null) {
                result.add(feature);
            }
        }
        //    for (Role role : ElasticeamContextUtil.getCurrentContext().getRoles()) {
        //      if (this.readableFeatures.containsKey(role)) {
        //        result.addAll(ListUtils.intersection(this.readableFeatures.get(role), features));
        //      }
        //    }
        for (F feature : features) {
            if (feature instanceof RelationshipEndExpression
                    && filterType(((RelationshipEndExpression) feature).getType()) != null) {
                result.add(feature);
            }
        }
        return Lists.newArrayList(result);
    }

    final <F extends FeatureExpression<?>> F filterFeature(F feature) {
        if (disableAccessControl) {
            return feature;
        }
        if (ElasticeamContextUtil.getCurrentContext().isSupervisor()) {
            return feature;
        }
        if (feature instanceof RelationshipEndExpression
                && filterType(((RelationshipEndExpression) feature).getType()) != null) {
            return feature;
        }
        // FIXME change once we have feature-level permissions
        if (feature instanceof PropertyExpression && filterType(feature.getHolder()) != null) {
            return feature;
        }
        //    for (Role role : ElasticeamContextUtil.getCurrentContext().getRoles()) {
        //      if (this.readableFeatures.get(role).contains(feature)) {
        //        return feature;
        //      }
        //    }
        return null;
    }

}