/*******************************************************************************
* Copyright (c) 2016 Obeo and others.
* 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.tests.merge;
import static com.google.common.base.Predicates.and;
import static com.google.common.collect.Collections2.permutations;
import static com.google.common.collect.Iterables.find;
import static org.eclipse.emf.compare.DifferenceSource.LEFT;
import static org.eclipse.emf.compare.DifferenceSource.RIGHT;
import static org.eclipse.emf.compare.utils.EMFComparePredicates.added;
import static org.eclipse.emf.compare.utils.EMFComparePredicates.fromSide;
import static org.eclipse.emf.compare.utils.EMFComparePredicates.moved;
import static org.eclipse.emf.compare.utils.EMFComparePredicates.removed;
import static org.junit.Assert.assertEquals;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.eclipse.emf.common.util.BasicMonitor;
import org.eclipse.emf.compare.Comparison;
import org.eclipse.emf.compare.Diff;
import org.eclipse.emf.compare.DifferenceSource;
import org.eclipse.emf.compare.EMFCompare;
import org.eclipse.emf.compare.Match;
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.scope.IComparisonScope;
import org.eclipse.emf.compare.tests.conflict.data.ConflictInputData;
import org.eclipse.emf.compare.tests.nodes.NodesPackage;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.resource.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
/**
* This use case features 12 distinct differences of all types, with 4 real conflicts and 1 pseudo conflict.
* The test makes sure that whatever the order in which the operations are performed, as long the user starts
* by rejecting all 'remote' diffs before accepting all other diffs, the 2 sides of the comparison are equal.
* The combinatorial number (34560 combinations) has been limited by dealing with pseudo-conflicting diff and
* non-conflicting diffs after dealing with all conflicts and not only after rejecting the conflicting diffs
* on the remote side.
* <ol>
* <li>Left : Node8 added</li>
* <li>Left : Node9 added</li>
* <li>Left : Node1 moved</li>
* <li>Left : Node0 added</li>
* <li>Left : Node5 removed</li>
* <li>Left : Node6 removed</li>
* <li>Left : Node7 removed</li>
* <li>Right : Node6 moved</li>
* <li>Right : Node9 added</li>
* <li>Right : Node0 added</li>
* <li>Right : Node1 moved</li>
* <li>Right : Node5 removed</li>
* </ol>
* <ul>
* <li>Real conflict : Node0 (Adding the same value at different indices)</li>
* <li>Real conflict : Node1 (Moving the same value to different indices on both sides)</li>
* <li>Real conflict : Node6 (Moving and deleting the same value)</li>
* <li>Real conflict : Node9 (Adding the same value at different indices)</li>
* <li>Pseudo conflict : Node5 (Removing the same value on both sides)</li>
* </ul>
* For reference,
* <ul>
* <li>"original" is : {Node1, Node2, Node3, Node4, Node5, Node6, Node7}</li>
* <li>"left" is : {Node8, Node9, Node2, Node3, Node4, Node1, Node0}</li>
* <li>"right" is : {Node6, Node2, Node9, Node3, Node0, Node1, Node4, Node7}</li>
* </ul>
*
* @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
* @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
*/
@RunWith(Parameterized.class)
public class ComplexMergeTest {
private ConflictInputData conflictInput = new ConflictInputData();
private IMerger.Registry mergerRegistry = IMerger.RegistryImpl.createStandaloneInstance();
private List<Predicate<? super Diff>> rightConflictPermutation;
private List<Predicate<? super Diff>> leftConflictPermutation;
private List<Predicate<? super Diff>> otherPermutation;
@SuppressWarnings("unchecked")
@Parameters
public static Iterable<Object[]> data() {
Collection<List<Predicate<? super Diff>>> rightConflictPermutations = permutations(
Arrays.<Predicate<? super Diff>> asList(added("Root.Node0"), //$NON-NLS-1$
moved("Root.Node1", "containmentRef1"), //$NON-NLS-1$ //$NON-NLS-2$
moved("Root.Node6", //$NON-NLS-1$
"containmentRef1"), //$NON-NLS-1$
added("Root.Node9"))); //$NON-NLS-1$
Collection<List<Predicate<? super Diff>>> leftConflictPermutations = permutations(
Arrays.<Predicate<? super Diff>> asList(added("Root.Node0"), //$NON-NLS-1$
moved("Root.Node1", "containmentRef1"), removed("Root.Node5"), removed("Root.Node6"), //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
added("Root.Node9"))); //$NON-NLS-1$
Collection<List<Predicate<? super Diff>>> otherPermutations = permutations(
Arrays.<Predicate<? super Diff>> asList(and(fromSide(LEFT), removed("Root.Node5")), //$NON-NLS-1$
and(fromSide(LEFT), removed("Root.Node7")), and( //$NON-NLS-1$
fromSide(LEFT), added("Root.Node8")))); //$NON-NLS-1$
List<Object[]> data = new ArrayList<Object[]>();
for (List<Predicate<? super Diff>> otherPermutation : otherPermutations) {
for (List<Predicate<? super Diff>> rightConflictPermutation : rightConflictPermutations) {
for (List<Predicate<? super Diff>> leftConflictPermutation : leftConflictPermutations) {
data.add(new Object[] {leftConflictPermutation, rightConflictPermutation,
otherPermutation });
}
}
}
return data;
}
public ComplexMergeTest(List<Predicate<? super Diff>> leftConflictpermutation,
List<Predicate<? super Diff>> rightConflictPermutation,
List<Predicate<? super Diff>> otherPermutation) {
this.rightConflictPermutation = rightConflictPermutation;
this.leftConflictPermutation = leftConflictpermutation;
this.otherPermutation = otherPermutation;
}
/**
* make sure that on a complex case, it's possible to merge left to right all conflicting right diffs
* (i.e. reject them all) and then merge left to right all the other differences and always get 2
* identical models.
*
* @throws IOException
*/
@Test
public void testLeftToRight() throws IOException {
final Resource left = conflictInput.getComplexLeft();
final Resource origin = conflictInput.getComplexOrigin();
final Resource right = conflictInput.getComplexRight();
final IComparisonScope scope = new DefaultComparisonScope(left, right, origin);
EMFCompare emfc = EMFCompare.builder().build();
Comparison comp = emfc.compare(scope);
for (Predicate<? super Diff> conflictingNode : rightConflictPermutation) {
Diff diff = getDiff(comp, RIGHT, conflictingNode);
copyLeftToRight(diff);
}
for (Predicate<? super Diff> conflictingNode : leftConflictPermutation) {
Diff diff = getDiff(comp, LEFT, conflictingNode);
copyLeftToRight(diff);
}
for (Predicate<? super Diff> otherNode : otherPermutation) {
Diff diff = getDiff(comp, otherNode);
copyLeftToRight(diff);
}
// Left and Right should now be equal
assertEqualContents(comp, getNodes(left), getNodes(right));
}
/**
* make sure that on a complex case, it's possible to merge left to right all conflicting right diffs
* (i.e. reject them all) and then merge left to right all the other differences and always get 2
* identical models.
*
* @throws IOException
*/
@Test
public void testRightToLeft() throws IOException {
final Resource left = conflictInput.getComplexLeft();
final Resource origin = conflictInput.getComplexOrigin();
final Resource right = conflictInput.getComplexRight();
final IComparisonScope scope = new DefaultComparisonScope(left, right, origin);
EMFCompare emfc = EMFCompare.builder().build();
Comparison comp = emfc.compare(scope);
for (Predicate<? super Diff> conflictingNode : leftConflictPermutation) {
Diff diff = getDiff(comp, LEFT, conflictingNode);
copyRightToLeft(diff);
}
for (Predicate<? super Diff> conflictingNode : rightConflictPermutation) {
Diff diff = getDiff(comp, RIGHT, conflictingNode);
copyRightToLeft(diff);
}
for (Predicate<? super Diff> otherNode : otherPermutation) {
Diff diff = getDiff(comp, otherNode);
copyRightToLeft(diff);
}
// Left and Right should now be equal
assertEqualContents(comp, getNodes(left), getNodes(right));
}
private List<EObject> getNodes(Resource r) {
EObject container = r.getContents().get(0);
return getAsList(container, NodesPackage.eINSTANCE.getNode_ContainmentRef1());
}
private Diff getDiff(Comparison comp, DifferenceSource side, Predicate<? super Diff> predicate) {
return find(comp.getDifferences(), and(fromSide(side), predicate));
}
private Diff getDiff(Comparison comp, Predicate<? super Diff> predicate) {
return find(comp.getDifferences(), predicate);
}
private void copyRightToLeft(Diff diff) {
new BatchMerger(mergerRegistry).copyAllRightToLeft(Arrays.asList(diff), new BasicMonitor());
}
private void copyLeftToRight(Diff diff) {
new BatchMerger(mergerRegistry).copyAllLeftToRight(Arrays.asList(diff), new BasicMonitor());
}
/**
* Ensure that the two given lists contain the same elements in the same order. The kind of list does not
* matter.
*
* @param list1
* First of the two lists to compare.
* @param list2
* Second of the two lists to compare.
*/
private static <T extends EObject> void assertEqualContents(Comparison comparison, List<T> list1,
List<T> list2) {
final int size = list1.size();
assertEquals(size, list2.size());
for (int i = 0; i < size; i++) {
final EObject eObject1 = list1.get(i);
final EObject eObject2 = list2.get(i);
final Match match = comparison.getMatch(eObject1);
if (match.getLeft() == eObject1) {
assertEquals(match.getRight(), eObject2);
} else {
assertEquals(match.getRight(), eObject1);
assertEquals(match.getLeft(), eObject2);
}
}
}
@SuppressWarnings("unchecked")
private static List<EObject> getAsList(EObject object, EReference feature) {
if (object != null) {
Object value = object.eGet(feature, false);
final List<EObject> asList;
if (value instanceof List) {
asList = (List<EObject>)value;
} else if (value instanceof Iterable) {
asList = ImmutableList.copyOf((Iterable<EObject>)value);
} else if (value != null) {
asList = ImmutableList.of((EObject)value);
} else {
asList = Collections.emptyList();
}
return asList;
}
return Collections.emptyList();
}
}