From f751f99b7360f839b53f2548f96b6161ff22b29e Mon Sep 17 00:00:00 2001 From: Jan Wittler Date: Fri, 25 Nov 2022 17:12:46 +0100 Subject: [PATCH] adapt `DefaultStateBasedChangeResolutionStrategy` to return changes with elements of the actually provided resource set instead of ones from the internally used resource set --- ...ltStateBasedChangeResolutionStrategy.xtend | 100 +++++++++++++++--- .../StateBasedChangeResolutionStrategy.xtend | 24 ++++- 2 files changed, 102 insertions(+), 22 deletions(-) diff --git a/bundles/tools.vitruv.framework.views/src/tools/vitruv/framework/views/changederivation/DefaultStateBasedChangeResolutionStrategy.xtend b/bundles/tools.vitruv.framework.views/src/tools/vitruv/framework/views/changederivation/DefaultStateBasedChangeResolutionStrategy.xtend index 7becdc5e9f..764ba63644 100644 --- a/bundles/tools.vitruv.framework.views/src/tools/vitruv/framework/views/changederivation/DefaultStateBasedChangeResolutionStrategy.xtend +++ b/bundles/tools.vitruv.framework.views/src/tools/vitruv/framework/views/changederivation/DefaultStateBasedChangeResolutionStrategy.xtend @@ -1,7 +1,10 @@ package tools.vitruv.framework.views.changederivation +import java.util.HashMap +import java.util.Map import org.eclipse.emf.common.notify.Notifier import org.eclipse.emf.common.util.BasicMonitor +import org.eclipse.emf.common.util.URI import org.eclipse.emf.compare.EMFCompare import org.eclipse.emf.compare.match.impl.MatchEngineFactoryImpl import org.eclipse.emf.compare.match.impl.MatchEngineFactoryRegistryImpl @@ -9,21 +12,28 @@ import org.eclipse.emf.compare.merge.BatchMerger import org.eclipse.emf.compare.merge.IMerger import org.eclipse.emf.compare.scope.DefaultComparisonScope import org.eclipse.emf.compare.utils.UseIdentifiers +import org.eclipse.emf.ecore.EObject import org.eclipse.emf.ecore.resource.Resource import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl import org.eclipse.emf.ecore.util.EcoreUtil +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.composite.description.TransactionalChange import tools.vitruv.change.composite.recording.ChangeRecorder import tools.vitruv.framework.views.util.ResourceCopier 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 extension edu.kit.ipd.sdq.commons.util.org.eclipse.emf.ecore.resource.ResourceUtil.getReferencedProxies -import tools.vitruv.change.atomic.uuid.UuidResolver /** * This default strategy for diff based state changes uses EMFCompare to resolve a * diff to a sequence of individual changes. - * @author Timur Saglam */ class DefaultStateBasedChangeResolutionStrategy implements StateBasedChangeResolutionStrategy { /** The identifier matching behavior used by this strategy */ @@ -51,21 +61,20 @@ class DefaultStateBasedChangeResolutionStrategy implements StateBasedChangeResol resource.URI, String.join(", ", proxies.map[toString])) } - override getChangeSequenceBetween(Resource newState, Resource oldState, UuidResolver uuidResolver) { + override getChangeSequenceBetween(Resource newState, Resource oldState) { checkArgument(oldState !== null && newState !== null, "old state or new state must not be null!") - checkArgument(uuidResolver !== null, "uuid resolver must not be null!") newState.checkNoProxies("new state") oldState.checkNoProxies("old state") val monitoredResourceSet = new ResourceSetImpl() - val currentStateCopy = ResourceCopier.copyViewResource(oldState, monitoredResourceSet) - val resolver = UuidResolver.create(monitoredResourceSet) - uuidResolver.resolveResource(oldState, currentStateCopy, resolver) - return currentStateCopy.record(resolver) [ + val resourceCopier = new ResourceCopier() + val currentStateCopy = resourceCopier.copyViewResource(oldState, monitoredResourceSet) + val change = currentStateCopy.record [ if (oldState.URI != newState.URI) { currentStateCopy.URI = newState.URI } compareStatesAndReplayChanges(newState, currentStateCopy) ] + return transformChangeToOriginalResourceSet(change, resourceCopier) } override getChangeSequenceForCreated(Resource newState) { @@ -76,27 +85,27 @@ class DefaultStateBasedChangeResolutionStrategy implements StateBasedChangeResol val monitoredResourceSet = new ResourceSetImpl() val newResource = monitoredResourceSet.createResource(newState.URI) newResource.contents.clear() - return newResource.record(UuidResolver.create(monitoredResourceSet)) [ + val change = newResource.record [ newResource.contents += EcoreUtil.copyAll(newState.contents) ] + return transformChangeToOriginalResourceSet(change, new ResourceCopier()); } - override getChangeSequenceForDeleted(Resource oldState, UuidResolver uuidResolver) { + override getChangeSequenceForDeleted(Resource oldState) { checkArgument(oldState !== null, "old state must not be null!") - checkArgument(uuidResolver !== null, "uuid resolver must not be null!") oldState.checkNoProxies("old state") // Setup resolver and copy state: val monitoredResourceSet = new ResourceSetImpl() - val currentStateCopy = ResourceCopier.copyViewResource(oldState, monitoredResourceSet) - val resolver = UuidResolver.create(monitoredResourceSet) - uuidResolver.resolveResource(oldState, currentStateCopy, resolver) - return currentStateCopy.record(resolver) [ + val resourceCopier = new ResourceCopier() + val currentStateCopy = resourceCopier.copyViewResource(oldState, monitoredResourceSet) + val change = currentStateCopy.record [ currentStateCopy.contents.clear() ] + return transformChangeToOriginalResourceSet(change, resourceCopier) } - private def record(Resource resource, UuidResolver uuidResolver, ()=>void function) { - try (val changeRecorder = new ChangeRecorder(resource.resourceSet, uuidResolver)) { + private def record(Resource resource, ()=>void function) { + try (val changeRecorder = new ChangeRecorder(resource.resourceSet)) { changeRecorder.beginRecording changeRecorder.addToRecording(resource) function.apply() @@ -120,4 +129,61 @@ class DefaultStateBasedChangeResolutionStrategy implements StateBasedChangeResol val merger = new BatchMerger(mergerRegistry) merger.copyAllLeftToRight(differences, new BasicMonitor) } + + /** + * Replaces all elements in the change, which are in the {@code monitoredResourceSet}, + * with their counterparts in the originally provided resource set. + */ + private def TransactionalChange transformChangeToOriginalResourceSet(TransactionalChange change, ResourceCopier copier) { + var Map createdObjects = new HashMap() + for (eChange : change.EChanges) { + switch eChange { + CreateEObject: + eChange.affectedEObject = getOriginalForCopy(copier, eChange.affectedEObject, createdObjects, true) + EObjectExistenceEChange: + eChange.affectedEObject = getOriginalForCopy(copier, eChange.affectedEObject, createdObjects, false) + FeatureEChange: + eChange.affectedEObject = getOriginalForCopy(copier, eChange.affectedEObject, createdObjects, false) + } + switch eChange { + EObjectSubtractedEChange: + if (eChange.oldValue !== null) { + eChange.oldValue = getOriginalForCopy(copier, eChange.oldValue, createdObjects, false) + } + } + switch eChange { + EObjectAddedEChange: + if (eChange.newValue !== null) { + eChange.newValue = getOriginalForCopy(copier, eChange.newValue, createdObjects, true) + } + } + } + return change + } + + private def T getOriginalForCopy(ResourceCopier copier, T copy, Map createdObjects, boolean isCreateChange) { + if (isReadOnlyEObject(copy)) { + return copy + } + var original = copier.getOriginalForCopy(copy) + if (original === null) { + original = createdObjects.get(copy) + if (original === null && isCreateChange) { + original = EcoreUtil.create(copy.eClass) as T + createdObjects.put(copy, original) + } + } + checkState(original !== null, "could not find original for %s", copy) + checkState(original.eClass == copy.eClass, "mismatching classes for original %s and copy %s", original, copy) + return original as T + } + + private def isReadOnlyEObject(EObject eObject) { + return eObject.eResource() !== null && eObject.eResource().getURI() !== null + && isReadOnlyUri(eObject.eResource().getURI()); + } + + private def isReadOnlyUri(URI uri) { + return isPathmap(uri) || uri.isArchive(); + } } diff --git a/bundles/tools.vitruv.framework.views/src/tools/vitruv/framework/views/changederivation/StateBasedChangeResolutionStrategy.xtend b/bundles/tools.vitruv.framework.views/src/tools/vitruv/framework/views/changederivation/StateBasedChangeResolutionStrategy.xtend index 4ccd82a26c..0d53a3a0e4 100644 --- a/bundles/tools.vitruv.framework.views/src/tools/vitruv/framework/views/changederivation/StateBasedChangeResolutionStrategy.xtend +++ b/bundles/tools.vitruv.framework.views/src/tools/vitruv/framework/views/changederivation/StateBasedChangeResolutionStrategy.xtend @@ -1,6 +1,7 @@ package tools.vitruv.framework.views.changederivation import org.eclipse.emf.ecore.resource.Resource +import org.eclipse.emf.ecore.resource.ResourceSet import tools.vitruv.change.composite.description.VitruviusChange /** @@ -10,22 +11,35 @@ import tools.vitruv.change.composite.description.VitruviusChange interface StateBasedChangeResolutionStrategy { /** - * Resolves the state-based delta of two resources and returns the correlating change sequences. + * Resolves the state-based delta of two resources and returns the correlating change sequence. + * Any element in the returned {@VitruviusChange} must be contained in the {@link ResourceSet} of the {@code oldState} + * except for newly created elements which are not contained in any resource. The {@code oldState} remains unmodified + * from calling this method. This implies that any element contained in the returned {@link VitruviusChange} is also based + * on the old state. + * * @param newState is the new state of the resource, must not be null and must not contain proxies. * @param oldState is the current or old state of the resource, must not be null and must not contain proxies. * @return a {@link VitruviusChange} that contains the individual change sequence. */ def VitruviusChange getChangeSequenceBetween(Resource newState, Resource oldState) - + /** - * Resolves the state-based delta for creating the given resource and returns the correlating change sequences. + * Resolves the state-based delta for creating the given resource and returns the correlating change sequence. + * Any element in the returned {@VitruviusChange} must be contained in the {@link ResourceSet} of the {@code oldState} + * except for newly created elements which are not contained in any resource. As the returned {@VitruviusChange} is not + * applied to any {@link ResourceSet}, its contained elements do not reflect the state of {@code newState}. + * * @param newState is the new state of the resource, must not be null and must not contain proxies. * @return a {@link VitruviusChange} that contains the individual change sequence. */ def VitruviusChange getChangeSequenceForCreated(Resource newState) - + /** - * Resolves the state-based delta for deleting the given resource and returns the correlating change sequences. + * Resolves the state-based delta for deleting the given resource and returns the correlating change sequence. + * Any element in the returned {@VitruviusChange} must be contained in the {@link ResourceSet} of the {@code oldState}. + * The {@code oldState} remains unmodified from calling this method. This implies that any element contained in the + * returned {@link VitruviusChange} is also based on the old state. + * * @param oldState is the new state of the resource, must not be null and must not contain proxies. * @return a {@link VitruviusChange} that contains the individual change sequence. */