diff --git a/bundles/tools.vitruv.change.atomic/META-INF/MANIFEST.MF b/bundles/tools.vitruv.change.atomic/META-INF/MANIFEST.MF index ac99dc8c..26170526 100644 --- a/bundles/tools.vitruv.change.atomic/META-INF/MANIFEST.MF +++ b/bundles/tools.vitruv.change.atomic/META-INF/MANIFEST.MF @@ -33,7 +33,8 @@ Export-Package: tools.vitruv.change.atomic, tools.vitruv.change.atomic.root.impl, tools.vitruv.change.atomic.root.util, tools.vitruv.change.atomic.resolve, - tools.vitruv.change.atomic.id + tools.vitruv.change.atomic.id, + tools.vitruv.change.atomic.uuid Require-Bundle: org.eclipse.xtend.lib, org.apache.log4j, org.eclipse.emf.ecore.edit, diff --git a/bundles/tools.vitruv.change.atomic/src/tools/vitruv/change/atomic/EChangeIdManager.xtend b/bundles/tools.vitruv.change.atomic/src/tools/vitruv/change/atomic/EChangeIdManager.xtend index c1be5978..bdd3e454 100644 --- a/bundles/tools.vitruv.change.atomic/src/tools/vitruv/change/atomic/EChangeIdManager.xtend +++ b/bundles/tools.vitruv.change.atomic/src/tools/vitruv/change/atomic/EChangeIdManager.xtend @@ -1,13 +1,14 @@ package tools.vitruv.change.atomic +import org.eclipse.emf.ecore.EObject import tools.vitruv.change.atomic.eobject.EObjectAddedEChange -import tools.vitruv.change.atomic.eobject.EObjectSubtractedEChange import tools.vitruv.change.atomic.eobject.EObjectExistenceEChange +import tools.vitruv.change.atomic.eobject.EObjectSubtractedEChange import tools.vitruv.change.atomic.feature.FeatureEChange -import org.eclipse.emf.ecore.EObject +import tools.vitruv.change.atomic.id.IdResolver + import static com.google.common.base.Preconditions.checkArgument import static com.google.common.base.Preconditions.checkState -import tools.vitruv.change.atomic.id.IdResolver /** * Provides logic for initializing the IDs within changes. diff --git a/bundles/tools.vitruv.change.atomic/src/tools/vitruv/change/atomic/EChangeUuidManager.xtend b/bundles/tools.vitruv.change.atomic/src/tools/vitruv/change/atomic/EChangeUuidManager.xtend new file mode 100644 index 00000000..07c3b968 --- /dev/null +++ b/bundles/tools.vitruv.change.atomic/src/tools/vitruv/change/atomic/EChangeUuidManager.xtend @@ -0,0 +1,109 @@ +package tools.vitruv.change.atomic + +import org.eclipse.emf.ecore.EObject +import tools.vitruv.change.atomic.eobject.CreateEObject +import tools.vitruv.change.atomic.eobject.EObjectAddedEChange +import tools.vitruv.change.atomic.eobject.EObjectExistenceEChange +import tools.vitruv.change.atomic.eobject.EObjectSubtractedEChange +import tools.vitruv.change.atomic.feature.FeatureEChange +import tools.vitruv.change.atomic.uuid.UuidResolver + +import static com.google.common.base.Preconditions.checkArgument +import static com.google.common.base.Preconditions.checkState + +/** + * Provides logic for initializing the UUIDs within changes. + */ +class EChangeUuidManager { + val UuidResolver uuidResolver + + /** + * Initializes the manager with a {@link UuidResolver}. + * + * @param uuidResolver - + * the {@link UuidResolver} to use for UUID management + */ + new(UuidResolver uuidResolver) { + checkArgument(uuidResolver !== null, "uuid resolver must not be null") + this.uuidResolver = uuidResolver + } + + def static void setOrGenerateIds(Iterable eChanges, UuidResolver uuidResolver) { + setOrGenerateIds(eChanges, uuidResolver, true) + } + + def static void setOrGenerateIds(Iterable eChanges, UuidResolver uuidResolver, boolean endTransaction) { + val manager = new EChangeUuidManager(uuidResolver) + eChanges.forEach [ eChange | + manager.setOrGenerateIds(eChange) + ] + if (endTransaction) { + uuidResolver.endTransaction + } + } + + def void setOrGenerateIds(EChange eChange) { + switch eChange { + CreateEObject: + setOrGenerateCreatedEObjectUuid(eChange) + EObjectExistenceEChange: + setAffectedEObjectUuid(eChange) + FeatureEChange: + setAffectedEObjectUuid(eChange) + } + switch eChange { + EObjectSubtractedEChange: + setOldValueUuid(eChange) + } + switch eChange { + EObjectAddedEChange: + setOrGenerateNewValueUuid(eChange) + } + } + + private def String getUuid(EObject object) { + val id = uuidResolver.getUuid(object) + checkState(id !== null, "uuid must not be null") + return id + } + + private def String getOrGenerateUuid(EObject object) { + if (uuidResolver.hasUuid(object)) { + return getUuid(object) + } + uuidResolver.registerEObject(object) + } + + private def void setOrGenerateNewValueUuid(EObjectAddedEChange addedEChange) { + if (addedEChange.newValue === null) { + return; + } + addedEChange.newValueID = addedEChange.newValue.getOrGenerateUuid + } + + private def void setOldValueUuid(EObjectSubtractedEChange subtractedEChange) { + if (subtractedEChange.oldValue === null) { + return; + } + subtractedEChange.oldValueID = subtractedEChange.oldValue.uuid + } + + private def void setAffectedEObjectUuid(EObjectExistenceEChange existenceChange) { + val affectedEObject = existenceChange.affectedEObject + checkArgument(affectedEObject !== null, "existence change must have an affected EObject: %s", existenceChange) + existenceChange.affectedEObjectID = affectedEObject.uuid + } + + private def void setOrGenerateCreatedEObjectUuid(CreateEObject existenceChange) { + val affectedEObject = existenceChange.affectedEObject + checkArgument(affectedEObject !== null, "existence change must have an affected EObject: %s", existenceChange) + existenceChange.affectedEObjectID = affectedEObject.getOrGenerateUuid + } + + private def void setAffectedEObjectUuid(FeatureEChange featureChange) { + val affectedEObject = featureChange.affectedEObject + checkArgument(affectedEObject !== null, "feature change must have an affected EObject: %s", featureChange) + featureChange.affectedEObjectID = affectedEObject.uuid + } + +} diff --git a/bundles/tools.vitruv.change.atomic/src/tools/vitruv/change/atomic/resolve/AtomicEChangeResolver.xtend b/bundles/tools.vitruv.change.atomic/src/tools/vitruv/change/atomic/resolve/AtomicEChangeIdResolver.xtend similarity index 99% rename from bundles/tools.vitruv.change.atomic/src/tools/vitruv/change/atomic/resolve/AtomicEChangeResolver.xtend rename to bundles/tools.vitruv.change.atomic/src/tools/vitruv/change/atomic/resolve/AtomicEChangeIdResolver.xtend index f7c48e27..87c01e53 100644 --- a/bundles/tools.vitruv.change.atomic/src/tools/vitruv/change/atomic/resolve/AtomicEChangeResolver.xtend +++ b/bundles/tools.vitruv.change.atomic/src/tools/vitruv/change/atomic/resolve/AtomicEChangeIdResolver.xtend @@ -22,7 +22,7 @@ import tools.vitruv.change.atomic.id.IdResolver /** * Static class for resolving EChanges internally. */ -package class AtomicEChangeResolver { +package class AtomicEChangeIdResolver { val IdResolver idResolver new (IdResolver idResolver) { diff --git a/bundles/tools.vitruv.change.atomic/src/tools/vitruv/change/atomic/resolve/AtomicEChangeUuidResolver.xtend b/bundles/tools.vitruv.change.atomic/src/tools/vitruv/change/atomic/resolve/AtomicEChangeUuidResolver.xtend new file mode 100644 index 00000000..4d8dff47 --- /dev/null +++ b/bundles/tools.vitruv.change.atomic/src/tools/vitruv/change/atomic/resolve/AtomicEChangeUuidResolver.xtend @@ -0,0 +1,215 @@ +package tools.vitruv.change.atomic.resolve + +import org.eclipse.emf.common.util.URI +import org.eclipse.emf.ecore.EObject +import org.eclipse.emf.ecore.EStructuralFeature +import org.eclipse.emf.ecore.util.EcoreUtil +import tools.vitruv.change.atomic.EChange +import tools.vitruv.change.atomic.eobject.CreateEObject +import tools.vitruv.change.atomic.eobject.DeleteEObject +import tools.vitruv.change.atomic.eobject.EObjectExistenceEChange +import tools.vitruv.change.atomic.feature.FeatureEChange +import tools.vitruv.change.atomic.feature.reference.InsertEReference +import tools.vitruv.change.atomic.feature.reference.RemoveEReference +import tools.vitruv.change.atomic.feature.reference.ReplaceSingleValuedEReference +import tools.vitruv.change.atomic.root.InsertRootEObject +import tools.vitruv.change.atomic.root.RemoveRootEObject +import tools.vitruv.change.atomic.root.RootEChange +import tools.vitruv.change.atomic.uuid.UuidResolver + +import static com.google.common.base.Preconditions.checkArgument +import static com.google.common.base.Preconditions.checkState + +/** + * Static class for resolving EChanges internally. + */ +package class AtomicEChangeUuidResolver { + val UuidResolver uuidResolver + + new (UuidResolver uuidResolver) { + checkArgument(uuidResolver !== null, "UUID resolver must not be null") + this.uuidResolver = uuidResolver + } + + /** + * Resolves {@link FeatureEChange} attributes {@code affectedEObject} and {@code affectedFeature}. + * @param change The change which should be resolved. + */ + def private void resolveFeatureEChange(FeatureEChange change) { + checkArgument(change.affectedEObjectID !== null, "change %s must have an affected EObject ID", change) + checkArgument(change.affectedFeature !== null, "change %s must have an affected feature", change) + if (uuidResolver.hasEObject(change.affectedEObjectID)) { + change.affectedEObject = uuidResolver.getEObject(change.affectedEObjectID) as A + } + change.affectedEObject.checkNotNullAndNotProxy(change, "affected object") + } + + def private EObject resolveObject(String valueId) { + if (valueId === null) { + return null + } + return uuidResolver.getEObject(valueId) + } + + /** + * Resolves {@link EObjectExistenceEChange} attribute {@code affectedEObject}. + * @param change The change which should be resolved. + * @param isNewObject true if the given change creates the object, false if it deletes the object + */ + def private void resolveEObjectExistenceEChange(EObjectExistenceEChange change, boolean isNewObject) { + checkArgument(change.affectedEObjectID !== null, "change %s must have an affected EObject ID", change) + + // Resolve the affected object + if (isNewObject) { + // Check if ID resolver may still contain the removed object + if (uuidResolver.hasEObject(change.affectedEObjectID)) { + val stillExistingObject = uuidResolver.getEObject(change.affectedEObjectID) as A + change.affectedEObject = stillExistingObject + change.affectedEObject.checkNotNullAndNotProxy(change, "affected object") + } else { + // Create new one + val newObject = EcoreUtil.create(change.affectedEObjectType) as A + change.affectedEObject = newObject + uuidResolver.registerEObject(change.affectedEObjectID, newObject) + } + } else { + // Object still exists + change.affectedEObject = uuidResolver.getEObject(change.affectedEObjectID) as A + change.affectedEObject.checkNotNullAndNotProxy(change, "affected object") + } + + if (change.idAttributeValue !== null) { + EcoreUtil.setID(change.affectedEObject, change.idAttributeValue) + } + } + + /** + * Resolves {@link RootEChange} attribute {@code resource}. + * @param change The change which should be resolved. + */ + def private void resolveRootEChange(RootEChange change) { + // Get resource where the root object will be inserted / removed. + change.resource = uuidResolver.getResource(URI.createURI(change.uri)) + } + + /** + * Resolves the value of an {@link RootEChange}. + * @param change The change whose value shall be resolved. + * @param value The value that should be used if no value can be resolved for an id linked in the given change + * @param isInserted {@code true} if the concrete value is already inserted into the resource. + * Depends on the kind of the change and the model state. + * @returns The resolved value. + */ + def private resolveRootValue(RootEChange change, T value, boolean isInserted) { + // Resolve the root object + val result = if (change instanceof InsertRootEObject) { + uuidResolver.getEObject(change.newValueID) + } else if (change instanceof RemoveRootEObject) { + uuidResolver.getEObject(change.oldValueID) + } else { + value + } + + if (isInserted) { + checkState(0 <= change.index && change.index < change.resource.contents.size && + change.resource.contents.get(change.index) === result, + "invalid index in change %s for resolved object %s", change, result) + } + return result + } + + /** + * Dispatch method for resolving the {@link EChange}. + * @param change The change which should be resolved. + */ + def package dispatch void resolve(EChange change) { + // If an EChange reaches this point, there is a dispatch method missing for the concrete type. + throw new UnsupportedOperationException("change of type " + change?.eClass + " is not supported") + } + + /** + * Dispatch method for resolving the {@link FeatureEChange} EChange. + * @param change The change which should be resolved. + */ + def package dispatch void resolve(FeatureEChange change) { + change.resolveFeatureEChange() + } + + /** + * Dispatch method for resolving the {@link InsertEReference} EChange. + * @param change The change which should be resolved. + */ + def package dispatch void resolve(InsertEReference change) { + change.resolveFeatureEChange() + change.newValue = change.newValueID.resolveObject() + change.newValue.checkNotNullAndNotProxy(change, "new value") + } + + /** + * Dispatch method for resolving the {@link RemoveEReference} EChange. + * @param change The change which should be resolved. + */ + def package dispatch void resolve(RemoveEReference change) { + change.resolveFeatureEChange() + change.oldValue = change.oldValueID.resolveObject() + change.oldValue.checkNotNullAndNotProxy(change, "old value") + } + + /** + * Dispatch method for resolving the {@link ReplaceSingleValuedEReferenceEReference} EChange. + * @param change The change which should be resolved. + */ + def package dispatch void resolve(ReplaceSingleValuedEReference change) { + change.resolveFeatureEChange() + change.newValue = change.newValueID.resolveObject() + change.oldValue = change.oldValueID.resolveObject() + change.oldValue.checkEitherNullOrNotProxy(change, "old value") + change.newValue.checkEitherNullOrNotProxy(change, "new value") + } + + /** + * Dispatch method for resolving the {@link InsertRootEObject} EChange. + * @param change The change which should be resolved. + */ + def package dispatch void resolve(InsertRootEObject change) { + change.resolveRootEChange() + change.newValue = change.resolveRootValue(change.newValue, false) + change.newValue.checkNotNullAndNotProxy(change, "new value") + } + + /** + * Dispatch method for resolving the {@link RemoveRootEObject} EChange. + * @param change The change which should be resolved. + */ + def package dispatch void resolve(RemoveRootEObject change) { + change.resolveRootEChange() + change.oldValue = change.resolveRootValue(change.oldValue, true) + change.oldValue.checkNotNullAndNotProxy(change, "old value") + } + + /** + * Dispatch method for resolving the {@link CreateEObject} EChange. + * @param change The change which should be resolved. + */ + def package dispatch void resolve(CreateEObject change) { + change.resolveEObjectExistenceEChange(true) + } + + /** + * Dispatch method for resolving the {@link DeleteEObject} EChange. + * @param change The change which should be resolved. + */ + def package dispatch void resolve(DeleteEObject change) { + change.resolveEObjectExistenceEChange(false) + } + + def private static void checkNotNullAndNotProxy(EObject object, EChange change, String nameOfElementInChange) { + checkState(object !== null, "%s of change %s was resolved to null", nameOfElementInChange, change) + checkState(!object.eIsProxy, "%s of change %s was resolved to a proxy", nameOfElementInChange, object) + } + + def private static void checkEitherNullOrNotProxy(EObject object, EChange change, String nameOfElementInChange) { + checkState(object === null || !object.eIsProxy, "%s of change %s was resolved to a proxy", nameOfElementInChange, object) + } +} + \ No newline at end of file diff --git a/bundles/tools.vitruv.change.atomic/src/tools/vitruv/change/atomic/resolve/EChangeResolverAndApplicator.xtend b/bundles/tools.vitruv.change.atomic/src/tools/vitruv/change/atomic/resolve/EChangeIdResolverAndApplicator.xtend similarity index 93% rename from bundles/tools.vitruv.change.atomic/src/tools/vitruv/change/atomic/resolve/EChangeResolverAndApplicator.xtend rename to bundles/tools.vitruv.change.atomic/src/tools/vitruv/change/atomic/resolve/EChangeIdResolverAndApplicator.xtend index 02220def..0efe9d85 100644 --- a/bundles/tools.vitruv.change.atomic/src/tools/vitruv/change/atomic/resolve/EChangeResolverAndApplicator.xtend +++ b/bundles/tools.vitruv.change.atomic/src/tools/vitruv/change/atomic/resolve/EChangeIdResolverAndApplicator.xtend @@ -18,13 +18,7 @@ import tools.vitruv.change.atomic.id.IdResolver * Utility class for applying and resolving a given EChange. */ @Utility -class EChangeResolverAndApplicator { - static def C unresolve(C eChange) { - val copy = EcoreUtil.copy(eChange) - EChangeUnresolver.unresolve(copy) - return copy - } - +class EChangeIdResolverAndApplicator { static def EChange resolveBefore(EChange eChange, IdResolver idResolver) { return resolveCopy(eChange, idResolver) } @@ -94,7 +88,7 @@ class EChangeResolverAndApplicator { def private static EChange resolveCopy(EChange change, IdResolver idResolver) { checkArgument(!change.isResolved, "change must not be resolved when trying to resolve") var EChange copy = EcoreUtil.copy(change) - new AtomicEChangeResolver(idResolver).resolve(copy) + new AtomicEChangeIdResolver(idResolver).resolve(copy) return copy } diff --git a/bundles/tools.vitruv.change.atomic/src/tools/vitruv/change/atomic/resolve/EChangeUuidResolverAndApplicator.xtend b/bundles/tools.vitruv.change.atomic/src/tools/vitruv/change/atomic/resolve/EChangeUuidResolverAndApplicator.xtend new file mode 100644 index 00000000..9424b7d3 --- /dev/null +++ b/bundles/tools.vitruv.change.atomic/src/tools/vitruv/change/atomic/resolve/EChangeUuidResolverAndApplicator.xtend @@ -0,0 +1,61 @@ +package tools.vitruv.change.atomic.resolve + +import edu.kit.ipd.sdq.activextendannotations.Utility +import org.eclipse.emf.ecore.EObject +import org.eclipse.emf.ecore.util.EcoreUtil +import tools.vitruv.change.atomic.EChange +import tools.vitruv.change.atomic.command.ApplyEChangeSwitch +import tools.vitruv.change.atomic.eobject.CreateEObject +import tools.vitruv.change.atomic.eobject.DeleteEObject +import tools.vitruv.change.atomic.uuid.UuidResolver + +import static com.google.common.base.Preconditions.checkArgument + +/** + * Utility class for applying and resolving a given EChange. + */ +@Utility +class EChangeUuidResolverAndApplicator { + static def C unresolve(C eChange) { + val copy = EcoreUtil.copy(eChange) + EChangeUnresolver.unresolve(copy) + return copy + } + + static def EChange resolveBefore(EChange eChange, UuidResolver uuidResolver) { + return resolveCopy(eChange, uuidResolver) + } + + static def void applyForward(EChange eChange, UuidResolver uuidResolver) { + ApplyEChangeSwitch.applyEChange(eChange, true) + switch (eChange) { + CreateEObject: + uuidResolver.registerEObject(eChange.affectedEObjectID, eChange.affectedEObject) + } + } + + static def void applyBackward(EChange eChange, UuidResolver uuidResolver) { + ApplyEChangeSwitch.applyEChange(eChange, false) + switch (eChange) { + DeleteEObject: + uuidResolver.registerEObject(eChange.affectedEObjectID, eChange.affectedEObject) + } + } + + /** + * Creates a copy of the change and resolves it using the given {@link UuidResolver}. + * + * @param change The {@link EChange} which shall be resolved. + * @param uuidResolver The {@link UuidResolver} to resolve {@link EObject}s from + * @return Returns a resolved copy of the change. If the copy could not be resolved + * an {@link IllegalStateException} is thrown + * @throws IllegalArgumentException The change is already resolved. + * @throws IllegalStateException The change cannot be resolved. + */ + def private static EChange resolveCopy(EChange change, UuidResolver uuidResolver) { + checkArgument(!change.isResolved, "change must not be resolved when trying to resolve") + var EChange copy = EcoreUtil.copy(change) + new AtomicEChangeUuidResolver(uuidResolver).resolve(copy) + return copy + } +} diff --git a/bundles/tools.vitruv.change.atomic/src/tools/vitruv/change/atomic/uuid/UuidResolver.java b/bundles/tools.vitruv.change.atomic/src/tools/vitruv/change/atomic/uuid/UuidResolver.java new file mode 100644 index 00000000..b27ccdd9 --- /dev/null +++ b/bundles/tools.vitruv.change.atomic/src/tools/vitruv/change/atomic/uuid/UuidResolver.java @@ -0,0 +1,193 @@ +package tools.vitruv.change.atomic.uuid; + +import static com.google.common.base.Preconditions.checkState; + +import java.io.IOException; +import java.util.Map; + +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; + +/** + * A UUID resolver manages the mapping of {@link EObject} to UUIDs within one + * resource set. UUIDs are used to uniquely identify an element in changes, + * independent of the location within a resource set and the actual resource set + * instance. + */ +public interface UuidResolver { + /** + * Returns whether the given {@link EObject} has a registered UUID or not. + */ + public default boolean hasUuid(EObject eObject) { + try { + String uuid = getUuid(eObject); + return uuid != null; + } catch (IllegalStateException e) { + return false; + } + }; + + /** + * Returns whether an {@link EObject} is registered for the given UUID or not. + */ + public default boolean hasEObject(String uuid) { + try { + EObject eObject = getEObject(uuid); + return eObject != null; + } catch (IllegalStateException e) { + return false; + } + }; + + /** + * Returns the UUID for the given {@link EObject}. If no UUID is registered for + * it, an {@link IllegalStateException} is thrown. + */ + public String getUuid(EObject eObject) throws IllegalStateException; + + /** + * Returns the {@link EObject} for the given UUID. If more than one object was + * registered for the UUID, the last one is returned. + * + * @throws IllegalStateException if no {@link EObject} was registered for the + * UUID + */ + public EObject getEObject(String uuid) throws IllegalStateException; + + /** + * Generates a new UUID for the given {@link EObject}. + * + * @param eObject is the object to generate a UUID for. Must not be + * null or a proxy. + */ + public String generateUuid(EObject eObject); + + /** + * Registers the given {@link EObject} for the given UUID. + * + * @param uuid is the UUID to register the {@link EObject} for. Must not be + * null. + * @param eObject is the {@link EObject} to register. Must not be + * null or a proxy. + * @throws IllegalStateException if there is already another UUID registered for + * the given {@link EObject} or vice versa. + */ + public void registerEObject(String uuid, EObject eObject) throws IllegalStateException; + + /** + * Registers the given {@link EObjecty} for a newly generated UUID and returns + * that UUID. The UUID is generated using {@link UuidResolver#generateUuid}. + * + * @param eObject is the object to register. Must not be null or a + * proxy. + * @throws IllegalStateException if there is already another UUID registered for + * the given {@link EObject}. + * @return the UUID registered for the given {@link EObject}. + */ + public default String registerEObject(EObject eObject) throws IllegalStateException { + String uuid = generateUuid(eObject); + registerEObject(uuid, eObject); + return uuid; + } + + /** + * Returns the {@link Resource} for the given {@link URI}. If the resource does + * not exist yet, it gets created. + */ + public Resource getResource(URI uri); + + /** + * Ends a transactions such that all {@link EObject}s not being contained in a + * resource, which is contained in a resource set, are removed from the UUID + * mapping. + */ + public void endTransaction(); + + /** + * Resolves all {@link EObject}s contained in any resource of the given + * mapping's key set to its counterpart {@link EObject} in the corresponding + * resource of the targetUuidResolver and registers the resolved + * object under the same UUID as in the current resolver. The resource + * correspondences are determined by the + * sourceToTargetResourceMapping. Each resource pair in the mapping + * is expected to be structurally equal. + * + * @param sourceToTargetResourceMapping is the mapping between resources from + * the current resolver to the given target + * resolver. Must not be null. + * The key set must contain only resources + * of the current UUID resolver. The values + * must contain only resources of the + * target UUID resolver. Each resource pair + * is expected to be structurally equal. + * @param targetUuidResolver is the {@link UuidResolver} to resolve + * the given resources in. Must not be + * null. + * @throws IllegalStateException if any {@link EObject} of the current resolver + * is not contained in a resource or a resource + * pair is not structurally equal. + */ + public void resolveResources(Map sourceToTargetResourceMapping, UuidResolver targetUuidResolver) + throws IllegalStateException; + + /** + * Resolves all {@link EObject}s contained in the given + * sourceResource to its counterpart {@link EObject} in the + * targetResource and registers the resolved object under the same + * UUID as in the current resolver. The source and target resources are expected + * to be structurally equal. + * + * @param sourceResource is the source resource, contained in the current + * resolver's resource set. Must not be + * null. + * @param targetResource is the target resource, contained in the target + * resolver's resource set. Must not be + * null. + * @param targetUuidResolver is the {@link UuidResolver} to resolve the given + * resources in. Must not be null. + * @throws IllegalStateException if any {@link EObject} of the current resolver + * is not contained in a resource or the given + * resources are not structurally equal. + */ + public default void resolveResource(Resource sourceResource, Resource targetResource, + UuidResolver targetUuidResolver) throws IllegalStateException { + checkState(sourceResource != null, "source resource must not be null"); + checkState(targetResource != null, "target resource must not be null"); + resolveResources(Map.of(sourceResource, targetResource), targetUuidResolver); + } + + /** + * Creates a new {@link UuidResolver} with the given resource set. + * + * @param resourceSet is the resource set the UUID resolver uses. + * @return a new {@link UuidResolver} instance. + */ + public static UuidResolver create(ResourceSet resourceSet) { + return new UuidResolverImpl(resourceSet); + } + + /** + * Stores the contents of this resolver at the given {@link URI}. + * + * @param uri is the {@link URI} to store the serialization at. Must not be + * null and must be a file URI. + * @throws IOException if saving to file fails. + * @throws IllegalStateException if any {@link EObject} of the current resolver + * is not contained in a resource. + */ + public void storeAtUri(URI uri) throws IOException, IllegalStateException; + + /** + * Initializes this resolver with the contents at the given {@link URI}. Can + * only be called before any elements are registered with this resolver. + * + * @param uri is the {@link URI} to load the serialization from. Must not be + * null and must be a file URI. + * @throws IOException if reading the file fails. + * @throws IllegalStateException if this resolver already has elements + * registered. + */ + public void loadFromUri(URI uri) throws IOException, IllegalStateException; +} diff --git a/bundles/tools.vitruv.change.atomic/src/tools/vitruv/change/atomic/uuid/UuidResolverImpl.java b/bundles/tools.vitruv.change.atomic/src/tools/vitruv/change/atomic/uuid/UuidResolverImpl.java new file mode 100644 index 00000000..8f8eda6a --- /dev/null +++ b/bundles/tools.vitruv.change.atomic/src/tools/vitruv/change/atomic/uuid/UuidResolverImpl.java @@ -0,0 +1,268 @@ +package tools.vitruv.change.atomic.uuid; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; +import static edu.kit.ipd.sdq.commons.util.org.eclipse.emf.common.util.URIUtil.isPathmap; +import static edu.kit.ipd.sdq.commons.util.org.eclipse.emf.ecore.resource.ResourceSetUtil.getOrCreateResource; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.apache.log4j.Logger; +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.EcoreUtil; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; + +import tools.vitruv.change.atomic.id.IdResolver; + +class UuidResolverImpl implements UuidResolver { + static private final Logger LOGGER = Logger.getLogger(UuidResolverImpl.class); + private static final String NON_READONLY_PREFIX = "ord_"; + + private static final String SERIALIZATION_SEPARATOR = "|"; + + private final ResourceSet resourceSet; + private final BiMap eObjectToUuid = HashBiMap.create(); + + public UuidResolverImpl(ResourceSet resourceSet) { + checkArgument(resourceSet != null, "Resource set may not be null"); + this.resourceSet = resourceSet; + } + + @Override + public String getUuid(EObject eObject) throws IllegalStateException { + String uuid = getUuidOrNull(eObject); + checkState(uuid != null, "no UUID could be found for EObject: %s", eObject); + return uuid; + } + + @Override + public EObject getEObject(String uuid) throws IllegalStateException { + EObject eObject = getEObjectOrNull(uuid); + checkState(eObject != null, "no EObject could be found for UUID: %s", uuid); + return eObject; + } + + @Override + public void registerEObject(String uuid, EObject eObject) throws IllegalStateException { + checkState(uuid != null, "uuid must not be null"); + checkState(eObject != null, "object must not be null"); + if (eObject.eResource() != null && eObject.eResource().getResourceSet() != null) { + checkState(eObject.eResource().getResourceSet() == resourceSet, + "element %s is contained in wrong resource set", eObject); + } + checkState(eObjectToUuid.getOrDefault(eObject, uuid).equals(uuid), + "element %s is already registered for UUID %s", eObject, eObjectToUuid.get(eObject)); + checkState(eObjectToUuid.inverse().getOrDefault(uuid, eObject).equals(eObject), + "UUID %s is already registered for element %s, was trying to register %s", uuid, + eObjectToUuid.inverse().get(uuid), eObject); + if (isReadOnlyEObject(eObject)) { + String expectedUuid = getUuidForReadOnlyEObject(eObject); + checkState(uuid.equals(expectedUuid), "read-only object %s must be registered for UUID %s but was %s", + eObject, expectedUuid, uuid); + return; + } + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Adding UUID " + uuid + " for EObject: " + eObject); + } + eObjectToUuid.put(eObject, uuid); + } + + @Override + public String generateUuid(EObject eObject) { + checkState(!eObject.eIsProxy(), "Cannot generate UUID for proxy object %s", eObject); + if (isReadOnlyEObject(eObject)) { + return getUuidForReadOnlyEObject(eObject); + } + return NON_READONLY_PREFIX + EcoreUtil.generateUUID(); + } + + @Override + public Resource getResource(URI uri) { + return getOrCreateResource(resourceSet, uri); + } + + @Override + public void endTransaction() { + cleanupRemovedElements(); + } + + @Override + public void resolveResources(Map sourceToTargetResourceMapping, + UuidResolver targetUuidResolver) { + checkState(sourceToTargetResourceMapping != null, "source to target resource mapping must not be null"); + checkState(targetUuidResolver != null, "target UUID resolver must not be null"); + if (sourceToTargetResourceMapping.isEmpty()) { + return; + } + sourceToTargetResourceMapping.keySet().forEach(resource -> checkState(resource.getResourceSet() == resourceSet, + "trying to unresolve resource %s from different resource set", resource)); + ResourceSet targetResourceSet = sourceToTargetResourceMapping.values().iterator().next().getResourceSet(); + sourceToTargetResourceMapping.values() + .forEach(resource -> checkState(resource.getResourceSet() == targetResourceSet, + "trying to resolve resource %s from different resource set", resource)); + Map uuidToIdMapping = generateUuidToIdMapping(sourceToTargetResourceMapping.keySet()); + applyUuidToIdMapping(uuidToIdMapping, targetUuidResolver, targetResourceSet, sourceToTargetResourceMapping); + } + + @Override + public void loadFromUri(URI uri) throws IOException { + checkState(eObjectToUuid.isEmpty(), + "trying to load stored UUID resolver configuration but contained already some UUIDs"); + checkState(uri.isFile(), "Loading UUID resolver requires a file uri but was %s", uri); + File file = new File(uri.toFileString()); + if (!file.exists()) { + return; + } + Map uuidToIdMapping = new HashMap<>(); + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + String line = reader.readLine(); + while (line != null) { + String[] components = line.split("\\" + SERIALIZATION_SEPARATOR); + checkState(components.length == 2, "invalid UUID resolver serialization (line %s) found at %s", line, + uri); + uuidToIdMapping.put(components[0], components[1]); + line = reader.readLine(); + } + } + applyUuidToIdMapping(uuidToIdMapping, this, resourceSet, null); + } + + @Override + public void storeAtUri(URI uri) throws IOException { + checkState(uri.isFile(), "Storing UUID resolver requires a file uri but was %s", uri); + Map uuidToIdMapping = generateUuidToIdMapping(null); + File file = new File(uri.toFileString()); + try (FileWriter writer = new FileWriter(file)) { + for (var entry : uuidToIdMapping.entrySet()) { + String uuid = entry.getKey(); + String id = entry.getValue(); + writer.write(uuid + SERIALIZATION_SEPARATOR + id); + writer.write(System.lineSeparator()); + } + } + } + + private String getUuidOrNull(EObject eObject) { + if (isReadOnlyEObject(eObject)) { + return getUuidForReadOnlyEObject(eObject); + } + String uuid = eObjectToUuid.get(eObject); + return uuid; + } + + private String getUuidForReadOnlyEObject(EObject eObject) { + return EcoreUtil.getURI(eObject).toString(); + } + + private EObject getEObjectOrNull(String uuid) { + if (isReadOnlyUuid(uuid)) { + return getEObjectForReadOnlyUuid(uuid); + } + return eObjectToUuid.inverse().get(uuid); + } + + private EObject getEObjectForReadOnlyUuid(String uuid) { + URI uri = URI.createURI(uuid); + return uri.hasFragment() ? resourceSet.getEObject(uri, true) : null; + } + + private boolean isReadOnlyEObject(EObject eObject) { + return eObject.eResource() != null && eObject.eResource().getURI() != null + && isReadOnlyUri(eObject.eResource().getURI()); + } + + private boolean isReadOnlyUri(URI uri) { + return isPathmap(uri) || uri.isArchive(); + } + + private boolean isReadOnlyUuid(String uuid) { + return !uuid.startsWith(NON_READONLY_PREFIX); + } + + private void cleanupRemovedElements() { + var iterator = eObjectToUuid.keySet().iterator(); + while (iterator.hasNext()) { + EObject object = iterator.next(); + if (object.eResource() == null || object.eResource().getResourceSet() == null) { + iterator.remove(); + } + } + } + + /** + * Creates a mapping from UUIDs to hierarchical IDs. If a + * resourcesFilter is provided, only {@link EObject}s contained in + * those resources are considered for creating the mapping. If + * resourcesFilter is null, all {@link EObject}s + * registered in the resolver are considered. + * + * @param resourcesFilter is the filter for the resources to consider, or + * null if all resources shall be considered. + */ + private Map generateUuidToIdMapping(Collection resourcesFilter) { + IdResolver idUnresolver = IdResolver.create(resourceSet); + Map uuidToIdMapping = new HashMap<>(); + for (var entry : eObjectToUuid.entrySet()) { + EObject eObject = entry.getKey(); + checkState(eObject.eResource() != null && eObject.eResource().getResourceSet() != null, + "trying to unresolve dangling EObject %s", eObject); + if (resourcesFilter != null && !resourcesFilter.contains(eObject.eResource())) { + continue; + } + String id = idUnresolver.getAndUpdateId(eObject); + String uuid = entry.getValue(); + uuidToIdMapping.put(uuid, id); + } + return uuidToIdMapping; + } + + /** + * Apply the given UUID to hierarchical ID mapping to the given + * {@link UuidResolver} by resolving each ID in the given resolver's resource + * set and registering the obtained object with the associated UUID. If + * sourceToTargetResourceMapping is not null, the + * resources of the obtained and own elements corresponding to a UUID must be a + * pair in the given mapping. + * + * @param uuidToIdMapping is the UUID to hierarchical ID mapping. + * @param targetUuidResolver is the {@link UuidResolver} to register + * the given UUIDs in. + * @param targetResourceSet is the resource set of the given uuid + * resolver. + * @param sourceToTargetResourceMapping is the mapping from own resources to the + * given resolver's resources, or + * null if the mapping shall + * not be validated. + */ + private void applyUuidToIdMapping(Map uuidToIdMapping, UuidResolver targetUuidResolver, + ResourceSet targetResourceSet, Map sourceToTargetResourceMapping) + throws IllegalStateException { + var idResolver = IdResolver.create(targetResourceSet); + for (var entry : uuidToIdMapping.entrySet()) { + String uuid = entry.getKey(); + String id = entry.getValue(); + EObject targetEObject = idResolver.getEObject(id); + checkState(targetEObject != null, "could not find object corresponding to %s in resource set %s", uuid, + targetResourceSet); + if (sourceToTargetResourceMapping != null) { + EObject sourceEObject = eObjectToUuid.inverse().get(uuid); + checkState(targetEObject.eResource() == sourceToTargetResourceMapping.get(sourceEObject.eResource()), + "resolved object %s to element %s which is contained in wrong resource", targetEObject, + sourceEObject); + } + targetUuidResolver.registerEObject(uuid, targetEObject); + } + } +} diff --git a/bundles/tools.vitruv.change.composite/src/tools/vitruv/change/composite/description/VitruviusChange.java b/bundles/tools.vitruv.change.composite/src/tools/vitruv/change/composite/description/VitruviusChange.java new file mode 100644 index 00000000..c3faf58d --- /dev/null +++ b/bundles/tools.vitruv.change.composite/src/tools/vitruv/change/composite/description/VitruviusChange.java @@ -0,0 +1,96 @@ +package tools.vitruv.change.composite.description; + +import java.util.List; +import java.util.Set; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.Resource; + +import tools.vitruv.change.atomic.EChange; +import tools.vitruv.change.atomic.id.IdResolver; +import tools.vitruv.change.atomic.uuid.UuidResolver; +import tools.vitruv.change.composite.MetamodelDescriptor; +import tools.vitruv.change.interaction.UserInteractionBase; + +/** + * Base interface for all kinds of changes in Vitruvius. + */ +public interface VitruviusChange { + /** + * Returns whether the change contains any concrete change or consists only of + * composite ones. + */ + public boolean containsConcreteChange(); + + /** + * Returns the {@link EChange}s that describe this change. Requires the change + * to be prepared so that the original change information is transformed into + * {@link EChange}s. + */ + public List getEChanges(); + + /** + * Resolves the change and applies it forward so that the model is in the state + * after the change afterwards. It has to be ensured that the model is in a + * state the change can be applied to before calling this method and that the + * changes use UUIDs. Returns the resolved change. + * + * @throws IllegalStateException if the change cannot be resolved or is already + * resolved. + */ + public VitruviusChange resolveAndApply(UuidResolver uuidResolver); + + /** + * Resolves the change and applies it forward so that the model is in the state + * after the change afterwards. It has to be ensured that the model is in a + * state the change can be applied to before calling this method and that the + * changes use hierarchical IDs. Returns the resolved change. + * + * @throws IllegalStateException if the change cannot be resolved or is already + * resolved. + */ + public VitruviusChange resolveAndApply(IdResolver idResolver); + + /** + * Returns an unresolved change, such that all its affected and referenced + * {@link EObjects} are removed. + */ + public VitruviusChange unresolve(); + + /** + * Returns all {@link EObject}s directly affected by this change. This does not + * include referenced elements. + */ + public Set getAffectedEObjects(); + + /** + * Returns all {@link EObject}s affected by this change, including both the + * elements of which an attribute or reference was changes, as well as the + * referenced elements. + */ + public Set getAffectedAndReferencedEObjects(); + + /** + * Returns the {@link URI}s of all {@link Resource}s changed by this change, + * i.e. the resources containing the changed {@link EObject}s. The returned + * {@link Iterable} may be empty if no {@link EObject}s are affected by this + * change or if this change was not resolved yet. + */ + public Set getChangedURIs(); + + /** + * Returns the descriptors for the metamodels of the elements whose instances + * have been modified in this change. These elements are the {@link EObject}s + * returned by {@link getAffectedEObjects}. + */ + public Set getAffectedEObjectsMetamodelDescriptors(); + + /** + * Returns all user interactions performed during application of this change and + * performing consistency preservation. + */ + public Iterable getUserInteractions(); + + public VitruviusChange copy(); +} diff --git a/bundles/tools.vitruv.change.composite/src/tools/vitruv/change/composite/description/VitruviusChange.xtend b/bundles/tools.vitruv.change.composite/src/tools/vitruv/change/composite/description/VitruviusChange.xtend deleted file mode 100644 index cda95041..00000000 --- a/bundles/tools.vitruv.change.composite/src/tools/vitruv/change/composite/description/VitruviusChange.xtend +++ /dev/null @@ -1,74 +0,0 @@ -package tools.vitruv.change.composite.description - -import java.util.List -import tools.vitruv.change.atomic.EChange -import org.eclipse.emf.ecore.EObject -import tools.vitruv.change.interaction.UserInteractionBase -import org.eclipse.emf.ecore.resource.Resource -import java.util.Set -import org.eclipse.emf.common.util.URI -import org.eclipse.emf.ecore.resource.ResourceSet -import tools.vitruv.change.composite.MetamodelDescriptor - -/** - * Base interface for all kinds of changes in Vitruvius. - * - * @author Heiko Klare - */ -interface VitruviusChange { - /** - * Returns whether the change contains any concrete change or consists only of composite ones. - */ - def boolean containsConcreteChange() - - /** - * Returns the {@link EChange}s that describe this change. Requires the change to be prepared so - * that the original change information is transformed into {@link EChange}s. - */ - def List getEChanges() - - /** - * Resolves the change and applies it forward so that the model is in the state after the change afterwards. - * It has to be ensured that the model is in a state the change can be applied to before calling this method. - * Returns the resolved change. - * - * @throws IllegalStateException if the change cannot be resolved or is already resolved. - */ - def VitruviusChange resolveAndApply(ResourceSet resourceSet) - - /** - * Returns an unresolved change, such that all its affected and referenced {@link EObjects} are removed. - */ - def VitruviusChange unresolve() - - /** - * Returns all {@link EObject}s directly affected by this change. This does not include referenced elements. - */ - def Set getAffectedEObjects() - - /** - * Returns all {@link EObject}s affected by this change, including both the elements of which an attribute or - * reference was changes, as well as the referenced elements. - */ - def Set getAffectedAndReferencedEObjects() - - /** - * Returns the {@link URI}s of all {@link Resource}s changed by this change, i.e. the resources containing the - * changed {@link EObject}s. The returned {@link Iterable} may be empty if no {@link EObject}s are affected by this - * change or if this change was not resolved yet. - */ - def Set getChangedURIs() - - /** - * Returns the descriptors for the metamodels of the elements whose instances have been modified in this change. - * These elements are the {@link EObject}s returned by {@link getAffectedEObjects}. - */ - def Set getAffectedEObjectsMetamodelDescriptors() - - /** - * Returns all user interactions performed during application of this change and performing consistency preservation. - */ - def Iterable getUserInteractions() - - def VitruviusChange copy() -} diff --git a/bundles/tools.vitruv.change.composite/src/tools/vitruv/change/composite/description/impl/CompositeContainerChangeImpl.xtend b/bundles/tools.vitruv.change.composite/src/tools/vitruv/change/composite/description/impl/CompositeContainerChangeImpl.xtend index 313e7954..40157b7e 100644 --- a/bundles/tools.vitruv.change.composite/src/tools/vitruv/change/composite/description/impl/CompositeContainerChangeImpl.xtend +++ b/bundles/tools.vitruv.change.composite/src/tools/vitruv/change/composite/description/impl/CompositeContainerChangeImpl.xtend @@ -1,10 +1,12 @@ package tools.vitruv.change.composite.description.impl +import java.util.List +import tools.vitruv.change.atomic.uuid.UuidResolver import tools.vitruv.change.composite.description.CompositeContainerChange import tools.vitruv.change.composite.description.VitruviusChange + import static extension edu.kit.ipd.sdq.commons.util.java.lang.IterableUtil.* -import java.util.List -import org.eclipse.emf.ecore.resource.ResourceSet +import tools.vitruv.change.atomic.id.IdResolver class CompositeContainerChangeImpl extends AbstractCompositeChangeImpl implements CompositeContainerChange { new(List changes) { @@ -15,8 +17,12 @@ class CompositeContainerChangeImpl extends AbstractCompositeChangeImpl eChanges @@ -83,8 +86,16 @@ class TransactionalChangeImpl implements TransactionalChange { return MetamodelDescriptor.of(changedPackages) } - override resolveAndApply(ResourceSet resourceSet) { - val idResolver = IdResolver.create(resourceSet) + override resolveAndApply(UuidResolver uuidResolver) { + val resolvedChanges = eChanges.mapFixed[ + val resolvedChange = resolveBefore(uuidResolver) + resolvedChange.applyForward(uuidResolver) + resolvedChange + ] + return new TransactionalChangeImpl(resolvedChanges) + } + + override resolveAndApply(IdResolver idResolver) { val resolvedChanges = eChanges.mapFixed[ val resolvedChange = resolveBefore(idResolver) resolvedChange.applyForward(idResolver) diff --git a/bundles/tools.vitruv.change.composite/src/tools/vitruv/change/composite/recording/ChangeRecorder.xtend b/bundles/tools.vitruv.change.composite/src/tools/vitruv/change/composite/recording/ChangeRecorder.xtend index eac8fad5..3cf84129 100644 --- a/bundles/tools.vitruv.change.composite/src/tools/vitruv/change/composite/recording/ChangeRecorder.xtend +++ b/bundles/tools.vitruv.change.composite/src/tools/vitruv/change/composite/recording/ChangeRecorder.xtend @@ -15,12 +15,10 @@ import org.eclipse.emf.ecore.resource.Resource import org.eclipse.emf.ecore.resource.ResourceSet import org.eclipse.xtend.lib.annotations.FinalFieldsConstructor import tools.vitruv.change.atomic.EChange -import tools.vitruv.change.atomic.EChangeIdManager import tools.vitruv.change.atomic.eobject.DeleteEObject import tools.vitruv.change.atomic.eobject.EObjectAddedEChange import tools.vitruv.change.atomic.eobject.EObjectSubtractedEChange import tools.vitruv.change.atomic.feature.reference.UpdateReferenceEChange -import tools.vitruv.change.atomic.id.IdResolver import tools.vitruv.change.composite.description.TransactionalChange import tools.vitruv.change.composite.description.VitruviusChangeFactory @@ -33,8 +31,6 @@ import static org.eclipse.emf.ecore.resource.ResourceSet.* import static extension org.eclipse.emf.ecore.util.EcoreUtil.* import static extension tools.vitruv.change.atomic.EChangeUtil.* -import static extension tools.vitruv.change.atomic.resolve.EChangeResolverAndApplicator.applyBackward -import static extension tools.vitruv.change.atomic.resolve.EChangeResolverAndApplicator.applyForward /** * Records changes to model elements as a {@link TransactionalChange}. @@ -58,16 +54,12 @@ class ChangeRecorder implements AutoCloseable { // closed: null List resultChanges = emptyList val NotificationToEChangeConverter converter - val IdResolver idResolver - val EChangeIdManager eChangeIdManager val Set existingObjects = new HashSet val Set toDesinfect = new HashSet val ResourceSet resourceSet new(ResourceSet resourceSet) { this.resourceSet = resourceSet - this.idResolver = IdResolver.create(resourceSet) - this.eChangeIdManager = new EChangeIdManager(idResolver) this.converter = new NotificationToEChangeConverter([ affectedObject, addedObject | isCreateChange(affectedObject, addedObject) ]) @@ -83,7 +75,7 @@ class ChangeRecorder implements AutoCloseable { // it can be potentially a reference to a third party model, for which no create shall be instantiated create = create && (addedObject.eResource === null || affectedObject === null || addedObject.eResource == affectedObject.eResource) - if(create) existingObjects += addedObject + if (create) existingObjects += addedObject return create; } @@ -97,11 +89,11 @@ class ChangeRecorder implements AutoCloseable { checkNotDisposed() checkNotNull(notifier, "notifier") checkArgument(notifier.isInOurResourceSet, - "cannot record changes in a different resource set than that of our ID resolver!") + "cannot record changes in a different resource set!") if (rootObjects += notifier) { notifier.recursively [ - if(it instanceof EObject) existingObjects.add(it) + if (it instanceof EObject) existingObjects += it addAdapter() ] } @@ -153,22 +145,10 @@ class ChangeRecorder implements AutoCloseable { checkNotDisposed() checkState(isRecording, "This recorder is not recording") isRecording = false - resultChanges = List.copyOf(resultChanges.postprocessRemovals().assignIds()) - idResolver.endTransaction() + resultChanges = List.copyOf(resultChanges.postprocessRemovals()) return getChange() } - def private List assignIds(List changes) { - changes.toList.reverseView.forEach[applyBackward] - changes.forEach[assignIds] - changes - } - - def private void assignIds(EChange change) { - eChangeIdManager.setOrGenerateIds(change) - change.applyForward(idResolver) - } - /** * Creates {@link DeleteEObject} changes for every element implicitly deleted in the change * sequence and all of its contained elements. The delete changes are appended at the end @@ -331,14 +311,14 @@ class ChangeRecorder implements AutoCloseable { converter.convert(new NotificationInfo(notification)) } if (!changes.isEmpty) { - // Register any added object as existing, even if we are not recording - changes.forEach [ - if (it instanceof EObjectAddedEChange) { - existingObjects += newValue - if(it instanceof UpdateReferenceEChange) existingObjects += affectedEObject - } - ] - } + // Register any added object as existing, even if we are not recording + changes.forEach [ + if (it instanceof EObjectAddedEChange) { + existingObjects += newValue + if(it instanceof UpdateReferenceEChange) existingObjects += affectedEObject + } + ] + } return changes } diff --git a/bundles/tools.vitruv.change.correspondence/src/tools/vitruv/change/correspondence/model/PersistableCorrespondenceModelImpl.java b/bundles/tools.vitruv.change.correspondence/src/tools/vitruv/change/correspondence/model/PersistableCorrespondenceModelImpl.java index c96b0fba..c56f2cb2 100644 --- a/bundles/tools.vitruv.change.correspondence/src/tools/vitruv/change/correspondence/model/PersistableCorrespondenceModelImpl.java +++ b/bundles/tools.vitruv.change.correspondence/src/tools/vitruv/change/correspondence/model/PersistableCorrespondenceModelImpl.java @@ -133,7 +133,7 @@ public Set removeCorrespondencesBetween(Class c private Set filterCorrespondenceTypeAndTag(Set original, Class filteredType, String expectedTag) { return original.stream().filter(filteredType::isInstance).map(filteredType::cast) - .filter(correspondence -> expectedTag == null || correspondence.getTag().equals(expectedTag)).collect(Collectors.toSet()); + .filter(correspondence -> expectedTag == null || expectedTag.equals(correspondence.getTag())).collect(Collectors.toSet()); } private Set getCorrespondences(List eObjects) { diff --git a/bundles/tools.vitruv.change.propagation/src/tools/vitruv/change/propagation/ChangeRecordingModelRepository.java b/bundles/tools.vitruv.change.propagation/src/tools/vitruv/change/propagation/ChangeRecordingModelRepository.java index 91cf7d0f..7a3380d8 100644 --- a/bundles/tools.vitruv.change.propagation/src/tools/vitruv/change/propagation/ChangeRecordingModelRepository.java +++ b/bundles/tools.vitruv.change.propagation/src/tools/vitruv/change/propagation/ChangeRecordingModelRepository.java @@ -1,8 +1,10 @@ package tools.vitruv.change.propagation; +import tools.vitruv.change.atomic.uuid.UuidResolver; import tools.vitruv.change.composite.description.TransactionalChange; import tools.vitruv.change.composite.description.VitruviusChange; import tools.vitruv.change.correspondence.Correspondence; +import tools.vitruv.change.correspondence.model.CorrespondenceModel; import tools.vitruv.change.correspondence.view.EditableCorrespondenceModelView; public interface ChangeRecordingModelRepository extends ResourceAccess, AutoCloseable { @@ -13,6 +15,11 @@ public interface ChangeRecordingModelRepository extends ResourceAccess, AutoClos * @return the {@link CorrespondenceModel} managed by this repository */ EditableCorrespondenceModelView getCorrespondenceModel(); + + /** + * Returns the {@link UuidResolver} associated with all model resources in this repository. + */ + UuidResolver getUuidResolver(); /** * Applies the given change to this model repository. It resolves the change diff --git a/bundles/tools.vitruv.change.propagation/src/tools/vitruv/change/propagation/impl/DefaultChangeRecordingModelRepository.java b/bundles/tools.vitruv.change.propagation/src/tools/vitruv/change/propagation/impl/DefaultChangeRecordingModelRepository.java index 286cb358..e05c453e 100644 --- a/bundles/tools.vitruv.change.propagation/src/tools/vitruv/change/propagation/impl/DefaultChangeRecordingModelRepository.java +++ b/bundles/tools.vitruv.change.propagation/src/tools/vitruv/change/propagation/impl/DefaultChangeRecordingModelRepository.java @@ -18,6 +18,8 @@ import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; +import tools.vitruv.change.atomic.EChangeUuidManager; +import tools.vitruv.change.atomic.uuid.UuidResolver; import tools.vitruv.change.composite.description.TransactionalChange; import tools.vitruv.change.composite.description.VitruviusChange; import tools.vitruv.change.composite.recording.ChangeRecorder; @@ -42,6 +44,7 @@ public class DefaultChangeRecordingModelRepository implements PersistableChangeR private final PersistableCorrespondenceModel correspondenceModel; private final ChangeRecorder changeRecorder; private final Path consistencyMetadataFolder; + private final UuidResolver uuidResolver; private boolean isLoading = false; @@ -56,6 +59,7 @@ public class DefaultChangeRecordingModelRepository implements PersistableChangeR public DefaultChangeRecordingModelRepository(URI correspondencesURI, Path consistencyMetadataFolder) { this.consistencyMetadataFolder = consistencyMetadataFolder; this.modelsResourceSet = withGlobalFactories(new ResourceSetImpl()); + this.uuidResolver = UuidResolver.create(modelsResourceSet); this.correspondenceModel = createPersistableCorrespondenceModel(correspondencesURI); this.modelsResourceSet.eAdapters().add(new ResourceRegistrationAdapter((resource) -> { if (!isLoading) { @@ -155,12 +159,14 @@ public Iterable recordChanges(Runnable changeApplicator) { changeApplicator.run(); LOGGER.debug("End recording changes"); changeRecorder.endRecording(); - return List.of(changeRecorder.getChange()); + TransactionalChange recordedChange = changeRecorder.getChange(); + EChangeUuidManager.setOrGenerateIds(recordedChange.getEChanges(), uuidResolver); + return List.of(recordedChange); } @Override public VitruviusChange applyChange(VitruviusChange change) { - return change.resolveAndApply(modelsResourceSet); + return change.resolveAndApply(uuidResolver); } @Override @@ -168,7 +174,13 @@ public void close() throws Exception { changeRecorder.close(); modelsResourceSet.getResources().stream().forEach((resource) -> resource.unload()); modelsResourceSet.getResources().clear(); + uuidResolver.endTransaction(); correspondenceModel.close(); } + @Override + public UuidResolver getUuidResolver() { + return uuidResolver; + } + } diff --git a/bundles/tools.vitruv.change.propagation/src/tools/vitruv/change/propagation/impl/DefaultChangeableModelRepository.java b/bundles/tools.vitruv.change.propagation/src/tools/vitruv/change/propagation/impl/DefaultChangeableModelRepository.java index c6f993a1..f252361a 100644 --- a/bundles/tools.vitruv.change.propagation/src/tools/vitruv/change/propagation/impl/DefaultChangeableModelRepository.java +++ b/bundles/tools.vitruv.change.propagation/src/tools/vitruv/change/propagation/impl/DefaultChangeableModelRepository.java @@ -1,5 +1,7 @@ package tools.vitruv.change.propagation.impl; +import static com.google.common.base.Preconditions.checkArgument; + import java.util.HashSet; import java.util.List; import java.util.Set; @@ -13,7 +15,6 @@ import tools.vitruv.change.interaction.InternalUserInteractor; import tools.vitruv.change.propagation.ChangePropagationSpecificationProvider; import tools.vitruv.change.propagation.PersistableChangeRecordingModelRepository; -import static com.google.common.base.Preconditions.checkArgument; /** * A default implementation of a {@link ChangeableModelRepository} that requires a diff --git a/bundles/tools.vitruv.testutils/src/tools/vitruv/testutils/TestModelRepositoryFactory.java b/bundles/tools.vitruv.testutils/src/tools/vitruv/testutils/TestModelRepositoryFactory.java index 19c0b309..24e4314c 100644 --- a/bundles/tools.vitruv.testutils/src/tools/vitruv/testutils/TestModelRepositoryFactory.java +++ b/bundles/tools.vitruv.testutils/src/tools/vitruv/testutils/TestModelRepositoryFactory.java @@ -25,11 +25,24 @@ public class TestModelRepositoryFactory { */ public static ChangeableModelRepository createTestChangeableModelRepository( ChangePropagationSpecificationProvider changePropagationSpecificationProvider, TestUserInteraction userInteraction) throws IOException { - InternalUserInteractor userInteractor = UserInteractionFactory.instance - .createUserInteractor(new TestUserInteraction.ResultProvider(userInteraction)); PersistableChangeRecordingModelRepository recordingModelRepository = new DefaultChangeRecordingModelRepository(null, Files.createTempDirectory(null)); - ChangeableModelRepository changeableModelRepository = new DefaultChangeableModelRepository(recordingModelRepository, + return createTestChangeableModelRepository(recordingModelRepository, changePropagationSpecificationProvider, userInteraction); + } + + /** + * Creates a {@link ChangeableModelRepository} for the given change propagation specification provider, user + * interaction, and persistable change recording model repository. + * @param modelRepository manages where files are stored. + * @param changePropagationSpecificationProvider provides the {@link ChangePropagationSpecifications} to use in the + * repository + * @param userInteraction the {@link TestUserInteraction} to use for interactions during change propagation + * @return the test model repository + */ + public static ChangeableModelRepository createTestChangeableModelRepository(PersistableChangeRecordingModelRepository modelRepository, ChangePropagationSpecificationProvider changePropagationSpecificationProvider, TestUserInteraction userInteraction) { + InternalUserInteractor userInteractor = UserInteractionFactory.instance + .createUserInteractor(new TestUserInteraction.ResultProvider(userInteraction)); + ChangeableModelRepository changeableModelRepository = new DefaultChangeableModelRepository(modelRepository, changePropagationSpecificationProvider, userInteractor); return changeableModelRepository; } diff --git a/bundles/tools.vitruv.testutils/src/tools/vitruv/testutils/views/BasicTestView.xtend b/bundles/tools.vitruv.testutils/src/tools/vitruv/testutils/views/BasicTestView.xtend index 051a85cd..127bbe05 100644 --- a/bundles/tools.vitruv.testutils/src/tools/vitruv/testutils/views/BasicTestView.xtend +++ b/bundles/tools.vitruv.testutils/src/tools/vitruv/testutils/views/BasicTestView.xtend @@ -11,6 +11,7 @@ import org.eclipse.emf.ecore.resource.ResourceSet import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl import org.eclipse.xtend.lib.annotations.Accessors import tools.vitruv.change.composite.description.PropagatedChange +import tools.vitruv.testutils.TestUserInteraction import static com.google.common.base.Preconditions.checkArgument import static edu.kit.ipd.sdq.commons.util.org.eclipse.emf.common.util.URIUtil.createFileURI @@ -28,7 +29,7 @@ class BasicTestView implements TestView { val Path persistenceDirectory val ResourceSet resourceSet @Accessors - val tools.vitruv.testutils.TestUserInteraction userInteraction + val TestUserInteraction userInteraction val UriMode uriMode /** @@ -36,7 +37,7 @@ class BasicTestView implements TestView { * provided {@code persistenceDirectory}, and use the provided {@code uriMode}. */ new(Path persistenceDirectory, UriMode uriMode) { - this(persistenceDirectory, new tools.vitruv.testutils.TestUserInteraction(), uriMode) + this(persistenceDirectory, new TestUserInteraction(), uriMode) } /** @@ -46,7 +47,7 @@ class BasicTestView implements TestView { */ new( Path persistenceDirectory, - tools.vitruv.testutils.TestUserInteraction userInteraction, + TestUserInteraction userInteraction, UriMode uriMode ) { this( @@ -65,7 +66,7 @@ class BasicTestView implements TestView { new( Path persistenceDirectory, ResourceSet resourceSet, - tools.vitruv.testutils.TestUserInteraction userInteraction, + TestUserInteraction userInteraction, UriMode uriMode ) { this.persistenceDirectory = persistenceDirectory diff --git a/bundles/tools.vitruv.testutils/src/tools/vitruv/testutils/views/ChangePublishingTestView.xtend b/bundles/tools.vitruv.testutils/src/tools/vitruv/testutils/views/ChangePublishingTestView.xtend index 01966c54..8aea81f4 100644 --- a/bundles/tools.vitruv.testutils/src/tools/vitruv/testutils/views/ChangePublishingTestView.xtend +++ b/bundles/tools.vitruv.testutils/src/tools/vitruv/testutils/views/ChangePublishingTestView.xtend @@ -1,71 +1,105 @@ package tools.vitruv.testutils.views +import java.nio.file.Files import java.nio.file.Path import java.util.ArrayList -import java.util.LinkedList import java.util.List +import java.util.function.BiConsumer import java.util.function.Consumer +import java.util.function.Function import org.eclipse.emf.common.notify.Notifier +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.resource.impl.ResourceSetImpl import org.eclipse.xtend.lib.annotations.Delegate +import tools.vitruv.change.atomic.EChangeUuidManager +import tools.vitruv.change.atomic.uuid.UuidResolver import tools.vitruv.change.composite.description.PropagatedChange import tools.vitruv.change.composite.description.TransactionalChange -import tools.vitruv.change.composite.description.VitruviusChange +import tools.vitruv.change.composite.propagation.ChangeableModelRepository import tools.vitruv.change.composite.recording.ChangeRecorder +import tools.vitruv.change.propagation.ChangePropagationSpecification +import tools.vitruv.change.propagation.ChangePropagationSpecificationRepository +import tools.vitruv.change.propagation.impl.DefaultChangeRecordingModelRepository +import tools.vitruv.change.propagation.impl.DefaultChangeableModelRepository +import tools.vitruv.testutils.TestUserInteraction import static com.google.common.base.Preconditions.checkArgument import static com.google.common.base.Preconditions.checkState +import static tools.vitruv.testutils.TestModelRepositoryFactory.createTestChangeableModelRepository import static extension edu.kit.ipd.sdq.commons.util.java.lang.IterableUtil.flatMapFixed import static extension edu.kit.ipd.sdq.commons.util.org.eclipse.emf.ecore.resource.ResourceSetUtil.withGlobalFactories -import tools.vitruv.change.composite.propagation.ChangeableModelRepository -import tools.vitruv.change.propagation.ChangePropagationSpecification -import tools.vitruv.testutils.TestUserInteraction -import tools.vitruv.change.propagation.ChangePropagationSpecificationRepository -import static tools.vitruv.testutils.TestModelRepositoryFactory.createTestChangeableModelRepository -import tools.vitruv.change.propagation.impl.DefaultChangeableModelRepository /** * A test view that will record and publish the changes created in it. */ class ChangePublishingTestView implements NonTransactionalTestView { val ResourceSet resourceSet + val UuidResolver uuidResolver @Delegate val TestView delegate val ChangeRecorder changeRecorder - val List<(VitruviusChange)=>List> changeProcessors = new LinkedList() + val ChangeableModelRepository modelRepository + val BiConsumer uuidResolution var disposeViewResourcesAfterPropagation = true /** * Creates a test view that will store its persisted resources in the * provided {@code persistenceDirectory}, allow to program interactions through the provided {@code userInteraction}, * use the provided {@code uriMode}. + * + * @param persistenceDirectory is the directory to store files at. + * @param userInteraction the {@link TestUserInteraction} to use for interactions during change propagation. + * @param uriMode is the URI mode. + * @param changeableModelRepository is the repository responsible for propagating and storing the models. + * @param uuidResolution is a consumer that populates the given view's {@link UuidResolver} with the UUIDs of all elements in the given {@link Resource}. */ new( Path persistenceDirectory, - tools.vitruv.testutils.TestUserInteraction userInteraction, - UriMode uriMode + TestUserInteraction userInteraction, + UriMode uriMode, + ChangeableModelRepository changeableModelRepository, + BiConsumer uuidResolution ) { this.resourceSet = new ResourceSetImpl().withGlobalFactories() + this.uuidResolver = UuidResolver.create(resourceSet) + this.modelRepository = changeableModelRepository this.delegate = new BasicTestView(persistenceDirectory, resourceSet, userInteraction, uriMode) this.changeRecorder = new ChangeRecorder(resourceSet) + this.uuidResolution = uuidResolution changeRecorder.beginRecording() } /** * Creates a test view that will store its persisted resources in the * provided {@code persistenceDirectory}, allow to program interactions through the provided {@code userInteraction}, - * use the provided {@code uriMode} and be connected to the provided {@code virtualModel}. + * use the provided {@code uriMode}. + * + * @param persistenceDirectory is the directory to store files at. + * @param userInteraction the {@link TestUserInteraction} to use for interactions during change propagation. + * @param uriMode is the URI mode. + * @param changeableModelRepository is the repository responsible for propagating and storing the models. + * @param modelUuidResolver is the {@link UuidResolver} associated with the {@code changeableModelRepository}. + * @param modelResourceAt is a function that provides the model resource as stored in the + * {@code changeableModelRepository} for a given URI. */ new( Path persistenceDirectory, - tools.vitruv.testutils.TestUserInteraction userInteraction, + TestUserInteraction userInteraction, UriMode uriMode, - ChangeableModelRepository changeableModelRepository + ChangeableModelRepository changeableModelRepository, + UuidResolver modelUuidResolver, + Function modelResourceAt ) { - this(persistenceDirectory, userInteraction, uriMode) - registerChangeProcessor [change|changeableModelRepository.propagateChange(change)] + this(persistenceDirectory, userInteraction, uriMode, changeableModelRepository) [ viewResource, viewUuidResolver | + val modelResource = modelResourceAt.apply(viewResource.URI) + if (modelResource !== null) { + modelUuidResolver.resolveResource(modelResource, viewResource, viewUuidResolver) + } + ] } override close() { @@ -106,22 +140,37 @@ class ChangePublishingTestView implements NonTransactionalTestView { } def private propagateChanges(TransactionalChange change) { - val propagationResult = changeProcessors.flatMapFixed[apply(change)] + EChangeUuidManager.setOrGenerateIds(change.EChanges, uuidResolver) + val propagationResult = modelRepository.propagateChange(change) if (disposeViewResourcesAfterPropagation) { disposeViewResources() } return propagationResult } - override disposeViewResources() { - resourceSet.resources.clear() + override Resource resourceAt(URI modelUri) { + val resource = delegate.resourceAt(modelUri) + uuidResolution.accept(resource, uuidResolver) + return resource } - /** - * Registers the provided {@code processor} to be used when this view publishes changes. - */ - def registerChangeProcessor((VitruviusChange)=>List processor) { - changeProcessors += processor + override Resource resourceAt(Path viewRelativePath) { + resourceAt(viewRelativePath.uri) + } + + override T from(Class clazz, URI modelUri) { + val resource = resourceSet.getResource(modelUri, true) + uuidResolution.accept(resource, uuidResolver) + return clazz.from(resource) + } + + override T from(Class clazz, Path viewRelativePath) { + clazz.from(viewRelativePath.uri) + } + + override disposeViewResources() { + resourceSet.resources.clear() + uuidResolver.endTransaction() } override T startRecordingChanges(T notifier) { @@ -145,7 +194,7 @@ class ChangePublishingTestView implements NonTransactionalTestView { def static List operator_plus(List a, List b) { return if (a.isEmpty) { b - } else if (b.isEmpty){ + } else if (b.isEmpty) { a } else { val result = new ArrayList(a.size + b.size) @@ -168,9 +217,10 @@ class ChangePublishingTestView implements NonTransactionalTestView { val userInteraction = new TestUserInteraction() val changePropagationSpecificationProvider = new ChangePropagationSpecificationRepository( changePropagationSpecifications) - val changeableModelRepository = createTestChangeableModelRepository(changePropagationSpecificationProvider, - userInteraction) + val modelRepository = new DefaultChangeRecordingModelRepository(null, Files.createTempDirectory(null)); + val changeableModelRepository = createTestChangeableModelRepository(modelRepository, + changePropagationSpecificationProvider, userInteraction) return new ChangePublishingTestView(persistenceDirectory, userInteraction, UriMode.FILE_URIS, - changeableModelRepository) + changeableModelRepository, modelRepository.uuidResolver) [ modelRepository.getModelResource(it) ] } } diff --git a/bundles/tools.vitruv.testutils/src/tools/vitruv/testutils/views/NonTransactionalTestView.xtend b/bundles/tools.vitruv.testutils/src/tools/vitruv/testutils/views/NonTransactionalTestView.xtend index 8695f56d..bbc65dc3 100644 --- a/bundles/tools.vitruv.testutils/src/tools/vitruv/testutils/views/NonTransactionalTestView.xtend +++ b/bundles/tools.vitruv.testutils/src/tools/vitruv/testutils/views/NonTransactionalTestView.xtend @@ -1,7 +1,7 @@ package tools.vitruv.testutils.views -import org.eclipse.emf.common.notify.Notifier import java.util.List +import org.eclipse.emf.common.notify.Notifier import tools.vitruv.change.composite.description.PropagatedChange /** diff --git a/bundles/tools.vitruv.testutils/src/tools/vitruv/testutils/views/TestView.xtend b/bundles/tools.vitruv.testutils/src/tools/vitruv/testutils/views/TestView.xtend index 509ed8e0..6b27cfe9 100644 --- a/bundles/tools.vitruv.testutils/src/tools/vitruv/testutils/views/TestView.xtend +++ b/bundles/tools.vitruv.testutils/src/tools/vitruv/testutils/views/TestView.xtend @@ -8,6 +8,7 @@ import org.eclipse.emf.common.util.URI import org.eclipse.emf.ecore.EObject import org.eclipse.emf.ecore.resource.Resource import tools.vitruv.change.composite.description.PropagatedChange +import tools.vitruv.testutils.TestUserInteraction import static com.google.common.base.Preconditions.checkState @@ -103,6 +104,6 @@ interface TestView extends AutoCloseable { /** * @return the user interaction that can be used to program user interactions for this view. */ - def tools.vitruv.testutils.TestUserInteraction getUserInteraction() + def TestUserInteraction getUserInteraction() } diff --git a/tests/tools.vitruv.change.atomic.tests/META-INF/MANIFEST.MF b/tests/tools.vitruv.change.atomic.tests/META-INF/MANIFEST.MF index 39e89e9e..d8218407 100644 --- a/tests/tools.vitruv.change.atomic.tests/META-INF/MANIFEST.MF +++ b/tests/tools.vitruv.change.atomic.tests/META-INF/MANIFEST.MF @@ -10,7 +10,9 @@ Fragment-Host: tools.vitruv.change.atomic Import-Package: org.junit.jupiter.api, org.junit.jupiter.api.extension, org.junit.jupiter.api.function, - org.junit.jupiter.api.io + org.junit.jupiter.api.io, + org.junit.jupiter.params, + org.junit.jupiter.params.provider Require-Bundle: org.eclipse.xtend.lib, org.hamcrest.core, tools.vitruv.testutils, diff --git a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/EChangeTest.xtend b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/EChangeTest.xtend index 5a8c93e7..62cd7c12 100644 --- a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/EChangeTest.xtend +++ b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/EChangeTest.xtend @@ -1,25 +1,25 @@ package tools.vitruv.change.atomic import allElementTypes.Root +import java.nio.file.Path +import java.util.List import org.eclipse.emf.ecore.resource.Resource import org.eclipse.emf.ecore.resource.ResourceSet import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl -import tools.vitruv.change.atomic.TypeInferringAtomicEChangeFactory -import tools.vitruv.change.atomic.TypeInferringCompoundEChangeFactory -import java.util.List -import tools.vitruv.change.atomic.EChange +import org.eclipse.xtend.lib.annotations.Accessors import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.io.TempDir +import tools.vitruv.change.atomic.util.EChangeAssertHelper +import tools.vitruv.change.atomic.uuid.UuidResolver + import static org.junit.jupiter.api.Assertions.assertFalse import static org.junit.jupiter.api.Assertions.assertTrue -import static extension edu.kit.ipd.sdq.commons.util.org.eclipse.emf.ecore.resource.ResourceSetUtil.withGlobalFactories -import org.eclipse.xtend.lib.annotations.Accessors import static tools.vitruv.testutils.metamodels.AllElementTypesCreators.* -import java.nio.file.Path -import org.junit.jupiter.api.io.TempDir + import static extension edu.kit.ipd.sdq.commons.util.org.eclipse.emf.common.util.URIUtil.createFileURI -import static extension tools.vitruv.change.atomic.resolve.EChangeResolverAndApplicator.* -import tools.vitruv.change.atomic.util.EChangeAssertHelper -import tools.vitruv.change.atomic.id.IdResolver +import static extension edu.kit.ipd.sdq.commons.util.org.eclipse.emf.ecore.resource.ResourceSetUtil.withGlobalFactories +import org.eclipse.emf.ecore.EObject +import static extension tools.vitruv.change.atomic.resolve.EChangeUuidResolverAndApplicator.* /** * Default class for testing EChange changes. @@ -35,7 +35,8 @@ abstract class EChangeTest { @Accessors(PROTECTED_GETTER) var Resource resource var ResourceSet resourceSet - var IdResolver idResolver + @Accessors(PROTECTED_GETTER) + var UuidResolver uuidResolver @Accessors(PROTECTED_GETTER) var TypeInferringAtomicEChangeFactory atomicFactory @@ -58,17 +59,17 @@ abstract class EChangeTest { // Create model resourceSet = new ResourceSetImpl().withGlobalFactories + uuidResolver = UuidResolver.create(resourceSet) resource = resourceSet.createResource(fileUri) - rootObject = aet.Root + rootObject = aet.Root.withUuid resource.contents += rootObject resource.save(null) // Factories for creating changes - idResolver = IdResolver.create(resourceSet) - atomicFactory = new TypeInferringUnresolvingAtomicEChangeFactory(idResolver) - compoundFactory = new TypeInferringUnresolvingCompoundEChangeFactory(idResolver) - helper = new EChangeAssertHelper(idResolver) + atomicFactory = new TypeInferringUnresolvingAtomicEChangeFactory(uuidResolver) + compoundFactory = new TypeInferringUnresolvingCompoundEChangeFactory(uuidResolver) + helper = new EChangeAssertHelper(uuidResolver) } protected def final getResourceContent() { @@ -98,7 +99,7 @@ abstract class EChangeTest { } def protected EChange resolveBefore(EChange change) { - return change.resolveBefore(idResolver) + return change.resolveBefore(uuidResolver) } def protected List resolveBefore(List changes) { @@ -109,7 +110,12 @@ abstract class EChangeTest { def protected void applyBackward(List changes) { assertIsResolved(changes) - changes.reverseView.forEach[applyBackward] + changes.reverseView.forEach[applyBackward(uuidResolver)] + } + + def protected void applyBackward(EChange change) { + assertIsResolved(change) + change.assertApplyForward } def protected void applyForward(List changes) { @@ -121,4 +127,9 @@ abstract class EChangeTest { change.assertApplyForward } + def protected withUuid(O eObject) { + uuidResolver.registerEObject(eObject) + return eObject + } + } diff --git a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/TypeInferringUnresolvingAtomicEChangeFactory.xtend b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/TypeInferringUnresolvingAtomicEChangeFactory.xtend index 45889186..81b76098 100644 --- a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/TypeInferringUnresolvingAtomicEChangeFactory.xtend +++ b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/TypeInferringUnresolvingAtomicEChangeFactory.xtend @@ -1,17 +1,14 @@ package tools.vitruv.change.atomic -import org.eclipse.emf.ecore.EObject -import org.eclipse.emf.ecore.resource.Resource - -import static extension tools.vitruv.change.atomic.resolve.EChangeResolverAndApplicator.unresolve +import org.eclipse.emf.common.util.URI import org.eclipse.emf.ecore.EAttribute +import org.eclipse.emf.ecore.EObject import org.eclipse.emf.ecore.EReference import org.eclipse.emf.ecore.EStructuralFeature -import org.eclipse.emf.common.util.URI -import tools.vitruv.change.atomic.TypeInferringAtomicEChangeFactory -import tools.vitruv.change.atomic.EChangeIdManager -import tools.vitruv.change.atomic.EChange -import tools.vitruv.change.atomic.id.IdResolver +import org.eclipse.emf.ecore.resource.Resource +import tools.vitruv.change.atomic.uuid.UuidResolver + +import static extension tools.vitruv.change.atomic.resolve.EChangeUuidResolverAndApplicator.unresolve /** * Factory singleton class for elements of change models. @@ -21,14 +18,14 @@ import tools.vitruv.change.atomic.id.IdResolver * Can be used by any transformation that creates change models. */ package final class TypeInferringUnresolvingAtomicEChangeFactory extends TypeInferringAtomicEChangeFactory { - val EChangeIdManager eChangeIdManager; + val EChangeUuidManager eChangeUuidManager; - new(IdResolver idResolver) { - this.eChangeIdManager = new EChangeIdManager(idResolver); + new(UuidResolver uuidResolver) { + this.eChangeUuidManager = new EChangeUuidManager(uuidResolver); } def private setIds(EChange change) { - eChangeIdManager.setOrGenerateIds(change); + eChangeUuidManager.setOrGenerateIds(change); } override createCreateEObjectChange(A affectedEObject) { diff --git a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/TypeInferringUnresolvingCompoundEChangeFactory.xtend b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/TypeInferringUnresolvingCompoundEChangeFactory.xtend index 64ced7ad..743994e2 100644 --- a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/TypeInferringUnresolvingCompoundEChangeFactory.xtend +++ b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/TypeInferringUnresolvingCompoundEChangeFactory.xtend @@ -1,12 +1,11 @@ package tools.vitruv.change.atomic -import tools.vitruv.change.atomic.TypeInferringCompoundEChangeFactory -import tools.vitruv.change.atomic.id.IdResolver +import tools.vitruv.change.atomic.uuid.UuidResolver package final class TypeInferringUnresolvingCompoundEChangeFactory extends TypeInferringCompoundEChangeFactory { - new(IdResolver idResolver) { - super(new TypeInferringUnresolvingAtomicEChangeFactory(idResolver)); + new(UuidResolver uuidResolver) { + super(new TypeInferringUnresolvingAtomicEChangeFactory(uuidResolver)); } } diff --git a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/compound/CreateAndInsertRootTest.xtend b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/compound/CreateAndInsertRootTest.xtend index cedba676..2aafdb77 100644 --- a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/compound/CreateAndInsertRootTest.xtend +++ b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/compound/CreateAndInsertRootTest.xtend @@ -34,8 +34,8 @@ class CreateAndInsertRootTest extends EChangeTest { */ @BeforeEach def void beforeTest() { - newRootObject = aet.Root - newRootObject2 = aet.Root + newRootObject = aet.Root.withUuid + newRootObject2 = aet.Root.withUuid resourceContent = resource.contents assertIsStateBefore } diff --git a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/compound/CreateAndReplaceAndDeleteNonRootTest.xtend b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/compound/CreateAndReplaceAndDeleteNonRootTest.xtend index 30e0d78a..14f34bbf 100644 --- a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/compound/CreateAndReplaceAndDeleteNonRootTest.xtend +++ b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/compound/CreateAndReplaceAndDeleteNonRootTest.xtend @@ -32,7 +32,7 @@ class CreateAndReplaceAndDeleteNonRootTest extends ReferenceEChangeTest { @BeforeEach def void prepareState() { - oldValue = aet.NonRoot + oldValue = aet.NonRoot.withUuid affectedFeature = AllElementTypesPackage.Literals.ROOT__SINGLE_VALUED_CONTAINMENT_EREFERENCE prepareStateBefore } @@ -70,15 +70,6 @@ class CreateAndReplaceAndDeleteNonRootTest extends ReferenceEChangeTest { // State after assertIsStateAfter - - // Create and resolve 2 - val resolvedChange2 = createUnresolvedChange(newValue2).resolveBefore - - // Apply change 2 forward - resolvedChange2.assertApplyForward - - var NonRoot valueAfterChange2 = affectedEObject.eGet(affectedFeature) as NonRoot - assertThat(valueAfterChange2, equalsDeeply(newValue2)) } /** diff --git a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/compound/CreateAndReplaceNonRootTest.xtend b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/compound/CreateAndReplaceNonRootTest.xtend index e19b8c84..3eb3aede 100644 --- a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/compound/CreateAndReplaceNonRootTest.xtend +++ b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/compound/CreateAndReplaceNonRootTest.xtend @@ -33,7 +33,7 @@ class CreateAndReplaceNonRootTest extends EChangeTest { def void beforeTest() { affectedEObject = rootObject affectedFeature = AllElementTypesPackage.Literals.ROOT__SINGLE_VALUED_CONTAINMENT_EREFERENCE - newNonRootObject = aet.NonRoot + newNonRootObject = aet.NonRoot.withUuid prepareStateBefore } diff --git a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/compound/ExplicitUnsetEReferenceTest.xtend b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/compound/ExplicitUnsetEReferenceTest.xtend index 94ef4a11..addb2dbc 100644 --- a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/compound/ExplicitUnsetEReferenceTest.xtend +++ b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/compound/ExplicitUnsetEReferenceTest.xtend @@ -45,9 +45,9 @@ class ExplicitUnsetEReferenceTest extends EChangeTest { @BeforeEach def void beforeTest() { affectedEObject = rootObject - oldValue = aet.NonRoot - oldValue2 = aet.NonRoot - oldValue3 = aet.NonRoot + oldValue = aet.NonRoot.withUuid + oldValue2 = aet.NonRoot.withUuid + oldValue3 = aet.NonRoot.withUuid } /** diff --git a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/compound/RemoveAndDeleteRootTest.xtend b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/compound/RemoveAndDeleteRootTest.xtend index d82ee841..64349b0c 100644 --- a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/compound/RemoveAndDeleteRootTest.xtend +++ b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/compound/RemoveAndDeleteRootTest.xtend @@ -1,35 +1,30 @@ package tools.vitruv.change.atomic.compound import allElementTypes.Root -import tools.vitruv.change.atomic.EChangeTest - -import static extension tools.vitruv.change.atomic.util.EChangeAssertHelper.* -import tools.vitruv.change.atomic.EChange import java.util.List -import tools.vitruv.change.atomic.root.RemoveRootEObject -import tools.vitruv.change.atomic.eobject.DeleteEObject import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import static org.junit.jupiter.api.Assertions.assertTrue -import static org.junit.jupiter.api.Assertions.assertFalse -import static org.junit.jupiter.api.Assertions.assertEquals -import static org.junit.jupiter.api.Assertions.assertNull -import static tools.vitruv.testutils.metamodels.AllElementTypesCreators.* +import tools.vitruv.change.atomic.EChange +import tools.vitruv.change.atomic.EChangeTest +import tools.vitruv.change.atomic.eobject.DeleteEObject +import tools.vitruv.change.atomic.root.RemoveRootEObject +import tools.vitruv.change.atomic.root.RootEChangeTest + import static org.hamcrest.MatcherAssert.assertThat +import static org.junit.jupiter.api.Assertions.assertEquals +import static org.junit.jupiter.api.Assertions.assertFalse +import static org.junit.jupiter.api.Assertions.assertTrue +import static tools.vitruv.change.atomic.util.EChangeAssertHelper.* import static tools.vitruv.testutils.matchers.ModelMatchers.equalsDeeply /** * Test class for the concrete {@link RemoveAndDeleteRoot} EChange, * which removes a root element from a resource and deletes it. */ -class RemoveAndDeleteRootTest extends EChangeTest { - var Root newRootObject - var Root newRootObject2 - +class RemoveAndDeleteRootTest extends RootEChangeTest { @BeforeEach - def void beforeTest() { - newRootObject = aet.Root - newRootObject2 = aet.Root + override void beforeTest() { + super.beforeTest() prepareStateBefore } @@ -60,7 +55,7 @@ class RemoveAndDeleteRootTest extends EChangeTest { @Test def void resolveToCorrectType() { // Create change - val unresolvedChange = createUnresolvedChange(newRootObject, 0) + val unresolvedChange = createUnresolvedChange(newRootObject, 1) // Resolve val resolvedChange = unresolvedChange.resolveBefore @@ -100,11 +95,11 @@ class RemoveAndDeleteRootTest extends EChangeTest { @Test def void applyBackwardTest() { // Create and resolve and apply change 1 - val resolvedChange = createUnresolvedChange(newRootObject, 0).resolveBefore + val resolvedChange = createUnresolvedChange(newRootObject, 1).resolveBefore resolvedChange.assertApplyForward // Create and resolve and apply change 2 - val resolvedChange2 = createUnresolvedChange(newRootObject2, 0).resolveBefore + val resolvedChange2 = createUnresolvedChange(newRootObject2, 1).resolveBefore resolvedChange2.assertApplyForward // State after diff --git a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/compound/ReplaceAndDeleteNonRootTest.xtend b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/compound/ReplaceAndDeleteNonRootTest.xtend index 435d89c9..5d1be734 100644 --- a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/compound/ReplaceAndDeleteNonRootTest.xtend +++ b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/compound/ReplaceAndDeleteNonRootTest.xtend @@ -34,7 +34,7 @@ class ReplaceAndDeleteNonRootTest extends EChangeTest { def void beforeTest() { affectedEObject = rootObject affectedFeature = AllElementTypesPackage.Literals.ROOT__SINGLE_VALUED_CONTAINMENT_EREFERENCE - oldNonRootObject = aet.NonRoot + oldNonRootObject = aet.NonRoot.withUuid prepareStateBefore } diff --git a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/eobject/EObjectTest.xtend b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/eobject/EObjectTest.xtend index 82280ff3..cdfe6f7a 100644 --- a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/eobject/EObjectTest.xtend +++ b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/eobject/EObjectTest.xtend @@ -21,7 +21,7 @@ abstract class EObjectTest extends EChangeTest { */ @BeforeEach def final void initializeRoots() { - createdObject = aet.Root - createdObject2 = aet.Root + createdObject = aet.Root.withUuid + createdObject2 = aet.Root.withUuid } } diff --git a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/feature/FeatureEChangeTest.xtend b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/feature/FeatureEChangeTest.xtend index d77478d8..eab7af33 100644 --- a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/feature/FeatureEChangeTest.xtend +++ b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/feature/FeatureEChangeTest.xtend @@ -6,23 +6,24 @@ import org.eclipse.emf.ecore.EAttribute import org.eclipse.emf.ecore.EObject import org.eclipse.emf.ecore.EStructuralFeature import org.eclipse.emf.ecore.resource.Resource -import org.eclipse.emf.ecore.util.EcoreUtil -import tools.vitruv.change.atomic.EChange -import tools.vitruv.change.atomic.feature.FeatureEChange -import tools.vitruv.change.atomic.EChangeTest import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl -import tools.vitruv.change.atomic.id.IdResolver -import static extension edu.kit.ipd.sdq.commons.util.org.eclipse.emf.ecore.resource.ResourceSetUtil.withGlobalFactories +import org.eclipse.emf.ecore.util.EcoreUtil import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import static org.junit.jupiter.api.Assertions.assertTrue +import tools.vitruv.change.atomic.EChange +import tools.vitruv.change.atomic.EChangeTest +import tools.vitruv.change.atomic.uuid.UuidResolver + import static org.junit.jupiter.api.Assertions.assertFalse +import static org.junit.jupiter.api.Assertions.assertNotSame import static org.junit.jupiter.api.Assertions.assertNull import static org.junit.jupiter.api.Assertions.assertSame -import static org.junit.jupiter.api.Assertions.assertNotSame import static org.junit.jupiter.api.Assertions.assertThrows +import static org.junit.jupiter.api.Assertions.assertTrue import static tools.vitruv.testutils.metamodels.AllElementTypesCreators.* -import static extension tools.vitruv.change.atomic.resolve.EChangeResolverAndApplicator.* + +import static extension edu.kit.ipd.sdq.commons.util.org.eclipse.emf.ecore.resource.ResourceSetUtil.withGlobalFactories +import static extension tools.vitruv.change.atomic.resolve.EChangeUuidResolverAndApplicator.* /** * Test class for {@link FeatureEChange} which is used by every {@link EChange} which modifies {@link EStructuralFeature}s @@ -33,7 +34,7 @@ class FeatureEChangeTest extends EChangeTest { var EAttribute affectedFeature // Second model instance - var IdResolver idResolver2 + var UuidResolver uuidResolver2 var Resource resource2 @BeforeEach @@ -43,8 +44,9 @@ class FeatureEChangeTest extends EChangeTest { // Load model in second resource val resourceSet2 = new ResourceSetImpl().withGlobalFactories - this.resource2 = resourceSet2.getResource(resource.URI, true) - this.idResolver2 = IdResolver.create(resourceSet2) + resource2 = resourceSet2.getResource(resource.URI, true) + uuidResolver2 = UuidResolver.create(resourceSet2) + uuidResolver.resolveResource(resource, resource2, uuidResolver2) } /** @@ -74,12 +76,12 @@ class FeatureEChangeTest extends EChangeTest { // second resource has no such root element assertNull(resource2.getEObject(EcoreUtil.getURI(affectedEObject).fragment)) - // Create change + // Create change val unresolvedChange = createUnresolvedChange() unresolvedChange.assertIsNotResolved(affectedEObject, affectedFeature) // Resolve - assertThrows(IllegalStateException)[unresolvedChange.resolveBefore(idResolver2)] + assertThrows(IllegalStateException)[unresolvedChange.resolveBefore(uuidResolver2)] } /** @@ -125,7 +127,7 @@ class FeatureEChangeTest extends EChangeTest { * Creates and inserts a new root element in the resource 1. */ def private Root prepareSecondRoot() { - val root = aet.Root + val root = aet.Root.withUuid resource.contents.add(root) return root } diff --git a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/feature/attribute/InsertEAttributeValueTest.xtend b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/feature/attribute/InsertEAttributeValueTest.xtend index b66bc74e..c154ded0 100644 --- a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/feature/attribute/InsertEAttributeValueTest.xtend +++ b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/feature/attribute/InsertEAttributeValueTest.xtend @@ -2,17 +2,13 @@ package tools.vitruv.change.atomic.feature.attribute import allElementTypes.NonRoot import allElementTypes.Root -import tools.vitruv.change.atomic.feature.attribute.InsertEAttributeValue - import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import static org.junit.jupiter.api.Assertions.assertTrue -import static org.junit.jupiter.api.Assertions.assertFalse + import static org.junit.jupiter.api.Assertions.assertEquals -import static org.junit.jupiter.api.Assertions.assertNull import static org.junit.jupiter.api.Assertions.assertThrows +import static org.junit.jupiter.api.Assertions.assertTrue import static tools.vitruv.testutils.metamodels.AllElementTypesCreators.* -import static extension tools.vitruv.change.atomic.resolve.EChangeResolverAndApplicator.* /** * Test class for the concrete {@link InsertEAttributeValue} EChange, @@ -120,7 +116,7 @@ class InsertEAttributeValueTest extends InsertRemoveEAttributeTest { @Test def void invalidAttributeTest() { // NonRoot has no multi-valued int attribute - val affectedNonRootEObject = aet.NonRoot + val affectedNonRootEObject = aet.NonRoot.withUuid resource.contents.add(affectedNonRootEObject) // Resolving the change will be tested in EFeatureChange diff --git a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/feature/attribute/RemoveEAttributeValueTest.xtend b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/feature/attribute/RemoveEAttributeValueTest.xtend index 0b1a2721..386e2a94 100644 --- a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/feature/attribute/RemoveEAttributeValueTest.xtend +++ b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/feature/attribute/RemoveEAttributeValueTest.xtend @@ -2,17 +2,13 @@ package tools.vitruv.change.atomic.feature.attribute import allElementTypes.NonRoot import allElementTypes.Root -import tools.vitruv.change.atomic.feature.attribute.RemoveEAttributeValue - import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import static org.junit.jupiter.api.Assertions.assertTrue -import static org.junit.jupiter.api.Assertions.assertFalse + import static org.junit.jupiter.api.Assertions.assertEquals -import static org.junit.jupiter.api.Assertions.assertNull -import static tools.vitruv.testutils.metamodels.AllElementTypesCreators.* import static org.junit.jupiter.api.Assertions.assertThrows -import static extension tools.vitruv.change.atomic.resolve.EChangeResolverAndApplicator.* +import static org.junit.jupiter.api.Assertions.assertTrue +import static tools.vitruv.testutils.metamodels.AllElementTypesCreators.* /** * Test class for the concrete {@link RemoveEAttributeValue} EChange, @@ -117,7 +113,7 @@ class RemoveEAttributeValueTest extends InsertRemoveEAttributeTest { */ @Test def void invalidAttributeTest() { - val affectedNonRootEObject = aet.NonRoot + val affectedNonRootEObject = aet.NonRoot.withUuid resource.contents.add(affectedNonRootEObject) // Create change and resolve diff --git a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/feature/attribute/ReplaceSingleValuedEAttributeTest.xtend b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/feature/attribute/ReplaceSingleValuedEAttributeTest.xtend index 3a69b73a..c7059469 100644 --- a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/feature/attribute/ReplaceSingleValuedEAttributeTest.xtend +++ b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/feature/attribute/ReplaceSingleValuedEAttributeTest.xtend @@ -4,17 +4,14 @@ import allElementTypes.AllElementTypesPackage import allElementTypes.NonRoot import allElementTypes.Root import org.eclipse.emf.ecore.EAttribute -import tools.vitruv.change.atomic.feature.attribute.ReplaceSingleValuedEAttribute -import tools.vitruv.change.atomic.EChangeTest - import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import static org.junit.jupiter.api.Assertions.assertTrue -import static org.junit.jupiter.api.Assertions.assertFalse +import tools.vitruv.change.atomic.EChangeTest + import static org.junit.jupiter.api.Assertions.assertEquals -import static tools.vitruv.testutils.metamodels.AllElementTypesCreators.* import static org.junit.jupiter.api.Assertions.assertThrows -import static extension tools.vitruv.change.atomic.resolve.EChangeResolverAndApplicator.* +import static org.junit.jupiter.api.Assertions.assertTrue +import static tools.vitruv.testutils.metamodels.AllElementTypesCreators.* /** * Test class for the concrete {@link ReplaceSingleValuedEAttribute} EChange, @@ -93,7 +90,7 @@ class ReplaceSingleValuedEAttributeTest extends EChangeTest { @Test def void invalidAttributeTest() { // NonRoot element has no int attribute. - val affectedNonRootEObject = aet.NonRoot + val affectedNonRootEObject = aet.NonRoot.withUuid resource.contents.add(affectedNonRootEObject) val affectedRootFeature = AllElementTypesPackage.Literals.ROOT__SINGLE_VALUED_EATTRIBUTE val oldIntValue = DEFAULT_SINGLE_VALUED_EATTRIBUTE_VALUE diff --git a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/feature/reference/InsertEReferenceTest.xtend b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/feature/reference/InsertEReferenceTest.xtend index 2a4748ed..a17b9b3d 100644 --- a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/feature/reference/InsertEReferenceTest.xtend +++ b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/feature/reference/InsertEReferenceTest.xtend @@ -5,18 +5,16 @@ import allElementTypes.NonRoot import allElementTypes.Root import org.eclipse.emf.common.util.EList import org.eclipse.emf.ecore.EReference -import tools.vitruv.change.atomic.feature.reference.InsertEReference - import org.junit.jupiter.api.Test -import static org.junit.jupiter.api.Assertions.assertTrue -import static org.junit.jupiter.api.Assertions.assertFalse + +import static org.hamcrest.MatcherAssert.assertThat import static org.junit.jupiter.api.Assertions.assertEquals -import static org.junit.jupiter.api.Assertions.assertSame +import static org.junit.jupiter.api.Assertions.assertFalse import static org.junit.jupiter.api.Assertions.assertNotSame +import static org.junit.jupiter.api.Assertions.assertSame import static org.junit.jupiter.api.Assertions.assertThrows -import static org.hamcrest.MatcherAssert.assertThat +import static org.junit.jupiter.api.Assertions.assertTrue import static tools.vitruv.testutils.matchers.ModelMatchers.equalsDeeply -import static extension tools.vitruv.change.atomic.resolve.EChangeResolverAndApplicator.* /** * Test class for the concrete {@link InsertEReferenceValue} EChange, diff --git a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/feature/reference/ReferenceEChangeTest.xtend b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/feature/reference/ReferenceEChangeTest.xtend index a9b7c8fc..aebc1aa4 100644 --- a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/feature/reference/ReferenceEChangeTest.xtend +++ b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/feature/reference/ReferenceEChangeTest.xtend @@ -25,8 +25,8 @@ abstract class ReferenceEChangeTest extends EChangeTest { @BeforeEach def final void beforeTest() { affectedEObject = rootObject - newValue = aet.NonRoot - newValue2 = aet.NonRoot + newValue = aet.NonRoot.withUuid + newValue2 = aet.NonRoot.withUuid } /** diff --git a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/feature/reference/RemoveEReferenceTest.xtend b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/feature/reference/RemoveEReferenceTest.xtend index 3d9e5fb0..3ffeb833 100644 --- a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/feature/reference/RemoveEReferenceTest.xtend +++ b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/feature/reference/RemoveEReferenceTest.xtend @@ -6,19 +6,16 @@ import allElementTypes.Root import org.eclipse.emf.common.util.EList import org.eclipse.emf.ecore.EObject import org.eclipse.emf.ecore.EReference -import tools.vitruv.change.atomic.feature.reference.RemoveEReference - import org.junit.jupiter.api.Test -import static org.junit.jupiter.api.Assertions.assertTrue -import static org.junit.jupiter.api.Assertions.assertFalse + +import static org.hamcrest.MatcherAssert.assertThat import static org.junit.jupiter.api.Assertions.assertEquals -import static org.junit.jupiter.api.Assertions.assertNull -import static org.junit.jupiter.api.Assertions.assertSame +import static org.junit.jupiter.api.Assertions.assertFalse import static org.junit.jupiter.api.Assertions.assertNotSame +import static org.junit.jupiter.api.Assertions.assertSame import static org.junit.jupiter.api.Assertions.assertThrows -import static org.hamcrest.MatcherAssert.assertThat +import static org.junit.jupiter.api.Assertions.assertTrue import static tools.vitruv.testutils.matchers.ModelMatchers.equalsDeeply -import static extension tools.vitruv.change.atomic.resolve.EChangeResolverAndApplicator.* /** * Test class for the concrete {@link RemoveEReference} EChange, diff --git a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/feature/reference/ReplaceSingleValuedEReferenceTest.xtend b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/feature/reference/ReplaceSingleValuedEReferenceTest.xtend index d30d9674..ca64ec81 100644 --- a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/feature/reference/ReplaceSingleValuedEReferenceTest.xtend +++ b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/feature/reference/ReplaceSingleValuedEReferenceTest.xtend @@ -28,7 +28,7 @@ class ReplaceSingleValuedEReferenceTest extends ReferenceEChangeTest { @BeforeEach def void prepareElements() { - oldValue = aet.NonRoot + oldValue = aet.NonRoot.withUuid } /** diff --git a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/id/IdResolverTest.xtend b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/id/IdResolverTest.xtend index 7152f530..d69cffde 100644 --- a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/id/IdResolverTest.xtend +++ b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/id/IdResolverTest.xtend @@ -1,25 +1,26 @@ package tools.vitruv.change.atomic.id -import org.junit.jupiter.api.Test +import java.nio.file.Path +import org.eclipse.emf.common.util.URI +import org.eclipse.emf.ecore.resource.ResourceSet +import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl +import org.eclipse.emf.ecore.util.EcoreUtil +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test import org.junit.jupiter.api.^extension.ExtendWith -import tools.vitruv.testutils.TestProjectManager import tools.vitruv.testutils.RegisterMetamodelsInStandalone -import static tools.vitruv.testutils.metamodels.AllElementTypesCreators.aet -import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl -import static extension edu.kit.ipd.sdq.commons.util.org.eclipse.emf.ecore.resource.ResourceSetUtil.withGlobalFactories -import org.eclipse.emf.common.util.URI import tools.vitruv.testutils.TestProject -import java.nio.file.Path +import tools.vitruv.testutils.TestProjectManager + import static org.junit.jupiter.api.Assertions.assertEquals import static org.junit.jupiter.api.Assertions.assertNotEquals -import static extension org.eclipse.emf.ecore.util.EcoreUtil.getURI -import org.junit.jupiter.api.BeforeEach -import org.eclipse.emf.ecore.util.EcoreUtil import static org.junit.jupiter.api.Assertions.assertThrows import static org.junit.jupiter.api.Assertions.assertTrue -import org.eclipse.emf.ecore.resource.ResourceSet -import tools.vitruv.change.atomic.id.IdResolver +import static tools.vitruv.testutils.metamodels.AllElementTypesCreators.aet + +import static extension edu.kit.ipd.sdq.commons.util.org.eclipse.emf.ecore.resource.ResourceSetUtil.withGlobalFactories +import static extension org.eclipse.emf.ecore.util.EcoreUtil.getURI @ExtendWith(#[TestProjectManager, RegisterMetamodelsInStandalone]) class IdResolverTest { @@ -221,7 +222,7 @@ class IdResolverTest { @Test @DisplayName("generate ID and resolve it after element deletion") - def void elementDeletionDoesNotRemoveUiud() { + def void elementDeletionDoesNotRemoveId() { val root = aet.Root resourceSet.createResource(URI.createFileURI(testProjectPath.resolve("root.aet").toString)) => [ contents += root diff --git a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/root/RemoveRootEObjectTest.xtend b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/root/RemoveRootEObjectTest.xtend index adba9cd5..69b53a24 100644 --- a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/root/RemoveRootEObjectTest.xtend +++ b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/root/RemoveRootEObjectTest.xtend @@ -55,7 +55,7 @@ class RemoveRootEObjectTest extends RootEChangeTest { @Test def void resolveToCorrectType() { // Create change - val unresolvedChange = createUnresolvedChange(newRootObject, 0) + val unresolvedChange = createUnresolvedChange(newRootObject, 1) unresolvedChange.assertIsNotResolved(newRootObject) // Resolve @@ -73,7 +73,7 @@ class RemoveRootEObjectTest extends RootEChangeTest { val resolvedChange = createUnresolvedChange(newRootObject, 1).resolveBefore as RemoveRootEObject resolvedChange.assertIsResolved(newRootObject, resource) - // Apply forward 1 + // Apply forward 1 resolvedChange.assertApplyForward assertEquals(resourceContent.size, 2) @@ -83,10 +83,10 @@ class RemoveRootEObjectTest extends RootEChangeTest { val resolvedChange2 = createUnresolvedChange(newRootObject2, 1).resolveBefore as RemoveRootEObject resolvedChange2.assertIsResolved(newRootObject2, resource) - // Apply forward 2 + // Apply forward 2 resolvedChange2.assertApplyForward - // State after + // State after assertIsStateAfter } @@ -97,11 +97,12 @@ class RemoveRootEObjectTest extends RootEChangeTest { @Test def void applyBackwardTest() { // Create and resolve and apply forward 1 - val resolvedChange = createUnresolvedChange(newRootObject, 0).resolveBefore as RemoveRootEObject + val resolvedChange = createUnresolvedChange(newRootObject, 1).resolveBefore as RemoveRootEObject resolvedChange.assertApplyForward + // Create and resolve and apply forward 2 - val resolvedChange2 = createUnresolvedChange(newRootObject2, 0).resolveBefore as RemoveRootEObject + val resolvedChange2 = createUnresolvedChange(newRootObject2, 1).resolveBefore as RemoveRootEObject resolvedChange2.assertApplyForward // State after diff --git a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/root/RootEChangeTest.xtend b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/root/RootEChangeTest.xtend index 12bca076..82eca389 100644 --- a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/root/RootEChangeTest.xtend +++ b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/root/RootEChangeTest.xtend @@ -19,8 +19,8 @@ abstract class RootEChangeTest extends EChangeTest { * Creates two new root elements which can be used in the tests. */ @BeforeEach - def final void beforeTest() { - newRootObject = aet.Root - newRootObject2 = aet.Root + def void beforeTest() { + newRootObject = aet.Root.withUuid + newRootObject2 = aet.Root.withUuid } } diff --git a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/util/EChangeAssertHelper.xtend b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/util/EChangeAssertHelper.xtend index c289a1af..0e5e4fc4 100644 --- a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/util/EChangeAssertHelper.xtend +++ b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/util/EChangeAssertHelper.xtend @@ -1,26 +1,27 @@ package tools.vitruv.change.atomic.util -import tools.vitruv.change.atomic.EChange import java.util.List -import static org.junit.jupiter.api.Assertions.assertTrue -import static org.junit.jupiter.api.Assertions.assertFalse +import org.eclipse.xtend.lib.annotations.FinalFieldsConstructor +import tools.vitruv.change.atomic.EChange +import tools.vitruv.change.atomic.uuid.UuidResolver + +import static org.hamcrest.MatcherAssert.assertThat +import static org.hamcrest.core.Is.is +import static org.hamcrest.core.IsInstanceOf.instanceOf import static org.junit.jupiter.api.Assertions.assertEquals -import static org.junit.jupiter.api.Assertions.assertNull +import static org.junit.jupiter.api.Assertions.assertFalse import static org.junit.jupiter.api.Assertions.assertNotNull import static org.junit.jupiter.api.Assertions.assertNotSame -import static org.hamcrest.MatcherAssert.assertThat -import static org.hamcrest.core.IsInstanceOf.instanceOf -import static org.hamcrest.core.Is.is -import static extension tools.vitruv.change.atomic.resolve.EChangeResolverAndApplicator.* -import org.eclipse.xtend.lib.annotations.FinalFieldsConstructor -import tools.vitruv.change.atomic.id.IdResolver +import static org.junit.jupiter.api.Assertions.assertTrue + +import static extension tools.vitruv.change.atomic.resolve.EChangeUuidResolverAndApplicator.* /** * Utility class for frequently used assert methods in the tests. */ @FinalFieldsConstructor class EChangeAssertHelper { - val IdResolver idResolver + val UuidResolver uuidResolver /** * Tests whether a unresolved change and a resolved change are the same class. @@ -49,7 +50,7 @@ class EChangeAssertHelper { def void assertApplyForward(EChange change) { assertNotNull(change) assertTrue(change.isResolved) - change.applyForward(idResolver) + change.applyForward(uuidResolver) } /** @@ -65,7 +66,7 @@ class EChangeAssertHelper { def void assertApplyBackward(EChange change) { assertNotNull(change) assertTrue(change.isResolved) - change.applyBackward + change.applyBackward(uuidResolver) } /** diff --git a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/uuid/MultiResourceSetUuidResolvingTest.java b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/uuid/MultiResourceSetUuidResolvingTest.java new file mode 100644 index 00000000..2ff2c472 --- /dev/null +++ b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/uuid/MultiResourceSetUuidResolvingTest.java @@ -0,0 +1,156 @@ +package tools.vitruv.change.atomic.uuid; + +import static edu.kit.ipd.sdq.commons.util.org.eclipse.emf.ecore.resource.ResourceSetUtil.withGlobalFactories; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static tools.vitruv.testutils.metamodels.AllElementTypesCreators.aet; + +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +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.resource.impl.ResourceSetImpl; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import tools.vitruv.testutils.RegisterMetamodelsInStandalone; +import tools.vitruv.testutils.TestProject; +import tools.vitruv.testutils.TestProjectManager; + +@ExtendWith({ TestProjectManager.class, RegisterMetamodelsInStandalone.class }) +class MultiResourceSetUuidResolvingTest { + private ResourceSet sourceResourceSet; + private UuidResolver sourceUuidResolver; + private ResourceSet targetResourceSet; + private UuidResolver targetUuidResolver; + private Path testProjectPath; + + @BeforeEach + void setup(@TestProject Path testProjectPath) { + this.testProjectPath = testProjectPath; + this.sourceResourceSet = withGlobalFactories(new ResourceSetImpl()); + this.sourceUuidResolver = UuidResolver.create(sourceResourceSet); + this.targetResourceSet = withGlobalFactories(new ResourceSetImpl()); + this.targetUuidResolver = UuidResolver.create(targetResourceSet); + } + + @ParameterizedTest(name = "{0} resource(s)") + @ValueSource(ints = { 1, 2, 5, 10 }) + @DisplayName("resolve resource(s)") + void resolveResources(int resourcesCount) { + Map uuidMapping = new HashMap<>(); + Map targetToSourceElementMapping = new HashMap<>(); + Map sourceToTargetResourceMap = new HashMap<>(); + for (int i = 0; i < resourcesCount; i++) { + URI uri = URI.createFileURI(testProjectPath.resolve("root" + i + ".aet").toString()); + Resource sourceResource = sourceResourceSet.createResource(uri); + Resource targetResource = targetResourceSet.createResource(uri); + sourceToTargetResourceMap.put(sourceResource, targetResource); + + var sourceRoot = aet.Root(); + sourceResource.getContents().add(sourceRoot); + var sourceNonRoot = aet.NonRoot(); + sourceRoot.setSingleValuedContainmentEReference(sourceNonRoot); + + var targetRoot = aet.Root(); + targetResource.getContents().add(targetRoot); + var targetNonRoot = aet.NonRoot(); + targetRoot.setSingleValuedContainmentEReference(targetNonRoot); + targetToSourceElementMapping.put(targetRoot, sourceRoot); + targetToSourceElementMapping.put(targetNonRoot, sourceNonRoot); + + List.of(sourceRoot, sourceNonRoot).forEach(element -> { + String uuid = sourceUuidResolver.registerEObject(element); + uuidMapping.put(element, uuid); + }); + } + + sourceUuidResolver.resolveResources(sourceToTargetResourceMap, targetUuidResolver); + targetToSourceElementMapping.forEach((targetElement, sourceElement) -> { + String uuid = uuidMapping.get(sourceElement); + + assertEquals(sourceElement, sourceUuidResolver.getEObject(uuid), "source element does not match UUID"); + assertEquals(uuid, sourceUuidResolver.getUuid(sourceElement), "source UUID does not match element"); + assertThrows(IllegalStateException.class, () -> sourceUuidResolver.getUuid(targetElement), + "target element registered in source resolver"); + + assertEquals(targetElement, targetUuidResolver.getEObject(uuid), "target element does not match UUID"); + assertEquals(uuid, targetUuidResolver.getUuid(targetElement), "target UUID does not match element"); + assertThrows(IllegalStateException.class, () -> targetUuidResolver.getUuid(sourceElement), + "source element registered in target resolver"); + }); + } + + @Test + @DisplayName("resolve only subset of available resources") + void resolveSubsetResources() { + URI resolveUri = URI.createFileURI(testProjectPath.resolve("root1.aet").toString()); + URI notResolvedUri = URI.createFileURI(testProjectPath.resolve("root2.aet").toString()); + List.of(resolveUri, notResolvedUri).forEach((uri) -> { + Resource sourceResource = sourceResourceSet.createResource(uri); + Resource targetResource = targetResourceSet.createResource(uri); + var sourceRoot = aet.Root(); + sourceResource.getContents().add(sourceRoot); + targetResource.getContents().add(EcoreUtil.copy(sourceRoot)); + sourceUuidResolver.registerEObject(sourceRoot); + }); + + sourceUuidResolver.resolveResource(sourceResourceSet.getResource(resolveUri, false), + targetResourceSet.getResource(resolveUri, false), targetUuidResolver); + var resolvedRoot = targetResourceSet.getResource(resolveUri, false).getContents().get(0); + var notResolvedRoot = targetResourceSet.getResource(notResolvedUri, false).getContents().get(0); + assertDoesNotThrow(() -> targetUuidResolver.getUuid(resolvedRoot), "element missing in target UUID resolver"); + assertThrows(IllegalStateException.class, () -> targetUuidResolver.getUuid(notResolvedRoot), + "element must not be registered in target UUID resolver"); + } + + @Test + @DisplayName("resolve already resolved resource") + void resolveRegisteredResource() { + URI uri = URI.createFileURI(testProjectPath.resolve("root.aet").toString()); + Resource sourceResource = sourceResourceSet.createResource(uri); + Resource targetResource = targetResourceSet.createResource(uri); + var sourceRoot = aet.Root(); + var targetRoot = aet.Root(); + sourceResource.getContents().add(sourceRoot); + targetResource.getContents().add(targetRoot); + String uuid = sourceUuidResolver.registerEObject(sourceRoot); + + sourceUuidResolver.resolveResource(sourceResource, targetResource, targetUuidResolver); + assertEquals(uuid, targetUuidResolver.getUuid(targetRoot)); + + // re-resolve resource + assertDoesNotThrow( + () -> sourceUuidResolver.resolveResource(sourceResource, targetResource, targetUuidResolver)); + assertEquals(uuid, targetUuidResolver.getUuid(targetRoot)); + } + + @Test + @DisplayName("resolve resource with dangling element") + void resolveResourceWithDanglingElement() { + URI uri = URI.createFileURI(testProjectPath.resolve("root.aet").toString()); + Resource sourceResource = sourceResourceSet.createResource(uri); + Resource targetResource = targetResourceSet.createResource(uri); + var danglingElement = aet.Root(); + String danglingUuid = sourceUuidResolver.registerEObject(danglingElement); + + Map sourceToTargetMapping = Map.of(sourceResource, targetResource); + assertThrows(IllegalStateException.class, + () -> sourceUuidResolver.resolveResources(sourceToTargetMapping, targetUuidResolver)); + assertThrows(IllegalStateException.class, () -> targetUuidResolver.getEObject(danglingUuid), + "target UUID resolver must not be modified when resolving fails"); + assertThrows(IllegalStateException.class, () -> targetUuidResolver.getUuid(danglingElement), + "target UUID resolver must not be modified when resolving fails"); + } +} diff --git a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/uuid/UuidResolvingTest.java b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/uuid/UuidResolvingTest.java new file mode 100644 index 00000000..08234346 --- /dev/null +++ b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/uuid/UuidResolvingTest.java @@ -0,0 +1,179 @@ +package tools.vitruv.change.atomic.uuid; + +import static edu.kit.ipd.sdq.commons.util.org.eclipse.emf.ecore.resource.ResourceSetUtil.withGlobalFactories; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static tools.vitruv.testutils.metamodels.AllElementTypesCreators.aet; + +import java.nio.file.Path; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +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.resource.impl.ResourceSetImpl; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import tools.vitruv.testutils.RegisterMetamodelsInStandalone; +import tools.vitruv.testutils.TestProject; +import tools.vitruv.testutils.TestProjectManager; + +@ExtendWith({ TestProjectManager.class, RegisterMetamodelsInStandalone.class }) +class UuidResolvingTest { + private ResourceSet resourceSet; + private UuidResolver uuidResolver; + private Path testProjectPath; + + @BeforeEach + void setup(@TestProject Path testProjectPath) { + this.testProjectPath = testProjectPath; + this.resourceSet = withGlobalFactories(new ResourceSetImpl()); + this.uuidResolver = UuidResolver.create(resourceSet); + } + + @ParameterizedTest(name = "{0} element(s)") + @ValueSource(ints = { 1, 5, 10, 100 }) + @DisplayName("resolve element(s) without resource") + void registerMultipleElementsWithoutResource(int count) { + Map uuidToEObjectMapping = IntStream.range(0, count).mapToObj((i) -> aet.Root()) + .collect(Collectors.toMap(root -> uuidResolver.generateUuid(root), root -> root)); + + uuidToEObjectMapping.forEach((uuid, element) -> uuidResolver.registerEObject(uuid, element)); + uuidToEObjectMapping.forEach((uuid, element) -> { + assertTrue(uuidResolver.hasUuid(element)); + assertTrue(uuidResolver.hasEObject(uuid)); + assertEquals(element, uuidResolver.getEObject(uuid)); + assertEquals(uuid, uuidResolver.getUuid(element)); + }); + } + + @ParameterizedTest(name = "{0} element(s)") + @ValueSource(ints = { 1, 5, 10, 100 }) + @DisplayName("resolve element(s) with resource") + void registerMultipleElementsWithResource(int count) { + Map uuidToEObjectMapping = IntStream.range(0, count).mapToObj((i) -> { + var root = aet.Root(); + URI resourceUri = URI.createFileURI(testProjectPath.resolve("root" + i + ".aet").toString()); + Resource resource = resourceSet.createResource(resourceUri); + resource.getContents().add(root); + return root; + }).collect(Collectors.toMap(root -> uuidResolver.generateUuid(root), root -> root)); + + uuidToEObjectMapping.forEach((uuid, element) -> uuidResolver.registerEObject(uuid, element)); + uuidToEObjectMapping.forEach((uuid, element) -> { + assertTrue(uuidResolver.hasUuid(element)); + assertTrue(uuidResolver.hasEObject(uuid)); + assertEquals(element, uuidResolver.getEObject(uuid)); + assertEquals(uuid, uuidResolver.getUuid(element)); + }); + } + + @Test + @DisplayName("re-register element with same UUID") + void reregisterElementSame() { + var root = aet.Root(); + String uuid = uuidResolver.registerEObject(root); + + assertDoesNotThrow(() -> uuidResolver.registerEObject(uuid, root)); + } + + @Test + @DisplayName("re-register element with changed UUID") + void reregisterElementChanged() { + var root = aet.Root(); + String uuid = uuidResolver.registerEObject(root); + + assertThrows(IllegalStateException.class, () -> uuidResolver.registerEObject(uuid + "modified", root)); + } + + @Test + @DisplayName("modify element after registration") + void modifyElementAfterRegistration() { + var root = aet.Root(); + String uuid = uuidResolver.registerEObject(root); + + root.setId("newId"); + URI resourceUri = URI.createFileURI(testProjectPath.resolve("root.aet").toString()); + Resource rootResource = resourceSet.createResource(resourceUri); + rootResource.getContents().add(root); + + assertEquals(root, uuidResolver.getEObject(uuid)); + assertEquals(uuid, uuidResolver.getUuid(root)); + + } + + @Test + @DisplayName("register element in wrong resource set") + void registerElementInWrongResourceSet() { + ResourceSet otherResourceSet = withGlobalFactories(new ResourceSetImpl()); + var root = aet.Root(); + URI resourceUri = URI.createFileURI(testProjectPath.resolve("root.aet").toString()); + Resource rootResource = otherResourceSet.createResource(resourceUri); + rootResource.getContents().add(root); + + assertThrows(IllegalStateException.class, () -> uuidResolver.registerEObject(root)); + assertThrows(IllegalStateException.class, + () -> uuidResolver.registerEObject(uuidResolver.generateUuid(root), root)); + assertFalse(uuidResolver.hasUuid(root)); + } + + @Test + @DisplayName("generate UUID and resolve it after element deletion") + void elementDeletionDoesNotRemoveUuid() { + var root = aet.Root(); + URI resourceUri = URI.createFileURI(testProjectPath.resolve("root.aet").toString()); + Resource rootResource = resourceSet.createResource(resourceUri); + rootResource.getContents().add(root); + String uuid = uuidResolver.registerEObject(root); + + EcoreUtil.delete(root); + assertEquals(root, uuidResolver.getEObject(uuid)); + assertEquals(uuid, uuidResolver.getUuid(root)); + assertTrue(uuidResolver.hasUuid(root)); + assertTrue(uuidResolver.hasEObject(uuid)); + } + + @Test + @DisplayName("cleanup resolver when saving after element removal") + void cleanupAfterElementRemovalRemovesUuid() { + var root = aet.Root(); + String uuid = uuidResolver.registerEObject(root); + uuidResolver.endTransaction(); + assertThrows(IllegalStateException.class, () -> uuidResolver.getEObject(uuid)); + assertThrows(IllegalStateException.class, () -> uuidResolver.getUuid(root)); + assertFalse(uuidResolver.hasUuid(root)); + assertFalse(uuidResolver.hasEObject(uuid)); + } + + @Test + @DisplayName("cleanup resolver when saving resource after element removal") + void cleanupAfterElementRemovalRemovesUuidWithResource() { + var root = aet.Root(); + URI resourceUri = URI.createFileURI(testProjectPath.resolve("root.aet").toString()); + Resource rootResource = resourceSet.createResource(resourceUri); + rootResource.getContents().add(root); + String uuid = uuidResolver.registerEObject(root); + + uuidResolver.endTransaction(); + assertEquals(root, uuidResolver.getEObject(uuid)); + + rootResource.getContents().clear(); + uuidResolver.endTransaction(); + assertThrows(IllegalStateException.class, () -> uuidResolver.getEObject(uuid)); + assertThrows(IllegalStateException.class, () -> uuidResolver.getUuid(root)); + assertFalse(uuidResolver.hasUuid(root)); + assertFalse(uuidResolver.hasEObject(uuid)); + } +} diff --git a/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/uuid/UuidSerializationTest.java b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/uuid/UuidSerializationTest.java new file mode 100644 index 00000000..c8224d16 --- /dev/null +++ b/tests/tools.vitruv.change.atomic.tests/src/tools/vitruv/change/atomic/uuid/UuidSerializationTest.java @@ -0,0 +1,107 @@ +package tools.vitruv.change.atomic.uuid; + +import static edu.kit.ipd.sdq.commons.util.org.eclipse.emf.ecore.resource.ResourceSetUtil.withGlobalFactories; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static tools.vitruv.testutils.metamodels.AllElementTypesCreators.aet; + +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + +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.resource.impl.ResourceSetImpl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import tools.vitruv.testutils.RegisterMetamodelsInStandalone; +import tools.vitruv.testutils.TestProject; +import tools.vitruv.testutils.TestProjectManager; + +@ExtendWith({ TestProjectManager.class, RegisterMetamodelsInStandalone.class }) +class UuidSerializationTest { + private ResourceSet storeResourceSet; + private UuidResolver storeUuidResolver; + private ResourceSet loadResourceSet; + private UuidResolver loadUuidResolver; + private Path testProjectPath; + + private URI getSerializationUri() { + return URI.createFileURI(testProjectPath.resolve("UUID.uuid").toString()); + } + + @BeforeEach + void setup(@TestProject Path testProjectPath) { + this.testProjectPath = testProjectPath; + this.storeResourceSet = withGlobalFactories(new ResourceSetImpl()); + this.storeUuidResolver = UuidResolver.create(storeResourceSet); + this.loadResourceSet = withGlobalFactories(new ResourceSetImpl()); + this.loadUuidResolver = UuidResolver.create(loadResourceSet); + } + + @ParameterizedTest(name = "{0} element(s)") + @ValueSource(ints = { 1, 2, 5, 10, 100 }) + @DisplayName("store and load elements") + void storeAndLoadElements(int count) { + Map uuidMapping = new HashMap<>(); + Map loadToStoreElementsMapping = new HashMap<>(); + for (int i = 0; i < count; i++) { + URI uri = URI.createFileURI(testProjectPath.resolve("root" + i + ".aet").toString()); + + Resource storeResource = storeResourceSet.createResource(uri); + var storeRoot = aet.Root(); + storeResource.getContents().add(storeRoot); + String uuid = storeUuidResolver.registerEObject(storeRoot); + uuidMapping.put(storeRoot, uuid); + + Resource loadResource = loadResourceSet.createResource(uri); + var loadRoot = aet.Root(); + loadResource.getContents().add(loadRoot); + loadToStoreElementsMapping.put(loadRoot, storeRoot); + } + + URI serializationUri = getSerializationUri(); + assertDoesNotThrow(() -> storeUuidResolver.storeAtUri(serializationUri)); + assertDoesNotThrow(() -> loadUuidResolver.loadFromUri(serializationUri)); + + loadToStoreElementsMapping.forEach((loadElement, storeElement) -> { + String uuid = uuidMapping.get(storeElement); + assertEquals(storeElement, storeUuidResolver.getEObject(uuid), "stored element does not match UUID"); + assertEquals(uuid, storeUuidResolver.getUuid(storeElement), "stored UUID does not match element"); + assertEquals(loadElement, loadUuidResolver.getEObject(uuid), "loaded element does not match UUID"); + assertEquals(uuid, loadUuidResolver.getUuid(loadElement), "loaded UUID does not match element"); + }); + } + + @Test + @DisplayName("store dangling element") + void storeDanglingElement() { + var danglingElement = aet.Root(); + storeUuidResolver.registerEObject(danglingElement); + assertThrows(IllegalStateException.class, () -> storeUuidResolver.storeAtUri(getSerializationUri())); + } + + @Test + @DisplayName("load with non-empty resolver") + void loadWithNonEmptyResolver() { + URI uri = URI.createFileURI(testProjectPath.resolve("root.aet").toString()); + Resource storeResource = storeResourceSet.createResource(uri); + var storeRoot = aet.Root(); + storeResource.getContents().add(storeRoot); + storeUuidResolver.registerEObject(storeRoot); + URI serializationUri = getSerializationUri(); + assertDoesNotThrow(() -> storeUuidResolver.storeAtUri(serializationUri)); + + loadUuidResolver.registerEObject(aet.Root()); + assertThrows(IllegalStateException.class, () -> loadUuidResolver.loadFromUri(serializationUri)); + + } +} diff --git a/tests/tools.vitruv.change.composite.tests/src/tools/vitruv/change/composite/ChangeDescription2ChangeTransformationTest.xtend b/tests/tools.vitruv.change.composite.tests/src/tools/vitruv/change/composite/ChangeDescription2ChangeTransformationTest.xtend index cf8b5ade..6d348ee4 100644 --- a/tests/tools.vitruv.change.composite.tests/src/tools/vitruv/change/composite/ChangeDescription2ChangeTransformationTest.xtend +++ b/tests/tools.vitruv.change.composite.tests/src/tools/vitruv/change/composite/ChangeDescription2ChangeTransformationTest.xtend @@ -1,41 +1,44 @@ package tools.vitruv.change.composite import allElementTypes.Root +import java.nio.file.Path +import java.util.HashMap import java.util.List - -import tools.vitruv.change.atomic.EChange +import java.util.function.Consumer +import org.eclipse.emf.common.notify.Notifier import org.eclipse.emf.ecore.resource.ResourceSet -import org.junit.jupiter.api.BeforeEach +import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl +import org.eclipse.emf.ecore.util.EcoreUtil import org.junit.jupiter.api.AfterEach -import static org.junit.jupiter.api.Assertions.assertEquals -import static tools.vitruv.testutils.metamodels.AllElementTypesCreators.aet -import org.eclipse.emf.common.notify.Notifier -import java.util.function.Consumer -import static com.google.common.base.Preconditions.checkState +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.^extension.ExtendWith -import tools.vitruv.testutils.TestProjectManager -import tools.vitruv.testutils.TestProject -import java.nio.file.Path -import tools.vitruv.testutils.RegisterMetamodelsInStandalone -import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl -import static extension edu.kit.ipd.sdq.commons.util.org.eclipse.emf.ecore.resource.ResourceSetUtil.withGlobalFactories -import tools.vitruv.change.composite.recording.ChangeRecorder +import tools.vitruv.change.atomic.EChange +import tools.vitruv.change.atomic.EChangeUuidManager +import tools.vitruv.change.atomic.uuid.UuidResolver import tools.vitruv.change.composite.description.TransactionalChange -import static extension edu.kit.ipd.sdq.commons.util.org.eclipse.emf.common.util.URIUtil.createFileURI -import static extension edu.kit.ipd.sdq.commons.util.org.eclipse.emf.ecore.resource.ResourceSetUtil.loadOrCreateResource -import static extension tools.vitruv.change.atomic.resolve.EChangeResolverAndApplicator.* -import org.eclipse.emf.ecore.util.EcoreUtil -import static tools.vitruv.testutils.matchers.ModelMatchers.equalsDeeply +import tools.vitruv.change.composite.recording.ChangeRecorder +import tools.vitruv.testutils.RegisterMetamodelsInStandalone +import tools.vitruv.testutils.TestProject +import tools.vitruv.testutils.TestProjectManager + +import static com.google.common.base.Preconditions.checkState import static org.hamcrest.MatcherAssert.assertThat -import static org.junit.jupiter.api.Assertions.assertTrue +import static org.junit.jupiter.api.Assertions.assertEquals import static org.junit.jupiter.api.Assertions.assertNotNull +import static org.junit.jupiter.api.Assertions.assertTrue +import static tools.vitruv.testutils.matchers.ModelMatchers.equalsDeeply +import static tools.vitruv.testutils.metamodels.AllElementTypesCreators.aet + +import static extension edu.kit.ipd.sdq.commons.util.org.eclipse.emf.common.util.URIUtil.createFileURI +import static extension edu.kit.ipd.sdq.commons.util.org.eclipse.emf.ecore.resource.ResourceSetUtil.loadOrCreateResource +import static extension edu.kit.ipd.sdq.commons.util.org.eclipse.emf.ecore.resource.ResourceSetUtil.withGlobalFactories import static extension edu.kit.ipd.sdq.commons.util.org.eclipse.emf.ecore.resource.ResourceUtil.getFirstRootEObject -import tools.vitruv.change.atomic.id.IdResolver +import static extension tools.vitruv.change.atomic.resolve.EChangeUuidResolverAndApplicator.* @ExtendWith(TestProjectManager, RegisterMetamodelsInStandalone) abstract class ChangeDescription2ChangeTransformationTest { var ChangeRecorder changeRecorder - var IdResolver idResolver + var UuidResolver uuidResolver var ResourceSet resourceSet var Path tempFolder @@ -46,7 +49,7 @@ abstract class ChangeDescription2ChangeTransformationTest { def void beforeTest(@TestProject Path tempFolder) { this.tempFolder = tempFolder this.resourceSet = new ResourceSetImpl().withGlobalFactories() - this.idResolver = IdResolver.create(resourceSet) + this.uuidResolver = UuidResolver.create(resourceSet) this.changeRecorder = new ChangeRecorder(resourceSet) this.resourceSet.startRecording } @@ -63,10 +66,12 @@ abstract class ChangeDescription2ChangeTransformationTest { protected def recordComposite(T objectToRecord, Consumer operationToRecord) { resourceSet.stopRecording + EChangeUuidManager.setOrGenerateIds(changeRecorder.change.EChanges, uuidResolver) objectToRecord.startRecording operationToRecord.accept(objectToRecord) objectToRecord.stopRecording val recordedChange = changeRecorder.change + EChangeUuidManager.setOrGenerateIds(recordedChange.EChanges, uuidResolver) resourceSet.startRecording return recordedChange } @@ -108,18 +113,20 @@ abstract class ChangeDescription2ChangeTransformationTest { // be applied to a different state val monitoredChanges = change.EChanges monitoredChanges.reverseView.forEach[monitoredChange| - monitoredChange.applyBackward + monitoredChange.applyBackward(uuidResolver) ] + uuidResolver.endTransaction val comparisonResourceSet = new ResourceSetImpl().withGlobalFactories() - val comparisonIdResolver = IdResolver.create(comparisonResourceSet) - resourceSet.copyTo(comparisonResourceSet) + val originalToComparisonResourceMapping = resourceSet.copyTo(comparisonResourceSet) + val comparisonUuidResolver = UuidResolver.create(comparisonResourceSet) + uuidResolver.resolveResources(originalToComparisonResourceMapping, comparisonUuidResolver) monitoredChanges.map[ - applyForward(idResolver) + applyForward(uuidResolver) EcoreUtil.copy(it) ].forEach[ val unresolvedChange = it.unresolve() - val resolvedChange = unresolvedChange.resolveBefore(comparisonIdResolver) - resolvedChange.applyForward(comparisonIdResolver) + val resolvedChange = unresolvedChange.resolveBefore(comparisonUuidResolver) + resolvedChange.applyForward(comparisonUuidResolver) ] resourceSet.assertContains(comparisonResourceSet) comparisonResourceSet.assertContains(resourceSet) @@ -127,12 +134,15 @@ abstract class ChangeDescription2ChangeTransformationTest { } private static def copyTo(ResourceSet original, ResourceSet target) { + var resourceMapping = new HashMap; for (originalResource : original.resources) { val comparisonResource = target.createResource(originalResource.URI) if (!originalResource.contents.empty) { comparisonResource.contents += EcoreUtil.copyAll(originalResource.contents) } + resourceMapping.put(originalResource, comparisonResource) } + return resourceMapping } private static def assertContains(ResourceSet first, ResourceSet second) { @@ -155,5 +165,4 @@ abstract class ChangeDescription2ChangeTransformationTest { ) return changes } - } diff --git a/tests/tools.vitruv.change.composite.tests/src/tools/vitruv/change/composite/recording/ChangeRecorderTest.xtend b/tests/tools.vitruv.change.composite.tests/src/tools/vitruv/change/composite/recording/ChangeRecorderTest.xtend index 95155fe5..3805270c 100644 --- a/tests/tools.vitruv.change.composite.tests/src/tools/vitruv/change/composite/recording/ChangeRecorderTest.xtend +++ b/tests/tools.vitruv.change.composite.tests/src/tools/vitruv/change/composite/recording/ChangeRecorderTest.xtend @@ -39,13 +39,15 @@ import static tools.vitruv.testutils.matchers.ModelMatchers.* import org.eclipse.emf.ecore.util.EcoreUtil import tools.vitruv.change.atomic.feature.reference.RemoveEReference import tools.vitruv.change.atomic.feature.reference.ReplaceSingleValuedEReference +import tools.vitruv.change.atomic.uuid.UuidResolver @ExtendWith(TestProjectManager, RegisterMetamodelsInStandalone) class ChangeRecorderTest { // this test only covers general behaviour of ChangeRecorder. Whether it always produces correct change sequences // is covered by other tests val ResourceSet resourceSet = new ResourceSetImpl().withGlobalFactories() - var ChangeRecorder changeRecorder = new ChangeRecorder(resourceSet) + val uuidResolver = UuidResolver.create(resourceSet) + val ChangeRecorder changeRecorder = new ChangeRecorder(resourceSet) private def T wrapIntoRecordedResource(T object) { val resource = resourceSet.createResource(URI.createURI('test://test.aet')) @@ -79,7 +81,7 @@ class ChangeRecorderTest { @Test @DisplayName("records direct changes to an object") def void recordOnObject() { - val root = aet.Root + val root = aet.Root.withUuid changeRecorder.addToRecording(root) record [ root.id = 'test' @@ -360,8 +362,8 @@ class ChangeRecorderTest { def void recordsOnDeletedResource(@TestProject Path testDir) { changeRecorder.addToRecording(resourceSet) val resource = resourceSet.createResource(URI.createFileURI(testDir.resolve("test.aet").toString)) => [ - contents += aet.Root => [ - singleValuedContainmentEReference = aet.NonRoot + contents += aet.Root.withUuid => [ + singleValuedContainmentEReference = aet.NonRoot.withUuid ] ] record [ @@ -832,6 +834,11 @@ class ChangeRecorderTest { def private static hasNoChanges() { new EChangeSequenceMatcher(emptyList) } + + def private withUuid(O eObject) { + uuidResolver.registerEObject(eObject) + eObject + } @FinalFieldsConstructor private static class EChangeSequenceMatcher extends TypeSafeMatcher {