From 4143f52204aa8dc36844c1c831a15c186a15d0a1 Mon Sep 17 00:00:00 2001
From: Simon Barner <barner@fortiss.org>
Date: Tue, 17 Mar 2015 09:59:19 +0000
Subject: [PATCH] Problem description:

- By default, EMF uses fragment-path based links to persist EReferences. While this works fine for standalone model resources, it can cause inconsistencies in case there are multiple resource models that contain cross-resource references. (I.e., if the structure of the AF3 model resource changes, the links from the external model resource can become invalid)
- The recommended way to work around this is to configure EMF to use IDs to encode EReferences.

Solution:
- Change persistence of AF3 model resources to encode references using an (extrinsic) xmi:id, that is identical to the model element's org.fortiss.tooling.kernel.model.IIdLabled.id. A dedicated XMIResource type for AF3 model resources is used (which is created by a resource factory registered for *.af3_23 files)
- The reason for not using IIdLabled.id as intrinsic ID is that the implemented solution using xmi:id is both forward and backward compatible
- At the price of losing forward compatibility, the IIdLabled.id could be changed to a derived, volatile EAttribute that simply returns the value of xmi:id
- Add EContentAdapter to sync xmi:id and IIdLabled.id
- Ensure uniqueness of IDs (that is now much more crucial) during when loading and saving as well as importing and exporting models (and print a warning to the console in case duplicates have been detected and fixed)
- As an additional safety layer, add constraint checkers to ensure uniqueness of IDs and consistency of IIdLabled.id and xmi:id

Related changes:
- Speed up loading and saving of XMI resources by setting corresponding XMLResource options
- Ensure that IDs are assigned to annotations as soon as they are instantiated
refs 2309
---
 org.fortiss.tooling.kernel/trunk/plugin.xml   |  15 ++
 .../kernel/constraint/ConstraintMessage.java  |  75 +++++++++
 .../constraint/IdConsistencyChecker.java      |  71 ++++++++
 .../IdConsistencyConstraintViolation.java     |  41 +++++
 .../constraint/IdUniquenessChecker.java       | 100 ++++++++++++
 .../IdUniquenessConstraintViolation.java      |  41 +++++
 .../tooling/kernel/constraint/package.html    |   8 +
 .../extension/data/ITopLevelElement.java      |  10 +-
 .../kernel/internal/DummyTopLevelElement.java |   8 +-
 .../internal/ElementCompositorService.java    |  10 +-
 .../storage/eclipse/AF3ResourceFactory.java   | 151 ++++++++++++++++++
 .../storage/eclipse/ModelContext.java         |  84 +++++++++-
 .../kernel/utils/EMFResourceUtils.java        |   7 +-
 .../tooling/kernel/utils/UniqueIDUtils.java   |  33 +++-
 14 files changed, 643 insertions(+), 11 deletions(-)
 create mode 100644 org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/constraint/ConstraintMessage.java
 create mode 100644 org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/constraint/IdConsistencyChecker.java
 create mode 100644 org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/constraint/IdConsistencyConstraintViolation.java
 create mode 100644 org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/constraint/IdUniquenessChecker.java
 create mode 100644 org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/constraint/IdUniquenessConstraintViolation.java
 create mode 100644 org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/constraint/package.html
 create mode 100644 org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/internal/storage/eclipse/AF3ResourceFactory.java

diff --git a/org.fortiss.tooling.kernel/trunk/plugin.xml b/org.fortiss.tooling.kernel/trunk/plugin.xml
index 59be36613..b8e577120 100644
--- a/org.fortiss.tooling.kernel/trunk/plugin.xml
+++ b/org.fortiss.tooling.kernel/trunk/plugin.xml
@@ -30,4 +30,19 @@
             provider="org.fortiss.tooling.kernel.internal.LibraryPrototypeProvider">
       </modelPrototypeProvider>
    </extension>
+
+   <extension point="org.fortiss.tooling.kernel.modelElementConstraintChecker">
+      <modelElementConstraintChecker checker="org.fortiss.tooling.kernel.constraint.IdUniquenessChecker">
+         <modelElementClass modelElementClass="org.fortiss.tooling.kernel.model.IIdLabeled"/>
+      </modelElementConstraintChecker>
+      <modelElementConstraintChecker checker="org.fortiss.tooling.kernel.constraint.IdConsistencyChecker">
+         <modelElementClass modelElementClass="org.fortiss.tooling.kernel.model.IIdLabeled"/>
+      </modelElementConstraintChecker>
+   </extension>
+   
+     <!-- Resource factories. Keep registration in sync with AF3 model file extension. -->
+   <extension point = "org.eclipse.emf.ecore.extension_parser">
+      <parser type="af3_23" class="org.fortiss.tooling.kernel.internal.storage.eclipse.AF3ResourceFactory"/>
+   </extension>
+
 </plugin>
diff --git a/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/constraint/ConstraintMessage.java b/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/constraint/ConstraintMessage.java
new file mode 100644
index 000000000..a1df7bf55
--- /dev/null
+++ b/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/constraint/ConstraintMessage.java
@@ -0,0 +1,75 @@
+/*--------------------------------------------------------------------------+
+$Id$
+|                                                                          |
+| Copyright 2015 ForTISS GmbH                                              |
+|                                                                          |
+| Licensed under the Apache License, Version 2.0 (the "License");          |
+| you may not use this file except in compliance with the License.         |
+| You may obtain a copy of the License at                                  |
+|                                                                          |
+|    http://www.apache.org/licenses/LICENSE-2.0                            |
+|                                                                          |
+| Unless required by applicable law or agreed to in writing, software      |
+| distributed under the License is distributed on an "AS IS" BASIS,        |
+| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
+| See the License for the specific language governing permissions and      |
+| limitations under the License.                                           |
++--------------------------------------------------------------------------*/
+package org.fortiss.tooling.kernel.constraint;
+
+import org.eclipse.emf.ecore.EObject;
+import org.fortiss.tooling.kernel.model.IIdLabeled;
+import org.fortiss.tooling.kernel.model.INamedElement;
+
+/**
+ * Class for implementation of constraint violation message.
+ * 
+ * @author barner
+ * @author $Author$
+ * @version $Rev$
+ * @ConQAT.Rating YELLOW Hash: FA47E0B446DFB0E62523A73A2DB8A1E6
+ */
+public class ConstraintMessage {
+
+	/** Formats the name of {@code element} for the {@link IdUniquenessConstraintViolation} message. */
+	private static String formatElementName(EObject element) {
+		return formatElementName(element, true);
+	}
+
+	/** Helper method to implement {@link #formatElementName(EObject)}. */
+	private static String formatElementName(EObject element, boolean offspring) {
+		if(element instanceof INamedElement) {
+			String elementName = ((INamedElement)element).getName();
+			if(elementName != null && !elementName.isEmpty()) {
+				return "\"" + elementName + "\"";
+			}
+		}
+
+		if(element != null && element.eContainer() != null) {
+			String elementName = formatElementName(element.eContainer(), false);
+
+			return offspring ? "<unnamed offspring of> " + elementName : elementName;
+		}
+
+		return "<unnamed>";
+	}
+
+	/** Creates the violation for an {@link IIdLabeled} element that contains a duplicated ID. */
+	public static IdUniquenessConstraintViolation<IIdLabeled> createDuplicateIdViolation(
+			IIdLabeled element, IIdLabeled duplicate) {
+		return new IdUniquenessConstraintViolation<IIdLabeled>(element, "The ID " +
+				element.getId() + " of model element " + formatElementName(element) +
+				" has already been assigned to model element " + formatElementName(duplicate) + ".");
+	}
+
+	/**
+	 * Creates the violation for an {@link IIdLabeled} whose ID attribute is inconsistent with the
+	 * elements persisted id (e.g., {@code xmi:id}).
+	 */
+	public static IdConsistencyConstraintViolation<IIdLabeled> createConsistencyIdViolation(
+			IIdLabeled element, String persistedId) {
+		return new IdConsistencyConstraintViolation<IIdLabeled>(element, "The ID " +
+				element.getId() + " of model element " + formatElementName(element) +
+				" does not match the element's persisted ID " + persistedId + ".");
+	}
+}
diff --git a/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/constraint/IdConsistencyChecker.java b/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/constraint/IdConsistencyChecker.java
new file mode 100644
index 000000000..2b71f3dcd
--- /dev/null
+++ b/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/constraint/IdConsistencyChecker.java
@@ -0,0 +1,71 @@
+/*--------------------------------------------------------------------------+
+$Id$
+|                                                                          |
+| Copyright 2015 ForTISS GmbH                                              |
+|                                                                          |
+| Licensed under the Apache License, Version 2.0 (the "License");          |
+| you may not use this file except in compliance with the License.         |
+| You may obtain a copy of the License at                                  |
+|                                                                          |
+|    http://www.apache.org/licenses/LICENSE-2.0                            |
+|                                                                          |
+| Unless required by applicable law or agreed to in writing, software      |
+| distributed under the License is distributed on an "AS IS" BASIS,        |
+| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
+| See the License for the specific language governing permissions and      |
+| limitations under the License.                                           |
++--------------------------------------------------------------------------*/
+package org.fortiss.tooling.kernel.constraint;
+
+import static org.fortiss.tooling.kernel.constraint.ConstraintMessage.createConsistencyIdViolation;
+import static org.fortiss.tooling.kernel.utils.EcoreUtils.getChildrenWithType;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.xmi.XMLResource;
+import org.fortiss.tooling.kernel.extension.IConstraintChecker;
+import org.fortiss.tooling.kernel.extension.base.ConstraintCheckerBase;
+import org.fortiss.tooling.kernel.extension.data.ITopLevelElement;
+import org.fortiss.tooling.kernel.model.IIdLabeled;
+import org.fortiss.tooling.kernel.service.IPersistencyService;
+
+/**
+ * {@link IConstraintChecker} to check whether the IDs provided by {@link IIdLabeled} is identical
+ * to the XMI ID stored in the corresponding {@link XMLResource}.
+ * 
+ * While this consistency of the IDs should be ensured by of the kernel (see {@code ModelContext}),
+ * this constraint checker is an additional safety layer.
+ * 
+ * @author barner
+ * @author $Author$
+ * @version $Rev$
+ * @ConQAT.Rating YELLOW Hash: B88B5832B0BFA99359A3DA7F20BBBCBE
+ */
+public class IdConsistencyChecker extends ConstraintCheckerBase<IIdLabeled> {
+
+	/** {@inheritDoc} */
+	@Override
+	public List<IdConsistencyConstraintViolation<IIdLabeled>> apply(IIdLabeled element) {
+		ArrayList<IdConsistencyConstraintViolation<IIdLabeled>> result =
+				new ArrayList<IdConsistencyConstraintViolation<IIdLabeled>>();
+
+		// Get project root element
+		EObject root = element;
+		while(root.eContainer() != null) {
+			root = root.eContainer();
+		}
+
+		ITopLevelElement topLevelElement = IPersistencyService.INSTANCE.getTopLevelElementFor(root);
+		if(topLevelElement != null) {
+			for(IIdLabeled current : getChildrenWithType(root, IIdLabeled.class)) {
+				String persistedId = topLevelElement.getId(current);
+				if(!(new Integer(current.getId()).toString()).equals(persistedId)) {
+					result.add(createConsistencyIdViolation(current, persistedId));
+				}
+			}
+		}
+		return result;
+	}
+}
diff --git a/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/constraint/IdConsistencyConstraintViolation.java b/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/constraint/IdConsistencyConstraintViolation.java
new file mode 100644
index 000000000..9a2f06ffd
--- /dev/null
+++ b/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/constraint/IdConsistencyConstraintViolation.java
@@ -0,0 +1,41 @@
+/*--------------------------------------------------------------------------+
+$Id$
+|                                                                          |
+| Copyright 2015 ForTISS GmbH                                              |
+|                                                                          |
+| Licensed under the Apache License, Version 2.0 (the "License");          |
+| you may not use this file except in compliance with the License.         |
+| You may obtain a copy of the License at                                  |
+|                                                                          |
+|    http://www.apache.org/licenses/LICENSE-2.0                            |
+|                                                                          |
+| Unless required by applicable law or agreed to in writing, software      |
+| distributed under the License is distributed on an "AS IS" BASIS,        |
+| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
+| See the License for the specific language governing permissions and      |
+| limitations under the License.                                           |
++--------------------------------------------------------------------------*/
+package org.fortiss.tooling.kernel.constraint;
+
+import static org.fortiss.tooling.kernel.extension.data.IConstraintViolation.ESeverity.FATAL;
+
+import org.fortiss.tooling.kernel.extension.base.ConstraintViolationBase;
+import org.fortiss.tooling.kernel.extension.data.IConstraintViolation;
+import org.fortiss.tooling.kernel.model.IIdLabeled;
+
+/**
+ * {@link IConstraintViolation} for {@link IIdLabeled} elements.
+ * 
+ * @author barner
+ * @author $Author$
+ * @version $Rev$
+ * @ConQAT.Rating YELLOW Hash: 399E20804354E3EA3EC65237BE6DE821
+ */
+public class IdConsistencyConstraintViolation<T extends IIdLabeled> extends
+		ConstraintViolationBase<T> {
+
+	/** Constructor. */
+	public IdConsistencyConstraintViolation(T modelElement, String explanation) {
+		super(modelElement, FATAL, explanation);
+	}
+}
diff --git a/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/constraint/IdUniquenessChecker.java b/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/constraint/IdUniquenessChecker.java
new file mode 100644
index 000000000..a1a945574
--- /dev/null
+++ b/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/constraint/IdUniquenessChecker.java
@@ -0,0 +1,100 @@
+/*--------------------------------------------------------------------------+
+$Id$
+|                                                                          |
+| Copyright 2015 ForTISS GmbH                                              |
+|                                                                          |
+| Licensed under the Apache License, Version 2.0 (the "License");          |
+| you may not use this file except in compliance with the License.         |
+| You may obtain a copy of the License at                                  |
+|                                                                          |
+|    http://www.apache.org/licenses/LICENSE-2.0                            |
+|                                                                          |
+| Unless required by applicable law or agreed to in writing, software      |
+| distributed under the License is distributed on an "AS IS" BASIS,        |
+| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
+| See the License for the specific language governing permissions and      |
+| limitations under the License.                                           |
++--------------------------------------------------------------------------*/
+package org.fortiss.tooling.kernel.constraint;
+
+import static org.fortiss.tooling.kernel.constraint.ConstraintMessage.createDuplicateIdViolation;
+import static org.fortiss.tooling.kernel.utils.EcoreUtils.getChildrenWithType;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EReference;
+import org.fortiss.tooling.kernel.extension.IConstraintChecker;
+import org.fortiss.tooling.kernel.extension.base.ConstraintCheckerBase;
+import org.fortiss.tooling.kernel.model.IIdLabeled;
+
+/**
+ * {@link IConstraintChecker} to check whether all {@link IIdLabeled} elements actually have unique
+ * IDs.
+ * 
+ * While the uniqueness of IDs should be ensured by the assignment strategy of the kernel, and
+ * {@code ModelContext} eliminates duplicated IDs when loading and saving models, this constraint
+ * checker is an additional safety layer.
+ * 
+ * This is because in persisted model resources, the encoding of {@link EReference} is based on IDs.
+ * Hence, model resources with duplicated IDs in referenced model elements are invalid and cannot be
+ * loaded.
+ * 
+ * @author barner
+ * @author $Author$
+ * @version $Rev$
+ * @ConQAT.Rating YELLOW Hash: A57025B59A7DB40046D9DD46EDDD5315
+ */
+public class IdUniquenessChecker extends ConstraintCheckerBase<IIdLabeled> {
+
+	/** Map to track ID -> element relationship (within a single project). */
+	private class IdToElementMap extends HashMap<Integer, IIdLabeled> {
+		// Nothing to do
+	}
+
+	/** Map: project root element -> {@link IdToElementMap}. */
+	private Map<EObject, IdToElementMap> idToElementMaps = new HashMap<EObject, IdToElementMap>();
+
+	/** {@inheritDoc} */
+	@Override
+	public List<IdUniquenessConstraintViolation<IIdLabeled>> apply(IIdLabeled element) {
+		ArrayList<IdUniquenessConstraintViolation<IIdLabeled>> result =
+				new ArrayList<IdUniquenessConstraintViolation<IIdLabeled>>();
+
+		// Get project root element
+		EObject root = element;
+		while(root.eContainer() != null) {
+			root = root.eContainer();
+		}
+
+		IdToElementMap idToElementMap = idToElementMaps.get(root);
+
+		// If required, populate map to track ID -> Element relationship
+		if(idToElementMap == null) {
+			idToElementMap = new IdToElementMap();
+			idToElementMaps.put(root, idToElementMap);
+
+			for(IIdLabeled current : getChildrenWithType(root, IIdLabeled.class)) {
+				// In case there are duplicates, the first occurrence is considered to be legitimate
+				// and is hence kept in the map
+				if(!idToElementMap.containsKey(current.getId())) {
+					idToElementMap.put(current.getId(), current);
+				}
+			}
+		}
+
+		if(idToElementMap.containsKey(element.getId())) {
+			IIdLabeled knownElement = idToElementMap.get(element.getId());
+			if(knownElement != element) {
+				result.add(createDuplicateIdViolation(element, knownElement));
+			}
+		} else {
+			idToElementMap.put(element.getId(), element);
+		}
+
+		return result;
+	}
+}
diff --git a/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/constraint/IdUniquenessConstraintViolation.java b/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/constraint/IdUniquenessConstraintViolation.java
new file mode 100644
index 000000000..342f11dbf
--- /dev/null
+++ b/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/constraint/IdUniquenessConstraintViolation.java
@@ -0,0 +1,41 @@
+/*--------------------------------------------------------------------------+
+$Id$
+|                                                                          |
+| Copyright 2015 ForTISS GmbH                                              |
+|                                                                          |
+| Licensed under the Apache License, Version 2.0 (the "License");          |
+| you may not use this file except in compliance with the License.         |
+| You may obtain a copy of the License at                                  |
+|                                                                          |
+|    http://www.apache.org/licenses/LICENSE-2.0                            |
+|                                                                          |
+| Unless required by applicable law or agreed to in writing, software      |
+| distributed under the License is distributed on an "AS IS" BASIS,        |
+| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
+| See the License for the specific language governing permissions and      |
+| limitations under the License.                                           |
++--------------------------------------------------------------------------*/
+package org.fortiss.tooling.kernel.constraint;
+
+import static org.fortiss.tooling.kernel.extension.data.IConstraintViolation.ESeverity.FATAL;
+
+import org.fortiss.tooling.kernel.extension.base.ConstraintViolationBase;
+import org.fortiss.tooling.kernel.extension.data.IConstraintViolation;
+import org.fortiss.tooling.kernel.model.IIdLabeled;
+
+/**
+ * {@link IConstraintViolation} for {@link IIdLabeled} elements.
+ * 
+ * @author barner
+ * @author $Author$
+ * @version $Rev$
+ * @ConQAT.Rating YELLOW Hash: 94BDDCCA8D8AC743AE40F428DF0DEF3C
+ */
+public class IdUniquenessConstraintViolation<T extends IIdLabeled> extends
+		ConstraintViolationBase<T> {
+
+	/** Constructor. */
+	public IdUniquenessConstraintViolation(T modelElement, String explanation) {
+		super(modelElement, FATAL, explanation);
+	}
+}
diff --git a/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/constraint/package.html b/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/constraint/package.html
new file mode 100644
index 000000000..07ff7ac2a
--- /dev/null
+++ b/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/constraint/package.html
@@ -0,0 +1,8 @@
+<!--  
+  $Id$
+  @version $Rev$
+  @ConQAT.Rating YELLOW Hash: 1469932365423F2E8747E876625E2D73
+-->
+<body>
+Package for constraint checkers of the kernel. 
+</body>
diff --git a/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/extension/data/ITopLevelElement.java b/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/extension/data/ITopLevelElement.java
index 32d7327ff..86116fa49 100644
--- a/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/extension/data/ITopLevelElement.java
+++ b/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/extension/data/ITopLevelElement.java
@@ -24,6 +24,7 @@ import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.emf.common.command.CommandStackListener;
 import org.eclipse.emf.ecore.EObject;
 import org.eclipse.emf.ecore.resource.ResourceSet;
+import org.eclipse.emf.ecore.xmi.impl.XMIResourceImpl;
 import org.fortiss.tooling.kernel.extension.IStorageProvider;
 import org.fortiss.tooling.kernel.service.IPersistencyService;
 
@@ -46,7 +47,7 @@ import org.fortiss.tooling.kernel.service.IPersistencyService;
  * @author hoelzl
  * @author $Author$
  * @version $Rev$
- * @ConQAT.Rating YELLOW Hash: EC2945EED4DFFA779750D8F89323C4CA
+ * @ConQAT.Rating YELLOW Hash: 3C74DD316772AD8334C0265BB2392BF3
  */
 public interface ITopLevelElement {
 
@@ -104,6 +105,13 @@ public interface ITopLevelElement {
 	 */
 	void prepareIDs(EObject modelElement);
 
+	/**
+	 * Returns the {@code element}'s persisted ID if it is available for the particular resource
+	 * type (e.g., {@code xmi:id} for {@link XMIResourceImpl}s). Otherwise, {@code null} is
+	 * returned.
+	 */
+	public String getId(EObject elements);
+
 	/** Returns whether this top-level element can be deleted. */
 	boolean canDelete();
 
diff --git a/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/internal/DummyTopLevelElement.java b/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/internal/DummyTopLevelElement.java
index e7d3048db..9111b39e4 100644
--- a/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/internal/DummyTopLevelElement.java
+++ b/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/internal/DummyTopLevelElement.java
@@ -35,7 +35,7 @@ import org.fortiss.tooling.kernel.service.IPersistencyService;
  * @author hoelzl
  * @author $Author$
  * @version $Rev$
- * @ConQAT.Rating YELLOW Hash: 1085F8EFF356A0082ADCA16F6373B3E3
+ * @ConQAT.Rating YELLOW Hash: 64D330706835D3C03BC7487F87364627
  */
 final class DummyTopLevelElement implements ITopLevelElement {
 
@@ -127,6 +127,12 @@ final class DummyTopLevelElement implements ITopLevelElement {
 
 	}
 
+	/** {@inheritDoc} */
+	@Override
+	public String getId(EObject modelElement) {
+		return null;
+	}
+
 	/** {@inheritDoc} */
 	@Override
 	public void prepareIDs(EObject modelElement) {
diff --git a/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/internal/ElementCompositorService.java b/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/internal/ElementCompositorService.java
index 3ded84599..79cad5fda 100644
--- a/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/internal/ElementCompositorService.java
+++ b/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/internal/ElementCompositorService.java
@@ -36,7 +36,7 @@ import org.fortiss.tooling.kernel.utils.LoggingUtils;
  * @author hoelzl
  * @author $Author$
  * @version $Rev$
- * @ConQAT.Rating GREEN Hash: D2813F2898EBA0ABD1BF12D7D1554323
+ * @ConQAT.Rating YELLOW Hash: 53DE129E073FF9BDACE271D26C3D257F
  */
 public final class ElementCompositorService extends
 		EObjectAwareServiceBase<IElementCompositor<EObject>> implements IElementCompositorService {
@@ -72,8 +72,14 @@ public final class ElementCompositorService extends
 					element.getClass()));
 			return false;
 		}
+
+		// Add element to the model (might instantiate annotations for IModelElements)
+		boolean rval = compositor.compose(container, element, context);
+
+		// Generate IDs (including potentially instantiated annotations)
 		IPersistencyService.INSTANCE.getTopLevelElementFor(container).prepareIDs(element);
-		return compositor.compose(container, element, context);
+
+		return rval;
 	}
 
 	/** {@inheritDoc} */
diff --git a/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/internal/storage/eclipse/AF3ResourceFactory.java b/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/internal/storage/eclipse/AF3ResourceFactory.java
new file mode 100644
index 000000000..db907c302
--- /dev/null
+++ b/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/internal/storage/eclipse/AF3ResourceFactory.java
@@ -0,0 +1,151 @@
+/*--------------------------------------------------------------------------+
+$Id$
+|                                                                          |
+| Copyright 2015 ForTISS GmbH                     |
+|                                                                          |
+| Licensed under the Apache License, Version 2.0 (the "License");          |
+| you may not use this file except in compliance with the License.         |
+| You may obtain a copy of the License at                                  |
+|                                                                          |
+|    http://www.apache.org/licenses/LICENSE-2.0                            |
+|                                                                          |
+| Unless required by applicable law or agreed to in writing, software      |
+| distributed under the License is distributed on an "AS IS" BASIS,        |
+| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
+| See the License for the specific language governing permissions and      |
+| limitations under the License.                                           |
++--------------------------------------------------------------------------*/
+package org.fortiss.tooling.kernel.internal.storage.eclipse;
+
+import static java.lang.System.identityHashCode;
+
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EReference;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.ecore.resource.impl.ResourceFactoryImpl;
+import org.eclipse.emf.ecore.xmi.impl.XMIResourceImpl;
+import org.eclipse.emf.ecore.xml.type.AnyType;
+import org.fortiss.tooling.kernel.model.IIdLabeled;
+
+/**
+ * {@link org.eclipse.emf.ecore.resource.Resource.Factory} that creates {@link Resource}s using the
+ * {@code id} filed of {@link IIdLabeled} model elements.
+ * 
+ * One effect of using this that {@link EReference}s are encoded based on these IDs,
+ * and hence cross-references between multiple resources are more reliable (the
+ * fragment-based encoding breaks as soon as the structure of of the referenced
+ * model changes).
+ * 
+ * @author barner
+ * @author $Author$
+ * @version $Rev$
+ * @ConQAT.Rating YELLOW Hash: FC2FB6F94F632A4E942D2C5085348710
+ */
+public class AF3ResourceFactory extends ResourceFactoryImpl {
+
+	/**
+	 * {@link XMIResourceImpl} used to persist AF3 models.
+	 * 
+	 * @author barner
+	 * @author $Author$
+	 * @version $Rev$
+	 * @ConQAT.Rating RED Hash:
+	 */
+	private final class AF3XMIResource extends XMIResourceImpl {
+		/** Constructs a new {@link AF3XMIResource}. */
+		private AF3XMIResource(URI uri) {
+			super(uri);
+		}
+
+		/** {@inheritDoc} */
+		@Override
+		protected boolean useIDs() {
+			return true;
+		}
+
+		/** {@inheritDoc} */
+		@Override
+		public String getID(EObject eObject) {
+			String id = super.getID(eObject);
+			if(id == null && eObject instanceof IIdLabeled) {
+				id = new Integer(((IIdLabeled)eObject).getId()).toString();
+				setID(eObject, id);
+			}
+			return id;
+		}
+
+		/** {@inheritDoc} */
+		@Override
+		public Map<EObject, String> getEObjectToIDMap() {
+			if(eObjectToIDMap == null) {
+				// See IDENTITY_HASH_COMPARATOR why an alternative map implementation is required
+				eObjectToIDMap = new TreeMap<EObject, String>(IDENTITY_HASH_COMPARATOR);
+			}
+
+			return eObjectToIDMap;
+		}
+
+		// No override is required for getIDToEObjectMap() since its key type is String
+
+		/** {@inheritDoc} */
+		@Override
+		public Map<EObject, AnyType> getEObjectToExtensionMap() {
+			if(eObjectToExtensionMap == null) {
+				// See IDENTITY_HASH_COMPARATOR why an alternative map implementation is required.
+				eObjectToExtensionMap = new TreeMap<EObject, AnyType>(IDENTITY_HASH_COMPARATOR);
+			}
+			return eObjectToExtensionMap;
+		}
+	}
+
+	/**
+	 * Comparator based on the {@link System#identityHashCode(Object)} function.
+	 * See {@link AF3ResourceFactory#IDENTITY_HASH_COMPARATOR} for more details.
+	 * 
+	 * @author barner
+	 * @author $Author$
+	 * @version $Rev$
+	 * @ConQAT.Rating RED Hash:
+	 */
+	private static final class IdentityHashComparator implements Comparator<EObject> {
+		/** {@inheritDoc} */
+		@Override
+		public int compare(EObject o1, EObject o2) {
+			Integer h1 = identityHashCode(o1);
+			Integer h2 = identityHashCode(o2);
+			return h1.compareTo(h2);
+		}
+	}
+
+	/**
+	 * <p>
+	 * {@link IdentityHashComparator} used to implement alternative {@link EObject} -> ID /
+	 * ExtensionMaps. This is required because {@link XMIResourceImpl} is based on {@link HashMap}s
+	 * may not work correctly for keys whose {@link #hashCode()} and {@link #equals(Object)} methods
+	 * have been overridden in such a way that different instances of the same object cannot be
+	 * distinguished.
+	 * </p>
+	 * 
+	 * <p>
+	 * Since it is not possible to provide an alternative {@link #hashCode()} and
+	 * {@link #equals(Object)} implementation to a {@link HashMap}, a {@link TreeMap} is used (in
+	 * contrast to the implementation in {@link XMIResourceImpl}). Coincidentally, the
+	 * implementation {@link IdentityHashComparator#compare(EObject, EObject)} is based on
+	 * {@link System#identityHashCode(Object)}.
+	 * </p>
+	 */
+	private static final IdentityHashComparator IDENTITY_HASH_COMPARATOR =
+			new IdentityHashComparator();
+
+	/** {@inheritDoc} */
+	@Override
+	public Resource createResource(URI uri) {
+		return new AF3XMIResource(uri);
+	}
+}
diff --git a/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/internal/storage/eclipse/ModelContext.java b/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/internal/storage/eclipse/ModelContext.java
index dff5d33a5..7e5bc189c 100644
--- a/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/internal/storage/eclipse/ModelContext.java
+++ b/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/internal/storage/eclipse/ModelContext.java
@@ -27,10 +27,12 @@ import java.io.IOException;
 import java.lang.reflect.Method;
 import java.util.EventObject;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.runtime.CoreException;
@@ -39,17 +41,20 @@ import org.eclipse.emf.common.command.AbstractCommand;
 import org.eclipse.emf.common.command.BasicCommandStack;
 import org.eclipse.emf.common.command.CommandStack;
 import org.eclipse.emf.common.command.CommandStackListener;
+import org.eclipse.emf.common.notify.Notification;
 import org.eclipse.emf.common.util.Diagnostic;
 import org.eclipse.emf.common.util.URI;
 import org.eclipse.emf.ecore.EObject;
 import org.eclipse.emf.ecore.resource.Resource;
 import org.eclipse.emf.ecore.resource.ResourceSet;
 import org.eclipse.emf.ecore.util.Diagnostician;
+import org.eclipse.emf.ecore.util.EContentAdapter;
 import org.eclipse.emf.ecore.xmi.XMIResource;
 import org.eclipse.emf.ecore.xml.type.AnyType;
 import org.eclipse.emf.transaction.TransactionalEditingDomain;
 import org.fortiss.tooling.kernel.ToolingKernelActivator;
 import org.fortiss.tooling.kernel.extension.data.ITopLevelElement;
+import org.fortiss.tooling.kernel.model.FortissToolingKernelPackage;
 import org.fortiss.tooling.kernel.model.IIdLabeled;
 import org.fortiss.tooling.kernel.service.IPersistencyService;
 import org.fortiss.tooling.kernel.utils.UniqueIDUtils;
@@ -61,7 +66,7 @@ import org.fortiss.tooling.kernel.utils.UniqueIDUtils;
  * @author hummel
  * @author $Author$
  * @version $Rev$
- * @ConQAT.Rating YELLOW Hash: 4DFE8CD44974ECADFC3FEFE865258E1C
+ * @ConQAT.Rating YELLOW Hash: 004152B90C670E50F5A41475456D5E33
  */
 class ModelContext implements ITopLevelElement, CommandStackListener {
 
@@ -110,7 +115,9 @@ class ModelContext implements ITopLevelElement, CommandStackListener {
 		resource =
 				getResourceSet().createResource(
 						URI.createPlatformResourceURI(file.getFullPath().toString(), true));
+
 		resource.load(buildOptionsMap());
+
 		unknownFeatures =
 				resource instanceof XMIResource ? ((XMIResource)resource)
 						.getEObjectToExtensionMap() : new HashMap<EObject, AnyType>();
@@ -118,9 +125,42 @@ class ModelContext implements ITopLevelElement, CommandStackListener {
 		transactionalCommandStack = new AutoUndoCommandStack(editingDomain);
 		transactionalCommandStack.addCommandStackListener(this);
 
+		// Install adapter to maintain XMI IDs
+		resource.eAdapters().add(new EContentAdapter() {
+			@Override
+			public void notifyChanged(Notification notification) {
+				super.notifyChanged(notification);
+
+				int eventType = notification.getEventType();
+				Object feature = notification.getFeature();
+
+				if(eventType == Notification.SET &&
+						feature == FortissToolingKernelPackage.Literals.IID_LABELED__ID) {
+
+					// Update XMI IDs when Kernel IDs are changed by calls of IdLabled.setId()
+					Object notifier = notification.getNotifier();
+
+					if(resource instanceof XMIResource && notifier instanceof EObject) {
+						((XMIResource)resource).setID((EObject)notifier,
+								notification.getNewStringValue());
+					}
+				}
+			}
+		});
+
 		checkIDs();
 	}
 
+	/** {@inheritDoc} */
+	@Override
+	public String getId(EObject modelElement) {
+		if(resource instanceof XMIResource) {
+			return ((XMIResource)resource).getID(modelElement);
+		}
+
+		return null;
+	}
+
 	/** {@inheritDoc} */
 	@Override
 	public EObject getRootModelElement() {
@@ -141,20 +181,47 @@ class ModelContext implements ITopLevelElement, CommandStackListener {
 		listeners.remove(listener);
 	}
 
-	/** Checks whether all IDs are present and updates {@link #maxId}. */
+	/**
+	 * Checks whether all IDs are present and unique and updates {@link #maxId}. In case duplicate
+	 * IDs have been detected (which could only be caused by bug in AF3), these are removed and an
+	 * error is logged.
+	 */
 	private void checkIDs() {
+		Set<Integer> ids = new HashSet<Integer>();
+
 		boolean hadMissing = false;
+		boolean hadDuplicates = false;
 		for(Iterator<EObject> i = getRootModelElement().eAllContents(); i.hasNext();) {
 			EObject eo = i.next();
 			if(eo instanceof IIdLabeled) {
-				if(((IIdLabeled)eo).getId() <= 0) {
+				final IIdLabeled element = (IIdLabeled)eo;
+				int id = element.getId();
+				if(id <= 0) {
 					hadMissing = true;
+				} else {
+					// Reset duplicate IDs. They will be regenerated together with missing IDs. This
+					// is a safe operation since at this stage, references are still based on Java
+					// references. Only in the persisted resource, references will be based on IDs,
+					// which is why special care must be taken to ensure uniqueness.
+					if(ids.contains(id)) {
+						hadDuplicates = true;
+						runAsCommand(new Runnable() {
+							@Override
+							public void run() {
+								element.setId(0);
+							}
+						});
+					} else {
+						ids.add(id);
+					}
 				}
-				maxId = Math.max(maxId, ((IIdLabeled)eo).getId());
+				maxId = Math.max(maxId, id);
 			}
 		}
+
 		maxId = Math.max(0, maxId);
-		if(hadMissing) {
+
+		if(hadMissing || hadDuplicates) {
 			runAsCommand(new Runnable() {
 				@Override
 				public void run() {
@@ -162,6 +229,13 @@ class ModelContext implements ITopLevelElement, CommandStackListener {
 				}
 			});
 		}
+
+		if(hadDuplicates) {
+			error(ToolingKernelActivator.getDefault(),
+					"Duplicate IDs that have been removed from \"" +
+							resource.getURI().lastSegment() +
+							"\". Please report this incident since it could result in corrupted model files.");
+		}
 	}
 
 	/** {@inheritDoc} */
diff --git a/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/utils/EMFResourceUtils.java b/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/utils/EMFResourceUtils.java
index c5a3b75e2..d798b0040 100644
--- a/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/utils/EMFResourceUtils.java
+++ b/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/utils/EMFResourceUtils.java
@@ -41,7 +41,7 @@ import org.fortiss.tooling.kernel.ToolingKernelActivator;
  * @author hoelzl
  * @author $Author$
  * @version $Rev$
- * @ConQAT.Rating GREEN Hash: 0F500D545960AA85BBE636519B9120C4
+ * @ConQAT.Rating YELLOW Hash: C9B362A70470F5A03AD3A9B92F5ACA78
  */
 public final class EMFResourceUtils {
 	/**
@@ -72,6 +72,11 @@ public final class EMFResourceUtils {
 		options.put(XMLResource.OPTION_ENCODING, "UTF-8");
 		options.put(XMLResource.OPTION_RECORD_UNKNOWN_FEATURE, true);
 		options.put(Resource.OPTION_ZIP, false);
+
+		// Options to increase performance of loading models
+		options.put(XMLResource.OPTION_DEFER_ATTACHMENT, true);
+		options.put(XMLResource.OPTION_DEFER_IDREF_RESOLUTION, true);
+
 		return options;
 	}
 
diff --git a/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/utils/UniqueIDUtils.java b/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/utils/UniqueIDUtils.java
index 09dcd31e3..911af96f3 100644
--- a/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/utils/UniqueIDUtils.java
+++ b/org.fortiss.tooling.kernel/trunk/src/org/fortiss/tooling/kernel/utils/UniqueIDUtils.java
@@ -36,7 +36,7 @@ import org.fortiss.tooling.kernel.service.IPersistencyService;
  * @author hoelzl
  * @author $Author$
  * @version $Rev$
- * @ConQAT.Rating GREEN Hash: 96CB692EF9A0A75F03B5E7A94DD151A1
+ * @ConQAT.Rating YELLOW Hash: 148BF02D8194CCBF2129756F8FD068D9
  */
 public class UniqueIDUtils {
 
@@ -61,6 +61,37 @@ public class UniqueIDUtils {
 		return currentMaxId;
 	}
 
+	/**
+	 * Removes duplicate IDs from the given model by setting them to {@code 0}. Subsequently,
+	 * {@link #generateMissingIDs(EObject, int)} may be used to regenerate the duplicates that have
+	 * been removed.
+	 * 
+	 * @param existingModel
+	 *            the model to be updated
+	 * @return
+	 *         {@code true} if the model contained at least one duplicate that has been removed.
+	 */
+	public static boolean removeDuplicateIds(EObject existingModel) {
+		Set<Integer> ids = new HashSet<Integer>();
+		boolean removedDuplicateId = false;
+
+		for(Iterator<EObject> i = existingModel.eAllContents(); i.hasNext();) {
+			EObject eo = i.next();
+			if(eo instanceof IIdLabeled) {
+				IIdLabeled element = (IIdLabeled)eo;
+				int id = element.getId();
+				if(ids.contains(id)) {
+					element.setId(0);
+					removedDuplicateId = true;
+				} else {
+					ids.add(id);
+				}
+			}
+		}
+
+		return removedDuplicateId;
+	}
+
 	/**
 	 * Returns the next ID not used within the given model
 	 * 
-- 
GitLab