diff --git a/org.fortiss.tooling.kernel/trunk/plugin.xml b/org.fortiss.tooling.kernel/trunk/plugin.xml index 59be3661325cc30b649305ec5cf498c2992c94b8..b8e577120ff74c261c1fa116d1f2c4f09c868c71 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 0000000000000000000000000000000000000000..a1df7bf55dab82b5b9b378e2cf28543f93a03381 --- /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 0000000000000000000000000000000000000000..2b71f3dcd2e4d82b999676af0604b51ef677b24e --- /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 0000000000000000000000000000000000000000..9a2f06ffd450b7ccbd67015f827d00406c85d3d0 --- /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 0000000000000000000000000000000000000000..a1a945574f825436fc6e1374dcd93e64ff2f3457 --- /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 0000000000000000000000000000000000000000..342f11dbf7afff2459d9e172cbfbe6cfc5a111b5 --- /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 0000000000000000000000000000000000000000..07ff7ac2a34987b3b788c1ea06625e731f5dabb7 --- /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 32d7327ff302849112cb2cb77c5cdb0c2798c580..86116fa49bad985d5d26fd76b6c12a5e421ef381 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 e7d3048db29456614d2b07c385a357b8a88e7303..9111b39e4ed6d192728032f8b713eee0144ebe91 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 3ded845996d5c83eac2b3aeef6e17aea7f4a9dd2..79cad5fda1ad6994ccb27dc038e255b1b1f5427e 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 0000000000000000000000000000000000000000..db907c302041dee78b550751f16d8fa0c15cd79d --- /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 dff5d33a5a69779910862bed3172fd06c592bee4..7e5bc189c8d09ea7be370a8fdce04662687824d1 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 c5a3b75e201cd5e82bf15c8428dba3dcd13da787..d798b0040d23f7936d28821829ba33be5b1deb2d 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 09dcd31e34451241f74326f023b7b3ff90ba2d30..911af96f30983b5f64b3a563b064339eaac9e36d 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 *