diff --git a/bundles/tools.vitruv.change.changederivation/src/tools/vitruv/change/changederivation/AbstractStateBasedChangeResolutionStrategy.xtend b/bundles/tools.vitruv.change.changederivation/src/tools/vitruv/change/changederivation/AbstractStateBasedChangeResolutionStrategy.xtend new file mode 100644 index 00000000..58a0365d --- /dev/null +++ b/bundles/tools.vitruv.change.changederivation/src/tools/vitruv/change/changederivation/AbstractStateBasedChangeResolutionStrategy.xtend @@ -0,0 +1,148 @@ +package tools.vitruv.change.changederivation + +import edu.kit.ipd.sdq.commons.util.org.eclipse.emf.ecore.resource.ResourceCopier +import java.util.Collection +import org.eclipse.emf.common.notify.Notifier +import org.eclipse.emf.common.util.BasicMonitor +import org.eclipse.emf.compare.EMFCompare +import org.eclipse.emf.compare.diff.IDiffEngine +import org.eclipse.emf.compare.match.IMatchEngine.Factory +import org.eclipse.emf.compare.match.impl.MatchEngineFactoryRegistryImpl +import org.eclipse.emf.compare.merge.BatchMerger +import org.eclipse.emf.compare.merge.IMerger +import org.eclipse.emf.compare.postprocessor.IPostProcessor +import org.eclipse.emf.compare.postprocessor.PostProcessorDescriptorRegistryImpl +import org.eclipse.emf.compare.scope.DefaultComparisonScope +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 tools.vitruv.change.atomic.EChangeIdManager +import tools.vitruv.change.atomic.id.IdResolver +import tools.vitruv.change.composite.description.VitruviusChange +import tools.vitruv.change.composite.recording.ChangeRecorder + +import static com.google.common.base.Preconditions.checkArgument +import static 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.getReferencedProxies +import static extension tools.vitruv.change.atomic.resolve.EChangeIdResolverAndApplicator.applyBackward +import static extension tools.vitruv.change.atomic.resolve.EChangeIdResolverAndApplicator.applyForward + +/** + * Abstract base class for StateBasedChangeResolutionStrategies that uses EMFCompare + * to resolve a diff to a sequence of individual changes. + */ +abstract class AbstractStateBasedChangeResolutionStrategy implements StateBasedChangeResolutionStrategy { + + private def checkNoProxies(Resource resource, String stateNotice) { + val proxies = resource.referencedProxies + checkArgument(proxies.empty, "%s '%s' should not contain proxies, but contains the following: %s", stateNotice, + resource.URI, String.join(", ", proxies.map[toString])) + } + + override getChangeSequenceBetween(Resource newState, Resource oldState) { + checkArgument(oldState !== null && newState !== null, "old state or new state must not be null!") + newState.checkNoProxies("new state") + oldState.checkNoProxies("old state") + val monitoredResourceSet = new ResourceSetImpl() + val currentStateCopy = ResourceCopier.copyViewResource(oldState, monitoredResourceSet) + return currentStateCopy.record [ + if (oldState.URI != newState.URI) { + currentStateCopy.URI = newState.URI + } + compareStatesAndReplayChanges(newState, currentStateCopy) + ] + } + + override getChangeSequenceForCreated(Resource newState) { + checkArgument(newState !== null, "new state must not be null!") + newState.checkNoProxies("new state") + // It is possible that root elements are automatically generated during resource creation (e.g., Java packages). + // Thus, we create the resource and then monitor the re-insertion of the elements + val monitoredResourceSet = withGlobalFactories(new ResourceSetImpl()); + val newResource = monitoredResourceSet.createResource(newState.URI) + newResource.contents.clear() + return newResource.record [ + newResource.contents += EcoreUtil.copyAll(newState.contents) + ] + } + + override getChangeSequenceForDeleted(Resource oldState) { + checkArgument(oldState !== null, "old state must not be null!") + oldState.checkNoProxies("old state") + // Setup resolver and copy state: + val monitoredResourceSet = new ResourceSetImpl() + val currentStateCopy = ResourceCopier.copyViewResource(oldState, monitoredResourceSet) + return currentStateCopy.record [ + currentStateCopy.contents.clear() + ] + } + + private def record(Resource resource, ()=>void function) { + try (val changeRecorder = new ChangeRecorder(resource.resourceSet)) { + changeRecorder.beginRecording + changeRecorder.addToRecording(resource) + function.apply() + val recordedChanges = changeRecorder.endRecording + assignIds(recordedChanges, resource.resourceSet) + return recordedChanges.unresolve + } + } + + private def void assignIds(VitruviusChange recordedChange, ResourceSet resourceSet) { + val changes = recordedChange.EChanges + val idResolver = IdResolver.create(resourceSet) + val eChangeIdManager = new EChangeIdManager(idResolver) + changes.toList.reverseView.forEach[applyBackward] + changes.forEach[ change | + eChangeIdManager.setOrGenerateIds(change) + change.applyForward(idResolver) + ] + } + + /** + * Compares states using the EMFCompareInstance provided by {@link #getEMFCompareInstance()} and replays the changes to the current state. + */ + private def compareStatesAndReplayChanges(Notifier newState, Notifier currentState) { + val scope = new DefaultComparisonScope(newState, currentState, null) + val differences = buildEMFCompare().compare(scope).differences + // Replay the EMF compare differences + val mergerRegistry = IMerger.RegistryImpl.createStandaloneInstance() + val merger = new BatchMerger(mergerRegistry) + merger.copyAllLeftToRight(differences, new BasicMonitor) + } + + private def buildEMFCompare() { + val usedMatchEngineFactoryRegistry = MatchEngineFactoryRegistryImpl.createStandaloneInstance() + getMatchEngineFactories.forEach[it | + it.ranking = 20 + usedMatchEngineFactoryRegistry.add(it) + ] + + val usedPostProcessorRegistry = new PostProcessorDescriptorRegistryImpl() + getPostProcessors.forEach[it | usedPostProcessorRegistry.put(it.getInstanceClassName, it)] + + return (EMFCompare.builder => [ + matchEngineFactoryRegistry = usedMatchEngineFactoryRegistry + diffEngine = getDiffEngine() + postProcessorRegistry = usedPostProcessorRegistry + ]).build + } + + /** + * {@link Factory}'s (MatchEngine + rank + is MatchEngine suitable for comparison scope) used by EMFCompare + * The rank will be overwritten by this class. To ensure that it has a greater value than the default implementation. + */ + abstract protected def Collection getMatchEngineFactories(); + + /** + * {@link IDiffEngine} used by EMFCompare + */ + abstract protected def IDiffEngine getDiffEngine(); + + /** + * {@link IPostProcessor.Descriptor}'s (PostProcessor + Pattern where to be applied) used by EMFCompare + */ + abstract protected def Collection getPostProcessors(); +} diff --git a/bundles/tools.vitruv.change.changederivation/src/tools/vitruv/change/changederivation/DefaultStateBasedChangeResolutionStrategy.xtend b/bundles/tools.vitruv.change.changederivation/src/tools/vitruv/change/changederivation/DefaultStateBasedChangeResolutionStrategy.xtend index f3a21cae..4dfd8dc5 100644 --- a/bundles/tools.vitruv.change.changederivation/src/tools/vitruv/change/changederivation/DefaultStateBasedChangeResolutionStrategy.xtend +++ b/bundles/tools.vitruv.change.changederivation/src/tools/vitruv/change/changederivation/DefaultStateBasedChangeResolutionStrategy.xtend @@ -1,37 +1,17 @@ package tools.vitruv.change.changederivation -import edu.kit.ipd.sdq.commons.util.org.eclipse.emf.ecore.resource.ResourceCopier -import org.eclipse.emf.common.notify.Notifier -import org.eclipse.emf.common.util.BasicMonitor -import org.eclipse.emf.compare.EMFCompare -import org.eclipse.emf.compare.match.impl.MatchEngineFactoryImpl -import org.eclipse.emf.compare.match.impl.MatchEngineFactoryRegistryImpl -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.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 tools.vitruv.change.atomic.EChangeIdManager -import tools.vitruv.change.atomic.id.IdResolver -import tools.vitruv.change.composite.description.VitruviusChange -import tools.vitruv.change.composite.recording.ChangeRecorder - -import static com.google.common.base.Preconditions.checkArgument -import static 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.getReferencedProxies -import static extension tools.vitruv.change.atomic.resolve.EChangeIdResolverAndApplicator.applyBackward -import static extension tools.vitruv.change.atomic.resolve.EChangeIdResolverAndApplicator.applyForward +import java.util.List +import org.eclipse.emf.compare.match.impl.MatchEngineFactoryImpl +import org.eclipse.emf.compare.diff.DiffBuilder +import org.eclipse.emf.compare.diff.DefaultDiffEngine /** - * This default strategy for diff based state changes uses EMFCompare to resolve a - * diff to a sequence of individual changes. + * This is a default strategy to resolve a diff as a sequence of individual changes */ -class DefaultStateBasedChangeResolutionStrategy implements StateBasedChangeResolutionStrategy { - /** The identifier matching behavior used by this strategy */ +class DefaultStateBasedChangeResolutionStrategy extends AbstractStateBasedChangeResolutionStrategy implements StateBasedChangeResolutionStrategy { + + /** The identifier matching behavior used by this strategy */ public val UseIdentifiers useIdentifiers /** @@ -49,87 +29,17 @@ class DefaultStateBasedChangeResolutionStrategy implements StateBasedChangeResol new(UseIdentifiers useIdentifiers) { this.useIdentifiers = useIdentifiers } - - private def checkNoProxies(Resource resource, String stateNotice) { - val proxies = resource.referencedProxies - checkArgument(proxies.empty, "%s '%s' should not contain proxies, but contains the following: %s", stateNotice, - resource.URI, String.join(", ", proxies.map[toString])) - } - - override getChangeSequenceBetween(Resource newState, Resource oldState) { - checkArgument(oldState !== null && newState !== null, "old state or new state must not be null!") - newState.checkNoProxies("new state") - oldState.checkNoProxies("old state") - val monitoredResourceSet = new ResourceSetImpl() - val currentStateCopy = ResourceCopier.copyViewResource(oldState, monitoredResourceSet) - return currentStateCopy.record [ - if (oldState.URI != newState.URI) { - currentStateCopy.URI = newState.URI - } - compareStatesAndReplayChanges(newState, currentStateCopy) - ] - } - - override getChangeSequenceForCreated(Resource newState) { - checkArgument(newState !== null, "new state must not be null!") - newState.checkNoProxies("new state") - // It is possible that root elements are automatically generated during resource creation (e.g., Java packages). - // Thus, we create the resource and then monitor the re-insertion of the elements - val monitoredResourceSet = withGlobalFactories(new ResourceSetImpl()); - val newResource = monitoredResourceSet.createResource(newState.URI) - newResource.contents.clear() - return newResource.record [ - newResource.contents += EcoreUtil.copyAll(newState.contents) - ] - } - - override getChangeSequenceForDeleted(Resource oldState) { - checkArgument(oldState !== null, "old state must not be null!") - oldState.checkNoProxies("old state") - // Setup resolver and copy state: - val monitoredResourceSet = new ResourceSetImpl() - val currentStateCopy = ResourceCopier.copyViewResource(oldState, monitoredResourceSet) - return currentStateCopy.record [ - currentStateCopy.contents.clear() - ] - } - - private def record(Resource resource, ()=>void function) { - try (val changeRecorder = new ChangeRecorder(resource.resourceSet)) { - changeRecorder.beginRecording - changeRecorder.addToRecording(resource) - function.apply() - val recordedChanges = changeRecorder.endRecording - assignIds(recordedChanges, resource.resourceSet) - return recordedChanges.unresolve - } - } - - private def void assignIds(VitruviusChange recordedChange, ResourceSet resourceSet) { - val changes = recordedChange.EChanges - val idResolver = IdResolver.create(resourceSet) - val eChangeIdManager = new EChangeIdManager(idResolver) - changes.toList.reverseView.forEach[applyBackward] - changes.forEach[ change | - eChangeIdManager.setOrGenerateIds(change) - change.applyForward(idResolver) - ] - } - - /** - * Compares states using EMFCompare and replays the changes to the current state. - */ - private def compareStatesAndReplayChanges(Notifier newState, Notifier currentState) { - val scope = new DefaultComparisonScope(newState, currentState, null) - val emfCompare = (EMFCompare.builder => [ - matchEngineFactoryRegistry = MatchEngineFactoryRegistryImpl.createStandaloneInstance => [ - add(new MatchEngineFactoryImpl(useIdentifiers)) - ] - ]).build - val differences = emfCompare.compare(scope).differences - // Replay the EMF compare differences - val mergerRegistry = IMerger.RegistryImpl.createStandaloneInstance() - val merger = new BatchMerger(mergerRegistry) - merger.copyAllLeftToRight(differences, new BasicMonitor) - } + + override protected getMatchEngineFactories() { + return List.of(new MatchEngineFactoryImpl(useIdentifiers)) + } + + override protected getDiffEngine() { + return new DefaultDiffEngine(new DiffBuilder()); + } + + override protected getPostProcessors() { + return List.of() + } + }