Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Dirix2014-11-18 13:34:03 +0000
committerLaurent Goubet2015-02-18 07:55:05 +0000
commitd5a1d68b68126e8e0b65d349ce3b89ff0c4638a5 (patch)
tree8114a17bd949cdd36a511007fb63c0ad96b441b7
parent46df54f5273d656c30ae05a8af5104860477edb5 (diff)
downloadorg.eclipse.emf.compare-d5a1d68b68126e8e0b65d349ce3b89ff0c4638a5.tar.gz
org.eclipse.emf.compare-d5a1d68b68126e8e0b65d349ce3b89ff0c4638a5.tar.xz
org.eclipse.emf.compare-d5a1d68b68126e8e0b65d349ce3b89ff0c4638a5.zip
[452147] Fix handling of diffs for interlocked one-to-one refs
Every difference describes an effect which modifies the model. In a model with interlocked one-to-one references merging a difference can implicitly execute the effect of other differences without being equivalent to them. These differences are added to the directResultingMerges of the AbstractMerger. This patch also modifies the determination of equivalences for the described case in the DefaultEquiEngine. Includes testcases. Bug: 452147 Signed-off-by: Stefan Dirix <sdirix@eclipsesource.com> Change-Id: I8741885c45736f82e2442a32843f7d0afbaf9a67
-rw-r--r--plugins/org.eclipse.emf.compare.tests/src/org/eclipse/emf/compare/tests/merge/MultipleMergeTest.java85
-rw-r--r--plugins/org.eclipse.emf.compare.tests/src/org/eclipse/emf/compare/tests/merge/data/TwoWayMergeInputData.java20
-rw-r--r--plugins/org.eclipse.emf.compare.tests/src/org/eclipse/emf/compare/tests/merge/data/twoway/onetoonerefmerge/ltr/left.nodes11
-rw-r--r--plugins/org.eclipse.emf.compare.tests/src/org/eclipse/emf/compare/tests/merge/data/twoway/onetoonerefmerge/ltr/right.nodes11
-rw-r--r--plugins/org.eclipse.emf.compare.tests/src/org/eclipse/emf/compare/tests/merge/data/twoway/onetoonerefmerge/rtl/left.nodes11
-rw-r--r--plugins/org.eclipse.emf.compare.tests/src/org/eclipse/emf/compare/tests/merge/data/twoway/onetoonerefmerge/rtl/right.nodes11
-rw-r--r--plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/equi/DefaultEquiEngine.java50
-rw-r--r--plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/merge/AbstractMerger.java130
8 files changed, 308 insertions, 21 deletions
diff --git a/plugins/org.eclipse.emf.compare.tests/src/org/eclipse/emf/compare/tests/merge/MultipleMergeTest.java b/plugins/org.eclipse.emf.compare.tests/src/org/eclipse/emf/compare/tests/merge/MultipleMergeTest.java
index 8fb7477bb..19c82580b 100644
--- a/plugins/org.eclipse.emf.compare.tests/src/org/eclipse/emf/compare/tests/merge/MultipleMergeTest.java
+++ b/plugins/org.eclipse.emf.compare.tests/src/org/eclipse/emf/compare/tests/merge/MultipleMergeTest.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2012, 2014 Obeo and others.
+ * Copyright (c) 2012, 2015 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
@@ -7,7 +7,7 @@
*
* Contributors:
* Obeo - initial API and implementation
- * Stefan Dirix - bug 441172
+ * Stefan Dirix - bugs 441172 and 452147
*******************************************************************************/
package org.eclipse.emf.compare.tests.merge;
@@ -48,6 +48,7 @@ import org.eclipse.emf.compare.scope.IComparisonScope;
import org.eclipse.emf.compare.tests.conflict.data.ConflictInputData;
import org.eclipse.emf.compare.tests.equi.data.EquiInputData;
import org.eclipse.emf.compare.tests.fullcomparison.data.identifier.IdentifierMatchInputData;
+import org.eclipse.emf.compare.tests.merge.data.TwoWayMergeInputData;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.resource.Resource;
@@ -60,6 +61,8 @@ public class MultipleMergeTest {
private EquiInputData equivalenceInput = new EquiInputData();
+ private TwoWayMergeInputData twoWayInput = new TwoWayMergeInputData();
+
private IMerger.Registry mergerRegistry = IMerger.RegistryImpl.createStandaloneInstance();
@Test
@@ -1169,6 +1172,84 @@ public class MultipleMergeTest {
assertEquals(0, comparison.getDifferences().size());
}
+ /**
+ * Tests a scenario in which two nodes contain interlocked one-to-one references. The merger must handle
+ * these cases with care since diffs can become redundant during merging.
+ */
+ @Test
+ public void testOneToOneRefMergeL2R() throws IOException {
+ final Resource left = twoWayInput.getOneToOneMergeL2RLeft();
+ final Resource right = twoWayInput.getOneToOneRefMergeL2RRight();
+
+ final IComparisonScope scope = new DefaultComparisonScope(left, right, null);
+ Comparison comparison = EMFCompare.builder().build().compare(scope);
+ final List<Diff> differences = comparison.getDifferences();
+
+ // differences:
+ // 1. Change c.source to c
+ // 2. Change c.destination to c
+ // 3. Change d.source to null
+ // 4. Change d.destination to null
+ // 1,4 and 2,3 are equivalent
+
+ final ReferenceChange setCSourceDiff = (ReferenceChange)Iterators.find(differences.iterator(),
+ changedReference("Root.c", "source", "Root.d", "Root.c"));
+
+ final ReferenceChange setDSourceDiff = (ReferenceChange)Iterators.find(differences.iterator(),
+ changedReference("Root.d", "source", "Root.c", null));
+
+ // By merging diff1 (setCSourceDiff) the model will be in a state where the remaining diffs
+ // describe actions which already occurred.
+ mergerRegistry.getHighestRankingMerger(setCSourceDiff).copyLeftToRight(setCSourceDiff,
+ new BasicMonitor());
+
+ // Check if the non-equivalent diff is also set to merged
+ assertEquals(DifferenceState.MERGED, setDSourceDiff.getState());
+
+ // Check if no differences between models are left
+ comparison = EMFCompare.builder().build().compare(scope);
+ assertEquals(0, comparison.getDifferences().size());
+ }
+
+ /**
+ * Tests a scenario in which two nodes contain interlocked one-to-one references. The merger must handle
+ * these cases with care since diffs can become redundant during merging.
+ */
+ @Test
+ public void testOneToOneRefMergeR2L() throws IOException {
+ final Resource left = twoWayInput.getOneToOneMergeR2LLeft();
+ final Resource right = twoWayInput.getOneToOneRefMergeR2LRight();
+
+ final IComparisonScope scope = new DefaultComparisonScope(left, right, null);
+ Comparison comparison = EMFCompare.builder().build().compare(scope);
+ final List<Diff> differences = comparison.getDifferences();
+
+ // differences:
+ // 1. Change c.source to d
+ // 2. Change c.destination to d
+ // 3. Change d.source to c
+ // 4. Change d.destination to c
+ // 1,4 and 2,3 are equivalent
+
+ final ReferenceChange setCSourceDiff = (ReferenceChange)Iterators.find(differences.iterator(),
+ changedReference("Root.c", "source", "Root.c", "Root.d"));
+
+ final ReferenceChange setDSourceDiff = (ReferenceChange)Iterators.find(differences.iterator(),
+ changedReference("Root.d", "source", null, "Root.c"));
+
+ // By merging diff1 (setCSourceDiff) R2L the model will be in a state where the remaining diffs
+ // describe actions which already occurred.
+ mergerRegistry.getHighestRankingMerger(setCSourceDiff).copyRightToLeft(setCSourceDiff,
+ new BasicMonitor());
+
+ // Check if the non-equivalent diff is marked as merged
+ assertEquals(DifferenceState.MERGED, setDSourceDiff.getState());
+
+ // Check if no differences between models are left
+ comparison = EMFCompare.builder().build().compare(scope);
+ assertEquals(0, comparison.getDifferences().size());
+ }
+
@Test
public void testEquivalenceC7LtoR() throws IOException {
final Resource left = equivalenceInput.getC7Left();
diff --git a/plugins/org.eclipse.emf.compare.tests/src/org/eclipse/emf/compare/tests/merge/data/TwoWayMergeInputData.java b/plugins/org.eclipse.emf.compare.tests/src/org/eclipse/emf/compare/tests/merge/data/TwoWayMergeInputData.java
index 0751eea72..01eeaeba0 100644
--- a/plugins/org.eclipse.emf.compare.tests/src/org/eclipse/emf/compare/tests/merge/data/TwoWayMergeInputData.java
+++ b/plugins/org.eclipse.emf.compare.tests/src/org/eclipse/emf/compare/tests/merge/data/TwoWayMergeInputData.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2014 EclipseSource Muenchen GmbH and others.
+ * Copyright (c) 2014, 2015 EclipseSource Muenchen GmbH 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
@@ -8,7 +8,7 @@
* Contributors:
* Philip Langer - initial API and implementation
* Alexandra Buzila - test data for bug 446252
- * Stefan Dirix - test data for bug 453749
+ * Stefan Dirix - test data for bugs 452147 and 453749
*******************************************************************************/
package org.eclipse.emf.compare.tests.merge.data;
@@ -91,4 +91,20 @@ public class TwoWayMergeInputData extends AbstractInputData {
public Resource getDeleteFeatureMapNonContainmentsL2RRight(ResourceSet resourceSet) throws IOException {
return loadFromClassLoader("twoway/deletefeaturemapnoncontainments/ltr/right.nodes", resourceSet);
}
+
+ public Resource getOneToOneMergeL2RLeft() throws IOException {
+ return loadFromClassLoader("twoway/onetoonerefmerge/ltr/left.nodes");
+ }
+
+ public Resource getOneToOneRefMergeL2RRight() throws IOException {
+ return loadFromClassLoader("twoway/onetoonerefmerge/ltr/right.nodes");
+ }
+
+ public Resource getOneToOneMergeR2LLeft() throws IOException {
+ return loadFromClassLoader("twoway/onetoonerefmerge/rtl/left.nodes");
+ }
+
+ public Resource getOneToOneRefMergeR2LRight() throws IOException {
+ return loadFromClassLoader("twoway/onetoonerefmerge/rtl/right.nodes");
+ }
}
diff --git a/plugins/org.eclipse.emf.compare.tests/src/org/eclipse/emf/compare/tests/merge/data/twoway/onetoonerefmerge/ltr/left.nodes b/plugins/org.eclipse.emf.compare.tests/src/org/eclipse/emf/compare/tests/merge/data/twoway/onetoonerefmerge/ltr/left.nodes
new file mode 100644
index 000000000..163cab7e8
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.tests/src/org/eclipse/emf/compare/tests/merge/data/twoway/onetoonerefmerge/ltr/left.nodes
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<nodes:Node
+ xmi:version="2.0"
+ xmlns:xmi="http://www.omg.org/XMI"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:nodes="http://www.eclipse.org/emf/compare/tests/nodes"
+ xmi:id="_root"
+ name="Root">
+ <containmentRef1 xsi:type="nodes:NodeOppositeRefOneToOne" xmi:id="_c" name="c" source="_c" destination="_c"/>
+ <containmentRef1 xsi:type="nodes:NodeOppositeRefOneToOne" xmi:id="_d" name="d"/>
+</nodes:Node> \ No newline at end of file
diff --git a/plugins/org.eclipse.emf.compare.tests/src/org/eclipse/emf/compare/tests/merge/data/twoway/onetoonerefmerge/ltr/right.nodes b/plugins/org.eclipse.emf.compare.tests/src/org/eclipse/emf/compare/tests/merge/data/twoway/onetoonerefmerge/ltr/right.nodes
new file mode 100644
index 000000000..28aa3184a
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.tests/src/org/eclipse/emf/compare/tests/merge/data/twoway/onetoonerefmerge/ltr/right.nodes
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<nodes:Node
+ xmi:version="2.0"
+ xmlns:xmi="http://www.omg.org/XMI"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:nodes="http://www.eclipse.org/emf/compare/tests/nodes"
+ xmi:id="_root"
+ name="Root">
+ <containmentRef1 xsi:type="nodes:NodeOppositeRefOneToOne" xmi:id="_c" name="c" source="_d" destination="_d"/>
+ <containmentRef1 xsi:type="nodes:NodeOppositeRefOneToOne" xmi:id="_d" name="d" source="_c" destination="_c"/>
+</nodes:Node> \ No newline at end of file
diff --git a/plugins/org.eclipse.emf.compare.tests/src/org/eclipse/emf/compare/tests/merge/data/twoway/onetoonerefmerge/rtl/left.nodes b/plugins/org.eclipse.emf.compare.tests/src/org/eclipse/emf/compare/tests/merge/data/twoway/onetoonerefmerge/rtl/left.nodes
new file mode 100644
index 000000000..28aa3184a
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.tests/src/org/eclipse/emf/compare/tests/merge/data/twoway/onetoonerefmerge/rtl/left.nodes
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<nodes:Node
+ xmi:version="2.0"
+ xmlns:xmi="http://www.omg.org/XMI"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:nodes="http://www.eclipse.org/emf/compare/tests/nodes"
+ xmi:id="_root"
+ name="Root">
+ <containmentRef1 xsi:type="nodes:NodeOppositeRefOneToOne" xmi:id="_c" name="c" source="_d" destination="_d"/>
+ <containmentRef1 xsi:type="nodes:NodeOppositeRefOneToOne" xmi:id="_d" name="d" source="_c" destination="_c"/>
+</nodes:Node> \ No newline at end of file
diff --git a/plugins/org.eclipse.emf.compare.tests/src/org/eclipse/emf/compare/tests/merge/data/twoway/onetoonerefmerge/rtl/right.nodes b/plugins/org.eclipse.emf.compare.tests/src/org/eclipse/emf/compare/tests/merge/data/twoway/onetoonerefmerge/rtl/right.nodes
new file mode 100644
index 000000000..163cab7e8
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.tests/src/org/eclipse/emf/compare/tests/merge/data/twoway/onetoonerefmerge/rtl/right.nodes
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<nodes:Node
+ xmi:version="2.0"
+ xmlns:xmi="http://www.omg.org/XMI"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:nodes="http://www.eclipse.org/emf/compare/tests/nodes"
+ xmi:id="_root"
+ name="Root">
+ <containmentRef1 xsi:type="nodes:NodeOppositeRefOneToOne" xmi:id="_c" name="c" source="_c" destination="_c"/>
+ <containmentRef1 xsi:type="nodes:NodeOppositeRefOneToOne" xmi:id="_d" name="d"/>
+</nodes:Node> \ No newline at end of file
diff --git a/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/equi/DefaultEquiEngine.java b/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/equi/DefaultEquiEngine.java
index 1e9de6e06..5b29b43f7 100644
--- a/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/equi/DefaultEquiEngine.java
+++ b/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/equi/DefaultEquiEngine.java
@@ -7,7 +7,7 @@
*
* Contributors:
* Obeo - initial API and implementation
- * Stefan Dirix - Fix for Bug 453218
+ * Stefan Dirix - Fixes for Bugs 452147 and 453218
*******************************************************************************/
package org.eclipse.emf.compare.equi;
@@ -30,8 +30,8 @@ import org.eclipse.emf.compare.Equivalence;
import org.eclipse.emf.compare.FeatureMapChange;
import org.eclipse.emf.compare.Match;
import org.eclipse.emf.compare.ReferenceChange;
+import org.eclipse.emf.compare.internal.utils.ComparisonUtil;
import org.eclipse.emf.compare.utils.IEqualityHelper;
-import org.eclipse.emf.compare.utils.MatchUtil;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
@@ -115,6 +115,14 @@ public class DefaultEquiEngine implements IEquiEngine {
*/
final Match valueMatch = comparison.getMatch(referenceChange.getValue());
final EReference eOpposite = referenceChange.getReference().getEOpposite();
+
+ final Object referenceContainer = ComparisonUtil.getExpectedSide(referenceChange.getMatch(),
+ referenceChange.getSource(), false);
+ final Object referenceValue = ComparisonUtil.getExpectedSide(valueMatch, referenceChange
+ .getSource(), false);
+ final boolean valueIsContainer = referenceContainer == referenceValue
+ && referenceContainer != null;
+
if (eOpposite != null && valueMatch != null) {
final Predicate<? super Diff> candidateFilter = new Predicate<Diff>() {
public boolean apply(Diff input) {
@@ -122,7 +130,12 @@ public class DefaultEquiEngine implements IEquiEngine {
&& ((ReferenceChange)input).getReference() == eOpposite) {
final Match candidateMatch = comparison.getMatch(((ReferenceChange)input)
.getValue());
- return candidateMatch == referenceChange.getMatch();
+
+ final boolean sameMatch = candidateMatch == referenceChange.getMatch();
+ final boolean oneIsMany = referenceChange.getReference().isMany()
+ || eOpposite.isMany();
+
+ return sameMatch && (oneIsMany || !valueIsContainer);
}
return false;
}
@@ -152,18 +165,27 @@ public class DefaultEquiEngine implements IEquiEngine {
*/
private void addChangesFromOrigin(Comparison comparison, ReferenceChange diff, Equivalence equivalence) {
if (!diff.getReference().isMany()) {
- final EObject originContainer = MatchUtil.getOriginContainer(diff.getMatch().getComparison(),
- diff);
+ final EObject originContainer = ComparisonUtil.getExpectedSide(diff.getMatch(), diff.getSource(),
+ false);
+ final Match valueMatch = comparison.getMatch(diff.getValue());
if (originContainer != null) {
- for (Diff referenceChange : comparison.getDifferences(originContainer)) {
- if (referenceChange instanceof ReferenceChange
- /*
- * && MatchUtil.getContainer(comparison, referenceChange).equals(
- * ReferenceUtil.safeEGet(originContainer, diff.getReference()))
- */
- && diff.getReference().equals(
- ((ReferenceChange)referenceChange).getReference().getEOpposite())) {
- equivalence.getDifferences().add(referenceChange);
+ for (Diff candidate : comparison.getDifferences(originContainer)) {
+ if (!(candidate instanceof ReferenceChange)) {
+ continue;
+ }
+ final ReferenceChange candidateRC = (ReferenceChange)candidate;
+
+ final boolean sameReference = diff.getReference().equals(
+ candidateRC.getReference().getEOpposite());
+
+ final boolean sameContainer = originContainer == ComparisonUtil.getExpectedSide(candidate
+ .getMatch(), candidate.getSource(), false);
+ final boolean containerIsValue = originContainer == ComparisonUtil.getExpectedSide(
+ valueMatch, candidate.getSource(), false);
+
+ if (sameReference
+ && (candidateRC.getReference().isMany() || !sameContainer || !containerIsValue)) {
+ equivalence.getDifferences().add(candidate);
}
}
}
diff --git a/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/merge/AbstractMerger.java b/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/merge/AbstractMerger.java
index d2eb713d9..3741b1e9f 100644
--- a/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/merge/AbstractMerger.java
+++ b/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/merge/AbstractMerger.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2012, 2014 Obeo and others.
+ * Copyright (c) 2012, 2015 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
@@ -7,7 +7,7 @@
*
* Contributors:
* Obeo - initial API and implementation
- * Stefan Dirix - bugs 441172 and 454579
+ * Stefan Dirix - bugs 441172, 452147 and 454579
* Alexandra Buzila - Fixes for Bug 446252
*******************************************************************************/
package org.eclipse.emf.compare.merge;
@@ -26,6 +26,8 @@ import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
+import java.util.Collection;
+import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
@@ -46,6 +48,7 @@ import org.eclipse.emf.compare.internal.utils.ComparisonUtil;
import org.eclipse.emf.compare.utils.EMFCompareCopier;
import org.eclipse.emf.compare.utils.ReferenceUtil;
import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.util.FeatureMap;
import org.eclipse.emf.ecore.util.InternalEList;
@@ -165,10 +168,115 @@ public abstract class AbstractMerger implements IMerger2 {
resulting.addAll(target.getConflict().getDifferences());
resulting.remove(target);
}
+
+ // Bug 452147:
+ // Add interlocked differences to the resulting merges to avoid merging redundant differences with
+ // undefined consequences.
+ if (target instanceof ReferenceChange) {
+ final ReferenceChange refTarget = (ReferenceChange)target;
+ if (isOneToOneAndChange(refTarget)) {
+ resulting.addAll(findInterlockedOneToOneDiffs(refTarget, mergeRightToLeft));
+ }
+ }
+
return resulting;
}
/**
+ * Interlocked differences only occur in special cases: When both ends of a one-to-one feature have the
+ * same type and are actually set to the container object in an instance model.
+ * <p>
+ * For each end of the feature usually two differences are determined: Setting the feature in object A and
+ * in object B. Each pair of differences is equivalent. But when the value of the feature is set to its
+ * containing object, those differences may ALL act as equivalent depending on the merge direction.
+ * <p>
+ * These interlocked differences are therefore indirectly equivalent and need special treatment to avoid
+ * merging the same effects twice. These differences are determined by this method.
+ *
+ * @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=452147">Bugzilla #452147</a> for more
+ * information.
+ * @param referenceChange
+ * The diff for which interlocked differences are determined.
+ * @param mergeRightToLeft
+ * The direction in which we're considering a merge.
+ * @return All interlocked differences in regards to the given {@code referenceChange} and
+ * {@code mergeDirection}.
+ */
+ private Collection<? extends Diff> findInterlockedOneToOneDiffs(ReferenceChange referenceChange,
+ boolean mergeRightToLeft) {
+ final boolean sanityChecks = referenceChange.getKind() != DifferenceKind.CHANGE
+ || referenceChange.getReference().isMany()
+ || referenceChange.getReference().getEOpposite().isMany();
+
+ // check if value to be set is the container itself
+ final EObject sourceContainer = ComparisonUtil.getExpectedSide(referenceChange.getMatch(),
+ referenceChange.getSource(), mergeRightToLeft);
+
+ if (!sanityChecks && sourceContainer != null) {
+ final Object sourceValue = ReferenceUtil
+ .safeEGet(sourceContainer, referenceChange.getReference());
+
+ if (sourceValue == sourceContainer) {
+ // collect all diffs which might be "equal"
+ final Match match = referenceChange.getMatch();
+ final Set<Diff> candidates = new LinkedHashSet<Diff>();
+ for (Diff diff : match.getDifferences()) {
+ candidates.add(diff);
+ if (diff.getEquivalence() != null) {
+ candidates.addAll(diff.getEquivalence().getDifferences());
+ }
+ }
+
+ // special case - check for interlocked diffs and return them as result
+ return filterInterlockedOneToOneDiffs(candidates, referenceChange, mergeRightToLeft);
+ }
+ }
+
+ return Collections.emptyList();
+ }
+
+ /**
+ * Checks for interlocked differences from a list of candidates. See
+ * {@link #findInterlockedOneToOneDiffs(ReferenceChange, boolean)} for more information.
+ *
+ * @param diffsToCheck
+ * The differences to be checked for indirect equivalence.
+ * @param referenceChange
+ * The diff to which the determined differences are indirectly equivalent.
+ * @param mergeRightToLeft
+ * The direction in which we're considering a merge.
+ * @return All differences (and their equivalents) from {@code diffsToCheck} which are indirectly
+ * equivalent to {@code referenceChange}. Does not modify the given collection.
+ */
+ private Collection<? extends Diff> filterInterlockedOneToOneDiffs(
+ Collection<? extends Diff> diffsToCheck, ReferenceChange referenceChange, boolean mergeRightToLeft) {
+
+ final Object sourceContainer = ComparisonUtil.getExpectedSide(referenceChange.getMatch(),
+ referenceChange.getSource(), mergeRightToLeft);
+ final EReference sourceReference = referenceChange.getReference();
+
+ final Set<Diff> result = new LinkedHashSet<Diff>();
+
+ for (Diff candidate : diffsToCheck) {
+ if (candidate instanceof ReferenceChange) {
+ // check if container & reference(-opposite) are the same as from the given referenceChange
+ final Object candidateContainer = ComparisonUtil.getExpectedSide(candidate.getMatch(),
+ candidate.getSource(), mergeRightToLeft);
+ final EReference candidateReference = ((ReferenceChange)candidate).getReference();
+
+ if (sourceContainer == candidateContainer
+ && sourceReference.getEOpposite() == candidateReference) {
+ result.add(candidate);
+ if (candidate.getEquivalence() != null) {
+ result.addAll(candidate.getEquivalence().getDifferences());
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
* {@inheritDoc}
*
* @since 3.2
@@ -444,7 +552,7 @@ public abstract class AbstractMerger implements IMerger2 {
/**
* Specifies whether the given reference changes, {@code diff} and {@code equivalent}, affect references
- * constituting an one-to-one relationship and whether {@code equivalent} is a set in the current merging.
+ * constituting a one-to-one relationship and whether {@code equivalent} is a set in the current merging.
*
* @param diff
* The difference to check.
@@ -464,6 +572,22 @@ public abstract class AbstractMerger implements IMerger2 {
}
/**
+ * Checks whether the given {@code diff} is of kind {@link DifferenceKind#CHANGE} and its reference is
+ * one-to-one.
+ *
+ * @param diff
+ * The ReferenceChange to check.
+ * @return {@code true} if the given {@code diff} is of kind {@link DifferenceKind#CHANGE} and describes a
+ * one-to-one reference, {@code false} otherwise.
+ */
+ private boolean isOneToOneAndChange(ReferenceChange diff) {
+ final boolean oppositeReferenceExists = diff.getReference() != null
+ && diff.getReference().getEOpposite() != null;
+ return diff.getKind() == DifferenceKind.CHANGE && oppositeReferenceExists
+ && !diff.getReference().isMany() && !diff.getReference().getEOpposite().isMany();
+ }
+
+ /**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.merge.IMerger#copyLeftToRight(org.eclipse.emf.compare.Diff,

Back to the top