blob: 94d6e5c38eca6f7bb955a78ba15b5c2934db034b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2020 Obeo.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.emf.compare.diagram.sirius.internal;
import static org.eclipse.emf.compare.DifferenceKind.ADD;
import static org.eclipse.emf.compare.DifferenceKind.DELETE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.emf.compare.Comparison;
import org.eclipse.emf.compare.Diff;
import org.eclipse.emf.compare.EMFCompare;
import org.eclipse.emf.compare.ReferenceChange;
import org.eclipse.emf.compare.diagram.internal.CompareDiagramPostProcessor;
import org.eclipse.emf.compare.domain.IMergeRunnable;
import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.actions.MergeRunnableImpl;
import org.eclipse.emf.compare.internal.merge.MergeMode;
import org.eclipse.emf.compare.merge.AbstractMerger;
import org.eclipse.emf.compare.merge.CachingDiffRelationshipComputer;
import org.eclipse.emf.compare.merge.IMerger;
import org.eclipse.emf.compare.postprocessor.BasicPostProcessorDescriptorImpl;
import org.eclipse.emf.compare.postprocessor.IPostProcessor;
import org.eclipse.emf.compare.postprocessor.PostProcessorDescriptorRegistryImpl;
import org.eclipse.emf.compare.rcp.EMFCompareRCPPlugin;
import org.eclipse.emf.compare.scope.DefaultComparisonScope;
import org.eclipse.emf.compare.tests.framework.AbstractInputData;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.sirius.viewpoint.DMappingBased;
import org.eclipse.sirius.viewpoint.DSemanticDecorator;
import org.junit.Before;
import org.junit.Test;
/**
* This test is related to the bug <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=561458">561458.</a>
*
* @author <a href="mailto:glenn.plouhinec@obeo.fr">Glenn Plouhinec</a>
*/
@SuppressWarnings("restriction")
public class TestBug561458 {
/**
* The main comparison.
*/
private Comparison comparison;
/**
* The "addedContainers.aird" data resource.
*/
private Resource addedContainers;
/**
* The "deletedContainers.aird" data resource.
*/
private Resource deletedContainers;
/**
* The "singleAddedContainer.aird" data resource.
*/
private Resource singleAddedContainer;
/**
* The "singleDeletedContainer.aird" data resource.
*/
private Resource singleDeletedContainer;
/**
* Used to initialize the comparison scope.
*/
private Resource origin;
/**
* The PostProcessors registry.
*/
private IPostProcessor.Descriptor.Registry<String> registry;
/**
* Used to initialize a default merger registry for merge action.
*/
private IMerger.Registry mergerRegistry;
/**
* Used to merge differences.
*/
private IMergeRunnable mergeRunnable;
/**
* Set up the test models.
* <p>
* addedContainers.nodes: In this model we have a tree nodes structured like this:
* <ul>
* <li>A Node "A" that holds a Node "B" that holds a Node "C" that holds a Node "D".</li>
* </ul>
* deletedContainers.nodes: In this model "C" and "D" have been deleted.
* </p>
* <p>
* A 2-way comparison between their representations files addedContainers.aird and deletedContainers.aird
* gives us these differences in particular:
* <ul>
* <li>A ReferenceChange difference for the addition/deletion of "C" DNodeContainer.</li>
* <li>A ReferenceChange difference for the addition/deletion of "D" DNodeContainer.</li>
* <li>A ReferenceChange difference for the modification of "C.actualMapping" ContainerMapping.</li>
* <li>A ReferenceChange difference for the modification of "D.actualMapping" ContainerMapping.</li>
* </ul>
* </p>
* <p>
* singleAddedContainer.nodes: In this model we have a tree nodes structured like this:
* <ul>
* <li>A Node "A" that holds a Node "B".</li>
* </ul>
* singleDeletedContainer.nodes: In this model "B" has been deleted.
* </p>
* <p>
* A 2-way comparison between their representations files singleAddedContainer.aird and
* singleDeletedContainer.aird gives us these differences in particular:
* <ul>
* <li>A ReferenceChange difference for the addition/deletion of "B" DNodeContainer.</li>
* <li>A ReferenceChange difference for the modification of "B.actualMapping" ContainerMapping.</li>
* </ul>
* </p>
*
* @throws IOException
* Thrown if we could not access either this class' resource, or the file towards which
* <code>string</code> points.
*/
@Before
public void setUp() throws IOException {
Bug561458 inputData = new Bug561458();
addedContainers = inputData.getResource("addedContainers.aird"); //$NON-NLS-1$
deletedContainers = inputData.getResource("deletedContainers.aird"); //$NON-NLS-1$
singleAddedContainer = inputData.getResource("singleAddedContainer.aird"); //$NON-NLS-1$
singleDeletedContainer = inputData.getResource("singleDeletedContainer.aird"); //$NON-NLS-1$
origin = null;
registry = registerPostProcessors();
mergerRegistry = EMFCompareRCPPlugin.getDefault().getMergerRegistry();
}
/**
* Tests that the added node difference implies its mapping difference.
*/
@Test
public void testSingleAddedContainerImpliesMapping() {
DefaultComparisonScope scope = new DefaultComparisonScope(singleAddedContainer,
singleDeletedContainer, origin);
comparison = EMFCompare.builder().setPostProcessorRegistry(registry).build().compare(scope);
List<ReferenceChange> decorators = getAddedOrRemovedDecorators();
ReferenceChange addedDiff = decorators.get(0);
DSemanticDecorator value = (DSemanticDecorator)addedDiff.getValue();
Diff mapping = comparison.getDifferences(((DMappingBased)value).getMapping()).get(0);
assertTrue(addedDiff.getKind() == ADD);
assertEquals(mapping, addedDiff.getImplies().get(0));
}
/**
* Tests that the deleted node difference is implied by its mapping difference.
*/
@Test
public void testSingleDeletedContainerImpliedByMapping() {
DefaultComparisonScope scope = new DefaultComparisonScope(singleDeletedContainer,
singleAddedContainer, origin);
comparison = EMFCompare.builder().setPostProcessorRegistry(registry).build().compare(scope);
List<ReferenceChange> decorators = getAddedOrRemovedDecorators();
ReferenceChange deletedDiff = decorators.get(0);
DSemanticDecorator value = (DSemanticDecorator)deletedDiff.getValue();
Diff mapping = comparison.getDifferences(((DMappingBased)value).getMapping()).get(0);
assertTrue(deletedDiff.getKind() == DELETE);
assertEquals(mapping, deletedDiff.getImpliedBy().get(0));
}
/**
* Tests that the added nodes differences implies their mapping difference.
*/
@Test
public void testAddedContainersImpliesMapping() {
DefaultComparisonScope scope = new DefaultComparisonScope(addedContainers, deletedContainers, origin);
comparison = EMFCompare.builder().setPostProcessorRegistry(registry).build().compare(scope);
List<ReferenceChange> decorators = getAddedOrRemovedDecorators();
ReferenceChange firstAddedDiff = decorators.get(0);
DSemanticDecorator firstValue = (DSemanticDecorator)firstAddedDiff.getValue();
Diff firstMapping = comparison.getDifferences(((DMappingBased)firstValue).getMapping()).get(0);
ReferenceChange secondAddedDiff = decorators.get(1);
DSemanticDecorator secondValue = (DSemanticDecorator)secondAddedDiff.getValue();
Diff secondMapping = comparison.getDifferences(((DMappingBased)secondValue).getMapping()).get(0);
assertTrue(firstAddedDiff.getKind() == ADD);
assertTrue(secondAddedDiff.getKind() == ADD);
assertEquals(firstMapping, firstAddedDiff.getImplies().get(0));
assertEquals(secondMapping, secondAddedDiff.getImplies().get(0));
}
/**
* Tests that the deleted nodes differences are implied by their mapping difference.
*/
@Test
public void testDeletedContainersImpliedByMapping() {
DefaultComparisonScope scope = new DefaultComparisonScope(deletedContainers, addedContainers, origin);
comparison = EMFCompare.builder().setPostProcessorRegistry(registry).build().compare(scope);
List<ReferenceChange> decorators = getAddedOrRemovedDecorators();
ReferenceChange firstDeletedDiff = decorators.get(0);
DSemanticDecorator firstValue = (DSemanticDecorator)firstDeletedDiff.getValue();
Diff firstMapping = comparison.getDifferences(((DMappingBased)firstValue).getMapping()).get(0);
ReferenceChange secondDeletedDiff = decorators.get(1);
DSemanticDecorator secondValue = (DSemanticDecorator)secondDeletedDiff.getValue();
Diff secondMapping = comparison.getDifferences(((DMappingBased)secondValue).getMapping()).get(0);
assertTrue(firstDeletedDiff.getKind() == DELETE);
assertTrue(secondDeletedDiff.getKind() == DELETE);
assertEquals(firstMapping, firstDeletedDiff.getImpliedBy().get(0));
assertEquals(secondMapping, secondDeletedDiff.getImpliedBy().get(0));
}
/**
* Tests that the merge from right to left of a deleted container does not fail.
*/
@Test
public void testMergeRightToLeftDeletedContainer() {
DefaultComparisonScope scope = new DefaultComparisonScope(singleDeletedContainer,
singleAddedContainer, origin);
comparison = EMFCompare.builder().setPostProcessorRegistry(registry).build().compare(scope);
boolean leftToRight = false;
mergeDiffs(comparison.getDifferences(), leftToRight);
comparison.getDifferences().forEach(diff -> assertTrue(AbstractMerger.isInTerminalState(diff)));
}
/**
* Tests that the merge from left to right of a deleted container does not fail.
*/
@Test
public void testMergeLeftToRightDeletedContainer() {
DefaultComparisonScope scope = new DefaultComparisonScope(singleDeletedContainer,
singleAddedContainer, origin);
comparison = EMFCompare.builder().setPostProcessorRegistry(registry).build().compare(scope);
boolean leftToRight = true;
mergeDiffs(comparison.getDifferences(), leftToRight);
comparison.getDifferences().forEach(diff -> assertTrue(AbstractMerger.isInTerminalState(diff)));
}
/**
* Tests that the merge from right to left of an added container does not fail.
*/
@Test
public void testMergeRightToLeftAddedContainer() {
DefaultComparisonScope scope = new DefaultComparisonScope(singleAddedContainer,
singleDeletedContainer, origin);
comparison = EMFCompare.builder().setPostProcessorRegistry(registry).build().compare(scope);
boolean leftToRight = false;
mergeDiffs(comparison.getDifferences(), leftToRight);
comparison.getDifferences().forEach(diff -> assertTrue(AbstractMerger.isInTerminalState(diff)));
}
/**
* Tests that the merge from left to right of an added container does not fail.
*/
@Test
public void testMergeLeftToRightAddedContainer() {
DefaultComparisonScope scope = new DefaultComparisonScope(singleAddedContainer,
singleDeletedContainer, origin);
comparison = EMFCompare.builder().setPostProcessorRegistry(registry).build().compare(scope);
boolean leftToRight = true;
mergeDiffs(comparison.getDifferences(), leftToRight);
comparison.getDifferences().forEach(diff -> assertTrue(AbstractMerger.isInTerminalState(diff)));
}
/**
* It creates a collection of differences with DSemanticDecorator type values that have been added or
* deleted.
*
* @see org.eclipse.sirius.viewpoint.DSemanticDecorator
* @return the list of added or removed decorators
*/
protected List<ReferenceChange> getAddedOrRemovedDecorators() {
Stream<ReferenceChange> refChanges = comparison.getDifferences().stream()
.filter(diff -> diff instanceof ReferenceChange
&& ((ReferenceChange)diff).getReference().isContainment())
.map(ReferenceChange.class::cast);
Stream<ReferenceChange> addedOrRemovedSemanticDecorators = refChanges
.filter(diff -> diff.getValue() instanceof DSemanticDecorator);
return addedOrRemovedSemanticDecorators.collect(Collectors.toList());
}
/**
* Used to register new post processors.
*
* @see org.eclipse.emf.compare.diagram.sirius.internal.SiriusDiffPostProcessor
* @see org.eclipse.emf.compare.diagram.internal.CompareDiagramPostProcessor
* @return the register composed of SiriusDiffPostProcessor and CompareDiagramPostProcessor.
*/
protected IPostProcessor.Descriptor.Registry<String> registerPostProcessors() {
final IPostProcessor.Descriptor.Registry<String> postProcessorRegistry = new PostProcessorDescriptorRegistryImpl<String>();
postProcessorRegistry.put(SiriusDiffPostProcessor.class.getName(),
new BasicPostProcessorDescriptorImpl(new SiriusDiffPostProcessor(),
Pattern.compile("http://www.eclipse.org/sirius/1.1.0"), //$NON-NLS-1$
Pattern.compile(""))); //$NON-NLS-1$
postProcessorRegistry.put(CompareDiagramPostProcessor.class.getName(),
new BasicPostProcessorDescriptorImpl(new CompareDiagramPostProcessor(),
Pattern.compile("http://www.eclipse.org/gmf/runtime/1.0.2/notation"), //$NON-NLS-1$
Pattern.compile(""))); //$NON-NLS-1$
return postProcessorRegistry;
}
/**
* Executes a right to left or left to right merge of a list of differences.
*
* @param differences
* the list of differences.
* @param leftToRight
* the merge direction.
*/
protected void mergeDiffs(List<? extends Diff> differences, boolean leftToRight) {
if (leftToRight) {
mergeRunnable = new MergeRunnableImpl(true, true, MergeMode.LEFT_TO_RIGHT,
new CachingDiffRelationshipComputer(mergerRegistry));
mergeRunnable.merge(differences, true, mergerRegistry);
} else {
mergeRunnable = new MergeRunnableImpl(true, true, MergeMode.RIGHT_TO_LEFT,
new CachingDiffRelationshipComputer(mergerRegistry));
mergeRunnable.merge(differences, false, mergerRegistry);
}
}
/**
* Input data for this bug.
*
* @author <a href="mailto:glenn.plouhinec@obeo.fr">Glenn Plouhinec</a>
*/
public class Bug561458 extends AbstractInputData {
/**
* The data path.
*/
private static final String PATH_PREFIX = "data/_561458/"; //$NON-NLS-1$
/**
* Used to load a resource.
*
* @param resourceName
* the resource to load.
* @return the resource loaded.
* @throws IOException
* Thrown if we could not access either this class' resource, or the file towards which
* <code>string</code> points.
*/
public Resource getResource(String resourceName) throws IOException {
StringBuilder resourceURL = new StringBuilder(PATH_PREFIX);
resourceURL.append(resourceName);
return loadFromClassLoader(resourceURL.toString());
}
}
}