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