blob: de5c61b59032ad0c4bc09a3c7d245d92ce3d43b9 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012, 2017 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
* Stefan Dirix - Bugs 450949, 453218, 460923 and 460675
*******************************************************************************/
package org.eclipse.emf.compare.diff;
import static org.eclipse.emf.compare.utils.ReferenceUtil.getAsList;
import static org.eclipse.emf.compare.utils.ReferenceUtil.safeEGet;
import com.google.common.base.Optional;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.log4j.Logger;
import org.eclipse.emf.common.util.Monitor;
import org.eclipse.emf.compare.Comparison;
import org.eclipse.emf.compare.ComparisonCanceledException;
import org.eclipse.emf.compare.DifferenceKind;
import org.eclipse.emf.compare.DifferenceSource;
import org.eclipse.emf.compare.EMFCompareMessages;
import org.eclipse.emf.compare.Match;
import org.eclipse.emf.compare.internal.utils.ComparisonUtil;
import org.eclipse.emf.compare.internal.utils.DiffUtil;
import org.eclipse.emf.compare.utils.IEqualityHelper;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.FeatureMap;
import org.eclipse.emf.ecore.util.FeatureMapUtil;
/**
* The diff engine is in charge of actually computing the differences between the objects mapped by a
* {@link Match} object.
* <p>
* This default implementation aims at being generic enough to be used for any model, whatever the metamodel.
* However, specific differences, refinements of differences or even higher level differences might be
* necessary.
* </p>
* TODO document available extension possibilities.
*
* @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
*/
public class DefaultDiffEngine implements IDiffEngine {
/**
* We'll use this "placeholder" to differentiate the unmatched elements from the "null" values that
* attributes can legitimately use.
*/
protected static final Object UNMATCHED_VALUE = new Object();
/** The logger. */
private static final Logger LOGGER = Logger.getLogger(DefaultDiffEngine.class);
/**
* The diff processor that will be used by this engine. Should be passed by the constructor and accessed
* by {@link #getDiffProcessor()}.
*/
private IDiffProcessor diffProcessor;
/**
* Create the diff engine.
*/
public DefaultDiffEngine() {
this(new DiffBuilder());
}
/**
* Create the diff engine.
*
* @param processor
* this instance will be called for each detected difference.
*/
public DefaultDiffEngine(IDiffProcessor processor) {
this.diffProcessor = processor;
}
/**
* Checks whether the given {@code iterable} contains the given {@code element} according to the semantics
* of {@link IEqualityHelper#matchingValues(Comparison, Object, Object)}.
*
* @param comparison
* This will be used in order to retrieve the Match for EObjects when comparing them.
* @param iterable
* Iterable which content we are to check.
* @param element
* The element we expect to be contained in {@code iterable}.
* @param <E>
* Type of the input iterable's content.
* @return {@code true} if the given {@code iterable} contains {@code element}, {@code false} otherwise.
*/
protected <E> boolean contains(Comparison comparison, Iterable<E> iterable, E element) {
final IEqualityHelper equality = comparison.getEqualityHelper();
for (E candidate : iterable) {
if (equality.matchingValues(candidate, element)) {
return true;
}
}
return false;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.diff.IDiffEngine#diff(org.eclipse.emf.compare.Comparison,
* org.eclipse.emf.common.util.Monitor)
*/
public void diff(Comparison comparison, Monitor monitor) {
long start = System.currentTimeMillis();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("detect differences - START")); //$NON-NLS-1$
}
monitor.subTask(EMFCompareMessages.getString("DefaultDiffEngine.monitor.diff")); //$NON-NLS-1$
for (Match rootMatch : comparison.getMatches()) {
checkForDifferences(rootMatch, monitor);
}
if (LOGGER.isInfoEnabled()) {
LOGGER.info(String.format("detect differences - END - Took %d ms", Long.valueOf(System //$NON-NLS-1$
.currentTimeMillis() - start)));
}
}
/**
* Checks the given {@link Match}'s sides for potential differences. Will recursively check for
* differences on submatches.
*
* @param match
* The match that is to be checked.
* @param monitor
* The monitor to report progress or to check for cancellation.
*/
protected void checkForDifferences(Match match, Monitor monitor) {
if (monitor.isCanceled()) {
throw new ComparisonCanceledException();
}
checkResourceAttachment(match, monitor);
final FeatureFilter featureFilter = createFeatureFilter();
final Iterator<EReference> references = featureFilter.getReferencesToCheck(match);
while (references.hasNext()) {
final EReference reference = references.next();
final boolean considerOrdering = featureFilter.checkForOrderingChanges(reference);
computeDifferences(match, reference, considerOrdering);
}
final Iterator<EAttribute> attributes = featureFilter.getAttributesToCheck(match);
while (attributes.hasNext()) {
final EAttribute attribute = attributes.next();
final boolean considerOrdering = featureFilter.checkForOrderingChanges(attribute);
computeDifferences(match, attribute, considerOrdering);
}
for (Match submatch : match.getSubmatches()) {
checkForDifferences(submatch, monitor);
}
}
/**
* Checks whether the given {@link Match}'s sides have changed resources. This will only be called for
* {@link Match} elements referencing the root(s) of an EMF Resource.
*
* @param match
* The match that is to be checked.
* @param monitor
* The monitor to report progress or to check for cancellation.
*/
protected void checkResourceAttachment(Match match, Monitor monitor) {
final Comparison comparison = match.getComparison();
if (comparison.getMatchedResources().isEmpty()) {
// This is a comparison of EObjects, do not go up to the resources
return;
}
final EObject left = match.getLeft();
final EObject right = match.getRight();
final EObject origin = match.getOrigin();
final boolean originIsRoot = isRoot(origin);
boolean threeWay = comparison.isThreeWay();
if (threeWay) {
if (originIsRoot) {
// Uncontrol or delete, the "resource attachment" is a deletion
if (!isRoot(left)) {
final String uri = origin.eResource().getURI().toString();
getDiffProcessor().resourceAttachmentChange(match, uri, DifferenceKind.DELETE,
DifferenceSource.LEFT);
}
if (!isRoot(right)) {
final String uri = origin.eResource().getURI().toString();
getDiffProcessor().resourceAttachmentChange(match, uri, DifferenceKind.DELETE,
DifferenceSource.RIGHT);
}
// Cases where isRoot(left) == true or isRoot(right) == true
// are handled in org.eclipse.emf.compare.egit by EGitPostProcessor#postDiff
} else {
// Control or add, the "resource attachment" is an addition
if (isRoot(left)) {
final String uri = left.eResource().getURI().toString();
getDiffProcessor().resourceAttachmentChange(match, uri, DifferenceKind.ADD,
DifferenceSource.LEFT);
}
if (isRoot(right)) {
final String uri = right.eResource().getURI().toString();
getDiffProcessor().resourceAttachmentChange(match, uri, DifferenceKind.ADD,
DifferenceSource.RIGHT);
}
}
} else {
final boolean leftIsRoot = isRoot(left);
final boolean rightIsRoot = isRoot(right);
if (leftIsRoot && !rightIsRoot) {
final String uri = left.eResource().getURI().toString();
getDiffProcessor().resourceAttachmentChange(match, uri, DifferenceKind.ADD,
DifferenceSource.LEFT);
} else if (!leftIsRoot && rightIsRoot) {
final String uri = right.eResource().getURI().toString();
getDiffProcessor().resourceAttachmentChange(match, uri, DifferenceKind.DELETE,
DifferenceSource.LEFT);
}
}
}
/**
* Checks whether the given EObject is a root of its resource or not.
*
* @param eObj
* The EObject to check.
* @return <code>true</code> if this object is a root of its containing resource, <code>false</code>
* otherwise.
*/
protected static boolean isRoot(EObject eObj) {
if (eObj instanceof InternalEObject) {
return ((InternalEObject)eObj).eDirectResource() != null;
}
boolean isRoot = false;
if (eObj != null) {
final Resource res = eObj.eResource();
final EObject container = eObj.eContainer();
// <root of containment tree> || <root of fragment>
isRoot = (container == null && res != null)
|| (container != null && container.eResource() != res);
}
return isRoot;
}
/**
* Delegates the computation of Differences for a given containment reference according to the type of
* comparison (two- or three-way), and whether we need to take ordering changes into account.
*
* @param match
* The match which sides we need to check for potential differences.
* @param reference
* The reference whose values are currently being checked for differences.
* @param checkOrdering
* Whether we need to detect ordering changes or ignore them.
*/
protected void computeContainmentDifferences(Match match, EReference reference, boolean checkOrdering) {
Comparison comparison = match.getComparison();
if (checkOrdering) {
if (comparison.isThreeWay()) {
computeContainmentDifferencesThreeWay(match, reference, checkOrdering);
} else {
computeContainmentDifferencesTwoWay(match, reference, checkOrdering);
}
} else {
List<Object> leftValues = getAsList(match.getLeft(), reference);
List<Object> rightValues = getAsList(match.getRight(), reference);
if (comparison.isThreeWay()) {
createContainmentDifferencesNoOrdering(match, reference, leftValues, DifferenceSource.LEFT);
createContainmentDifferencesNoOrdering(match, reference, rightValues, DifferenceSource.RIGHT);
List<Object> originValues = getAsList(match.getOrigin(), reference);
for (Object value : originValues) {
Match valueMatch = comparison.getMatch((EObject)value);
if (valueMatch.getLeft() == null) {
featureChange(match, reference, value, DifferenceKind.DELETE, DifferenceSource.LEFT);
}
if (valueMatch.getRight() == null) {
featureChange(match, reference, value, DifferenceKind.DELETE, DifferenceSource.RIGHT);
}
}
} else {
createContainmentDifferencesNoOrdering(match, reference, leftValues, DifferenceSource.LEFT);
for (Object value : rightValues) {
Match valueMatch = comparison.getMatch((EObject)value);
if (valueMatch.getLeft() == null) {
featureChange(match, reference, value, DifferenceKind.DELETE, DifferenceSource.LEFT);
}
}
}
}
}
/**
* Delegates the computation of Differences for a given muti-valued feature according to the type of
* comparison (two- or three-way), and whether we need to take ordering changes into account.
*
* @param match
* The match which sides we need to check for potential differences.
* @param feature
* The feature whose values are currently being checked for differences.
* @param checkOrdering
* Whether we need to detect ordering changes or ignore them.
*/
protected void computeMultiValuedFeatureDifferences(Match match, EStructuralFeature feature,
boolean checkOrdering) {
Comparison comparison = match.getComparison();
if (checkOrdering) {
if (comparison.isThreeWay()) {
computeMultiValuedFeatureDifferencesThreeWay(match, feature, checkOrdering);
} else {
computeMultiValuedFeatureDifferencesTwoWay(match, feature, checkOrdering);
}
} else {
List<Object> leftValues = getAsList(match.getLeft(), feature);
List<Object> rightValues = getAsList(match.getRight(), feature);
if (comparison.isThreeWay()) {
List<Object> originValues = getAsList(match.getOrigin(), feature);
createMultiValuedFeatureDifferencesNoOrdering(match, feature, leftValues, originValues,
DifferenceSource.LEFT);
createMultiValuedFeatureDifferencesNoOrdering(match, feature, rightValues, originValues,
DifferenceSource.RIGHT);
} else {
createMultiValuedFeatureDifferencesNoOrdering(match, feature, leftValues, rightValues,
DifferenceSource.LEFT);
}
}
}
/**
* Computes the difference between the sides of the given {@code match} for the given containment
* {@code reference}.
* <p>
* This is only meant for three-way comparisons.
* </p>
* <p>
* <b>Note</b> that this is no longer used for references which ordering is not considered, so
* {@code checkOrdering} will always be <code>true</code> .
* </p>
*
* @param match
* The match which sides we need to check for potential differences.
* @param reference
* The containment reference which values are to be checked.
* @param checkOrdering
* {@code true} if we should consider ordering changes on this reference, {@code false}
* otherwise.
*/
protected void computeContainmentDifferencesThreeWay(Match match, EReference reference,
boolean checkOrdering) {
final Comparison comparison = match.getComparison();
// We won't use iterables here since we need random access collections for fast LCS.
final List<Object> leftValues = getAsList(match.getLeft(), reference);
final List<Object> rightValues = getAsList(match.getRight(), reference);
final List<Object> originValues = getAsList(match.getOrigin(), reference);
final List<Object> lcsOriginLeft = DiffUtil.longestCommonSubsequence(comparison, originValues,
leftValues);
final List<Object> lcsOriginRight = DiffUtil.longestCommonSubsequence(comparison, originValues,
rightValues);
createContainmentDifferences(match, reference, checkOrdering, leftValues, lcsOriginLeft,
DifferenceSource.LEFT);
createContainmentDifferences(match, reference, checkOrdering, rightValues, lcsOriginRight,
DifferenceSource.RIGHT);
// deleted from either side
for (Object diffCandidate : originValues) {
/*
* A value that is in the origin but not in either left or right has been deleted or is a moved
* element which previously was in this reference. If the latter, we'll detect it later on when
* checking its new reference.
*/
final Match candidateMatch = comparison.getMatch((EObject)diffCandidate);
if (candidateMatch == null) {
// out of scope
} else {
if (candidateMatch.getLeft() == null) {
featureChange(match, reference, diffCandidate, DifferenceKind.DELETE,
DifferenceSource.LEFT);
}
if (candidateMatch.getRight() == null) {
featureChange(match, reference, diffCandidate, DifferenceKind.DELETE,
DifferenceSource.RIGHT);
}
}
}
}
/**
* This will iterate over the given list of values from a containment reference and create the differences
* that can be detected from it.
* <p>
* Ordering changes will not be considered at all from this method. Values that exist in both the given
* list of elements and the origin (either ancestor or right side for two-way comparisons), will only have
* a Diff if their container or containing reference has changed. If they are still in the same container
* and reference, then even if they are not in the same position, we will not try and detect an ordering
* change.
* </p>
*
* @param match
* The match which sides we need to check for potential differences.
* @param reference
* The containment reference which values are to be checked.
* @param sideValues
* Value of that <code>reference</code> on the given <code>side</code>.
* @param side
* The side currently being compared.
*/
protected void createContainmentDifferencesNoOrdering(Match match, EReference reference,
List<Object> sideValues, DifferenceSource side) {
Comparison comparison = match.getComparison();
IEqualityHelper equalityHelper = comparison.getEqualityHelper();
int sizeSide = sideValues.size();
int currentSide = 0;
while (currentSide < sizeSide) {
EObject sideValue = (EObject)sideValues.get(currentSide++);
Match candidateMatch = comparison.getMatch(sideValue);
EObject originValue;
if (comparison.isThreeWay()) {
originValue = candidateMatch.getOrigin();
} else {
originValue = candidateMatch.getRight();
}
if (matchingContainment(equalityHelper, sideValue, originValue)) {
// Object present in both sides, in the same container and same containment feature.
// We don't care about ordering so there is no change here.
} else {
// Object has either changed container or isn't present in the origin
if (originValue != null) {
featureChange(match, reference, sideValue, DifferenceKind.MOVE, side);
} else {
featureChange(match, reference, sideValue, DifferenceKind.ADD, side);
}
}
}
}
/**
* Called from {@link #computeContainmentDifferencesThreeWay(Match, EReference, boolean)} once our LCS
* have been computed and we know what really changed. It will be used for both the left and right side.
*
* @param match
* The match which sides we need to check for potential differences.
* @param reference
* The containment reference which values are to be checked.
* @param checkOrdering
* {@code true} if we should consider ordering changes on this reference, {@code false}
* otherwise.
* @param values
* Value of that <code>reference</code> on the given <code>side</code>.
* @param lcsWithOrigin
* LCS between the reference values on the given <code>side</code> and the values in origin.
* @param side
* The side currently being compared.
*/
protected void createContainmentDifferences(Match match, EReference reference, boolean checkOrdering,
List<Object> values, List<Object> lcsWithOrigin, DifferenceSource side) {
final Comparison comparison = match.getComparison();
final IEqualityHelper equality = comparison.getEqualityHelper();
int lcsCursor = 0;
Optional<Match> lcsCurrent = getMatchIfPresent(comparison, lcsWithOrigin, lcsCursor);
for (Object diffCandidate : values) {
final Match candidateMatch = comparison.getMatch((EObject)diffCandidate);
// See bug 405000 for this strange iteration on the LCS
if (candidateMatch == null || lcsCurrent.orNull() == candidateMatch) {
lcsCursor++;
lcsCurrent = getMatchIfPresent(comparison, lcsWithOrigin, lcsCursor);
continue;
}
final EObject value;
if (side == DifferenceSource.LEFT) {
value = candidateMatch.getLeft();
} else {
value = candidateMatch.getRight();
}
final EObject originValue;
if (comparison.isThreeWay()) {
originValue = candidateMatch.getOrigin();
} else {
originValue = candidateMatch.getRight();
}
if (matchingContainment(equality, value, originValue)) {
/*
* Contained in both compared side and the origin, and not part of the LCS. It has moved
* within its (containment) reference.
*/
if (checkOrdering) {
featureChange(match, reference, diffCandidate, DifferenceKind.MOVE, side);
}
} else {
/*
* This element is in different containers in the compared side and origin (if it is in origin
* at all).
*/
if (originValue != null) {
featureChange(match, reference, diffCandidate, DifferenceKind.MOVE, side);
} else {
featureChange(match, reference, diffCandidate, DifferenceKind.ADD, side);
}
}
}
}
/**
* Checks whether the two given EObjects are contained within the same object, under the same reference.
*
* @param equalityHelper
* Our current equality helper.
* @param o1
* First of the two EObjects to compare.
* @param o2
* Second of the two EObjects to compare.
* @return <code>true</code> if these two objects are contained within the same container, false
* otherwise.
*/
protected boolean matchingContainment(IEqualityHelper equalityHelper, EObject o1, EObject o2) {
if (o1 == null || o2 == null) {
return false;
}
boolean matchingContainment = false;
final EObject container1 = o1.eContainer();
final EObject container2 = o2.eContainer();
if (container1 != null && container2 != null) {
final EReference containing1 = o1.eContainmentFeature();
final EReference containing2 = o2.eContainmentFeature();
matchingContainment = (containing1 == containing2
|| containing1.getName().equals(containing2.getName()))
&& equalityHelper.matchingValues(o1.eContainer(), o2.eContainer());
}
return matchingContainment;
}
/**
* This will be used in order to read the LCS synchronously with the iteration on its target lists'
* values. This should be used cautiously since it will work on empty lists, null values and out-of-scope
* objects.
*
* @param comparison
* The current comparison.
* @param list
* A list of EObjects. May be empty or contain out-of-scope values.
* @param index
* Index of the object we seek within this list.
* @return An optional containing the match of the object at the given index... or
* {@link Optional#absent()}.
*/
protected static Optional<Match> getMatchIfPresent(final Comparison comparison, List<Object> list,
int index) {
Optional<Match> value = Optional.absent();
if (list.size() > index) {
EObject current = (EObject)list.get(index);
if (current != null) {
value = Optional.fromNullable(comparison.getMatch(current));
}
}
return value;
}
/**
* This will be used in order to read the LCS synchronously with the iteration on its target lists'
* values. This should be used cautiously since it will work on empty lists, and null values contained in
* the list are treated the same as an empty list and considered to be Optional.absent().
*
* @param list
* A list of EObjects. May be empty or contain null or out-of-scope values.
* @param index
* Index of the object we seek within this list.
* @return An optional containing the object at the given index... or {@link Optional#absent()}.
*/
protected static Optional<Object> getIfPresent(List<Object> list, int index) {
Optional<Object> value = Optional.absent();
if (list.size() > index) {
value = Optional.fromNullable(list.get(index));
}
return value;
}
/**
* Computes the difference between the sides of the given {@code match} for the given containment
* {@code reference}.
* <p>
* This is only meant for two-way comparisons.
* </p>
* <p>
* <b>Note</b> that this is no longer used for references which ordering is not considered, so
* {@code checkOrdering} will always be <code>true</code> .
* </p>
*
* @param match
* The match which sides we need to check for potential differences.
* @param reference
* The containment reference which values are to be checked.
* @param checkOrdering
* {@code true} if we should consider ordering changes on this reference, {@code false}
* otherwise.
*/
protected void computeContainmentDifferencesTwoWay(Match match, EReference reference,
boolean checkOrdering) {
final Comparison comparison = match.getComparison();
final List<Object> leftValues = getAsList(match.getLeft(), reference);
final List<Object> rightValues = getAsList(match.getRight(), reference);
final List<Object> lcs = DiffUtil.longestCommonSubsequence(comparison, rightValues, leftValues);
createContainmentDifferences(match, reference, checkOrdering, leftValues, lcs, DifferenceSource.LEFT);
// deleted
for (Object diffCandidate : rightValues) {
/*
* A value that is in the right but not in the left either has been deleted or is a moved element
* which previously was in this reference. We'll detect the move on its new reference.
*/
final Match candidateMatch = comparison.getMatch((EObject)diffCandidate);
if (candidateMatch == null) {
// out of scope
} else if (candidateMatch.getLeft() == null) {
featureChange(match, reference, diffCandidate, DifferenceKind.DELETE, DifferenceSource.LEFT);
}
}
}
/**
* Computes the difference between the sides of the given <code>match</code> for the given
* <code>attribute</code>.
*
* @param match
* The match which sides we need to check for potential differences.
* @param attribute
* The attribute which values are to be checked.
* @param checkOrdering
* <code>true</code> if we should consider ordering changes on this attribute,
* <code>false</code> otherwise.
*/
protected void computeDifferences(Match match, EAttribute attribute, boolean checkOrdering) {
final Comparison comparison = match.getComparison();
// This default implementation does not care about "attribute" changes on added/removed elements
boolean shortcut = false;
if (comparison.isThreeWay()) {
shortcut = match.getOrigin() == null;
} else {
shortcut = match.getLeft() == null || match.getRight() == null;
}
// Do not shortcut when multi-valued FeatureMaps are affected to keep their ordering intact
if (shortcut && FeatureMapUtil.isFeatureMap(attribute)) {
final EObject owner = getOwner(match);
if (owner != null && FeatureMapUtil.isMany(owner, attribute)) {
shortcut = false;
}
}
if (shortcut) {
return;
}
if (attribute.isMany()) {
if (comparison.isThreeWay()) {
computeMultiValuedFeatureDifferencesThreeWay(match, attribute, checkOrdering);
} else {
computeMultiValuedFeatureDifferencesTwoWay(match, attribute, checkOrdering);
}
} else {
computeSingleValuedAttributeDifferences(match, attribute);
}
}
/**
* Returns one side of the match if any exist. The order of the checked sides is Origin, Left and Right.
*
* @param match
* The match whose sides are checked.
* @return Either Origin, Left or Right if one of them exists, {@code null} otherwise.
*/
private EObject getOwner(Match match) {
EObject owner = match.getOrigin();
if (owner == null) {
owner = match.getLeft();
if (owner == null) {
owner = match.getRight();
}
}
return owner;
}
/**
* Computes the difference between the sides of the given <code>match</code> for the given
* <code>reference</code>.
*
* @param match
* The match which sides we need to check for potential differences.
* @param reference
* The reference which values are to be checked.
* @param checkOrdering
* <code>true</code> if we should consider ordering changes on this reference,
* <code>false</code> otherwise.
*/
protected void computeDifferences(Match match, EReference reference, boolean checkOrdering) {
final Comparison comparison = match.getComparison();
if (reference.isContainment()) {
computeContainmentDifferences(match, reference, checkOrdering);
} else if (reference.isMany()) {
computeMultiValuedFeatureDifferences(match, reference, checkOrdering);
} else {
if (comparison.isThreeWay()) {
computeSingleValuedReferenceDifferencesThreeWay(match, reference);
} else {
computeSingleValuedReferenceDifferencesTwoWay(match, reference);
}
}
}
/**
* Computes the difference between the sides of the given {@code match} for the given multi-valued
* {@code feature}.
* <p>
* The given {@code feature} cannot be a containment reference.
* </p>
* <p>
* This is only meant for three-way comparisons.
* </p>
* <p>
* <b>Note</b> that this is no longer used for features which ordering is not considered, so
* {@code checkOrdering} will always be <code>true</code> .
* </p>
*
* @param match
* The match which sides we need to check for potential differences.
* @param feature
* The feature which values are to be checked.
* @param checkOrdering
* {@code true} if we should consider ordering changes on this feature, {@code false}
* otherwise.
*/
protected void computeMultiValuedFeatureDifferencesThreeWay(Match match, EStructuralFeature feature,
boolean checkOrdering) {
final Comparison comparison = match.getComparison();
final IEqualityHelper equality = comparison.getEqualityHelper();
// We won't use iterables here since we need random access collections for fast LCS.
final List<Object> leftValues = getAsList(match.getLeft(), feature);
final List<Object> rightValues = getAsList(match.getRight(), feature);
final List<Object> originValues = getAsList(match.getOrigin(), feature);
final List<Object> lcsOriginLeft = DiffUtil.longestCommonSubsequence(comparison, originValues,
leftValues);
final List<Object> lcsOriginRight = DiffUtil.longestCommonSubsequence(comparison, originValues,
rightValues);
// Any value that is _not_ in the LCS has changed.
int lcsCursor = 0;
Optional<Object> lcsCurrent = getIfPresent(lcsOriginLeft, lcsCursor);
for (Object diffCandidate : leftValues) {
// See bug 405000 for this strange iteration on the LCS
if (equality.matchingValues(diffCandidate, lcsCurrent.orNull())) {
lcsCursor++;
lcsCurrent = getIfPresent(lcsOriginLeft, lcsCursor);
continue;
}
if (contains(comparison, originValues, diffCandidate)) {
if (checkOrdering) {
featureChange(match, feature, diffCandidate, DifferenceKind.MOVE, DifferenceSource.LEFT);
}
} else if (FeatureMapUtil.isFeatureMap(feature) && diffCandidate instanceof FeatureMap.Entry) {
// A value of a FeatureMap changed his key
if (isFeatureMapEntryKeyChange(equality, (FeatureMap.Entry)diffCandidate, originValues)) {
featureChange(match, feature, diffCandidate, DifferenceKind.CHANGE,
DifferenceSource.LEFT);
} else if (isFeatureMapValueMove(comparison, (FeatureMap.Entry)diffCandidate,
DifferenceSource.LEFT)) {
featureChange(match, feature, diffCandidate, DifferenceKind.MOVE, DifferenceSource.LEFT);
} else {
featureChange(match, feature, diffCandidate, DifferenceKind.ADD, DifferenceSource.LEFT);
}
} else {
featureChange(match, feature, diffCandidate, DifferenceKind.ADD, DifferenceSource.LEFT);
}
}
lcsCursor = 0;
lcsCurrent = getIfPresent(lcsOriginRight, lcsCursor);
for (Object diffCandidate : rightValues) {
// See bug 405000 for this strange iteration on the LCS
if (equality.matchingValues(diffCandidate, lcsCurrent.orNull())) {
lcsCursor++;
lcsCurrent = getIfPresent(lcsOriginRight, lcsCursor);
continue;
}
if (contains(comparison, originValues, diffCandidate)) {
if (checkOrdering) {
featureChange(match, feature, diffCandidate, DifferenceKind.MOVE, DifferenceSource.RIGHT);
}
} else if (FeatureMapUtil.isFeatureMap(feature) && diffCandidate instanceof FeatureMap.Entry) {
// A value of a FeatureMap changed his key
if (isFeatureMapEntryKeyChange(equality, (FeatureMap.Entry)diffCandidate, originValues)) {
featureChange(match, feature, diffCandidate, DifferenceKind.CHANGE,
DifferenceSource.RIGHT);
} else if (isFeatureMapValueMove(comparison, (FeatureMap.Entry)diffCandidate,
DifferenceSource.RIGHT)) {
featureChange(match, feature, diffCandidate, DifferenceKind.MOVE, DifferenceSource.RIGHT);
} else {
featureChange(match, feature, diffCandidate, DifferenceKind.ADD, DifferenceSource.RIGHT);
}
} else {
featureChange(match, feature, diffCandidate, DifferenceKind.ADD, DifferenceSource.RIGHT);
}
}
// Removed from either side
for (Object diffCandidate : originValues) {
// A value that is in the origin but not in one of the side has been deleted.
// However, we do not want attribute changes on removed elements.
if (!contains(comparison, leftValues, diffCandidate)) {
if ((feature instanceof EReference || match.getLeft() != null)
&& !isFeatureMapChangeOrMove(comparison, feature, diffCandidate, leftValues,
DifferenceSource.LEFT)) {
featureChange(match, feature, diffCandidate, DifferenceKind.DELETE,
DifferenceSource.LEFT);
}
}
if (!contains(comparison, rightValues, diffCandidate)) {
if ((feature instanceof EReference || match.getRight() != null)
&& !isFeatureMapChangeOrMove(comparison, feature, diffCandidate, rightValues,
DifferenceSource.RIGHT)) {
featureChange(match, feature, diffCandidate, DifferenceKind.DELETE,
DifferenceSource.RIGHT);
}
}
}
}
/**
* This will iterate over the given list of values from a multi-valued feature and create the differences
* that can be detected from it.
* <p>
* Ordering changes will not be considered at all from this method. Values that exist in both the given
* list of elements and the origin (either ancestor or right side for two-way comparisons), will never
* have a Diff even if they are not at the same index in the list of their respective side.
* </p>
*
* @param match
* The match which sides we need to check for potential differences.
* @param feature
* The feature which values are to be checked. Cannot be a containment reference.
* @param sideValues
* Value of that <code>reference</code> on the given <code>side</code>.
* @param originValues
* Value of that <code>reference</code> on the origin side. Could be the common ancestor or the
* right side in case of two-way comparisons.
* @param side
* The side currently being compared.
*/
protected void createMultiValuedFeatureDifferencesNoOrdering(Match match, EStructuralFeature feature,
List<Object> sideValues, List<Object> originValues, DifferenceSource side) {
Comparison comparison = match.getComparison();
IEqualityHelper equalityHelper = comparison.getEqualityHelper();
List<Object> originCopy = new ArrayList<>(originValues);
int sizeSide = sideValues.size();
int currentSide = 0;
while (currentSide < sizeSide) {
Object sideValue = sideValues.get(currentSide++);
int currentOrigin = 0;
int sizeOrigin = originCopy.size();
boolean matching = false;
while (currentOrigin < sizeOrigin && !matching) {
Object originValue = originCopy.get(currentOrigin);
if (equalityHelper.matchingValues(sideValue, originValue)) {
originCopy.remove(currentOrigin);
matching = true;
}
currentOrigin++;
}
if (!matching) {
// This object is in the side checked, but not in the origin
if (FeatureMapUtil.isFeatureMap(feature) && sideValue instanceof FeatureMap.Entry) {
// A value of a FeatureMap changed its key
if (isFeatureMapEntryKeyChange(equalityHelper, (FeatureMap.Entry)sideValue,
originValues)) {
featureChange(match, feature, sideValue, DifferenceKind.CHANGE, side);
} else if (isFeatureMapValueMove(comparison, (FeatureMap.Entry)sideValue, side)) {
featureChange(match, feature, sideValue, DifferenceKind.MOVE, side);
} else {
featureChange(match, feature, sideValue, DifferenceKind.ADD, side);
}
} else {
featureChange(match, feature, sideValue, DifferenceKind.ADD, side);
}
}
}
// Objects that are still present in "originCopy" now have been deleted from the current side.
// We want all reference changes, but if the current side has been deleted, we do not want to have
// attribute changes under this deletion.
boolean sideDeleted;
if (side == DifferenceSource.LEFT) {
sideDeleted = match.getLeft() == null;
} else {
sideDeleted = match.getRight() == null;
}
for (Object originValue : originCopy) {
if ((feature instanceof EReference || !sideDeleted)
&& !isFeatureMapChangeOrMove(comparison, feature, originValue, sideValues, side)) {
featureChange(match, feature, originValue, DifferenceKind.DELETE, side);
}
}
}
/**
* Computes the difference between the sides of the given {@code match} for the given multi-valued
* {@code feature}.
* <p>
* The given {@code feature} cannot be a containment reference.
* </p>
* <p>
* This is only meant for two-way comparisons.
* </p>
* <p>
* <b>Note</b> that this is no longer used for features which ordering is not considered, so
* {@code checkOrdering} will always be <code>true</code> .
* </p>
*
* @param match
* The match which sides we need to check for potential differences.
* @param feature
* The feature which values are to be checked.
* @param checkOrdering
* {@code true} if we should consider ordering changes on this feature, {@code false}
* otherwise.
*/
protected void computeMultiValuedFeatureDifferencesTwoWay(Match match, EStructuralFeature feature,
boolean checkOrdering) {
final Comparison comparison = match.getComparison();
final IEqualityHelper equality = comparison.getEqualityHelper();
// We won't use iterables here since we need random access collections for fast LCS.
final List<Object> leftValues = getAsList(match.getLeft(), feature);
final List<Object> rightValues = getAsList(match.getRight(), feature);
final List<Object> lcs = DiffUtil.longestCommonSubsequence(comparison, rightValues, leftValues);
int lcsCursor = 0;
Optional<Object> lcsCurrent = getIfPresent(lcs, lcsCursor);
for (Object diffCandidate : leftValues) {
// See bug 405000 for this strange iteration on the LCS
if (equality.matchingValues(diffCandidate, lcsCurrent.orNull())) {
lcsCursor++;
lcsCurrent = getIfPresent(lcs, lcsCursor);
continue;
}
if (contains(comparison, rightValues, diffCandidate)) {
if (checkOrdering) {
featureChange(match, feature, diffCandidate, DifferenceKind.MOVE, DifferenceSource.LEFT);
}
} else if (FeatureMapUtil.isFeatureMap(feature) && diffCandidate instanceof FeatureMap.Entry) {
// A value of a FeatureMap changed his key
if (isFeatureMapEntryKeyChange(equality, (FeatureMap.Entry)diffCandidate, rightValues)) {
featureChange(match, feature, diffCandidate, DifferenceKind.CHANGE,
DifferenceSource.LEFT);
} else if (isFeatureMapValueMove(comparison, (FeatureMap.Entry)diffCandidate,
DifferenceSource.LEFT)) {
featureChange(match, feature, diffCandidate, DifferenceKind.MOVE, DifferenceSource.LEFT);
} else {
featureChange(match, feature, diffCandidate, DifferenceKind.ADD, DifferenceSource.LEFT);
}
} else {
featureChange(match, feature, diffCandidate, DifferenceKind.ADD, DifferenceSource.LEFT);
}
}
for (Object diffCandidate : rightValues) {
if (contains(comparison, leftValues, diffCandidate)) {
// skip elements which were already looked at earlier
continue;
}
// A value that is in the right but not in the left has been deleted or moved.
if (isFeatureMapMoveFromNonFeatureMapContainment(comparison, feature, diffCandidate, leftValues,
DifferenceSource.LEFT)) {
// add move change if the move originates from a non-feature-map containment.
featureChange(match, feature, diffCandidate, DifferenceKind.MOVE, DifferenceSource.LEFT);
} else if (!isFeatureMapChangeOrMove(comparison, feature, diffCandidate, leftValues,
DifferenceSource.LEFT)) {
featureChange(match, feature, diffCandidate, DifferenceKind.DELETE, DifferenceSource.LEFT);
}
}
}
/**
* Checks if the given candidate is a FeatureMap change of type DifferenceKind.CHANGE.
*
* @param comparison
* The comparison object.
* @param feature
* The feature which values are to be checked.
* @param diffCandidate
* The given candidate for which we search an equivalent value.
* @param values
* The entries in which we search.
* @param source
* The given DifferenceSource of the entry.
* @return true if the given candidate is a FeatureMap change of type DifferenceKind.CHANGE, false
* otherwise.
*/
private boolean isFeatureMapChange(final Comparison comparison, final EStructuralFeature feature,
final Object diffCandidate, final List<Object> values, final DifferenceSource source) {
return FeatureMapUtil.isFeatureMap(feature)
&& (isFeatureMapEntryKeyChange(comparison.getEqualityHelper(),
(FeatureMap.Entry)diffCandidate, values));
}
/**
* Checks if the given candidate is a FeatureMap change of type DifferenceKind.MOVE.
*
* @param comparison
* The comparison object.
* @param feature
* The feature which values are to be checked.
* @param diffCandidate
* The given candidate for which we search an equivalent value.
* @param values
* The entries in which we search.
* @param source
* The given DifferenceSource of the entry.
* @return true if the given candidate is a FeatureMap change of type DifferenceKind.MOVE, false
* otherwise.
*/
private boolean isFeatureMapMove(final Comparison comparison, final EStructuralFeature feature,
final Object diffCandidate, final List<Object> values, final DifferenceSource source) {
return FeatureMapUtil.isFeatureMap(feature)
&& isFeatureMapValueMove(comparison, (FeatureMap.Entry)diffCandidate, source);
}
/**
* Checks if the given candidate is a FeatureMap change of type DifferenceKind.MOVE which originates from
* a Non-FeatureMap-Containment.
*
* @param comparison
* The comparison object.
* @param feature
* The feature which values are to be checked.
* @param diffCandidate
* The given candidate for which we search an equivalent value.
* @param values
* The entries in which we search.
* @param source
* The given DifferenceSource of the entry.
* @return true if the given candidate is a FeatureMap change of type DifferenceKind.MOVE, false
* otherwise.
*/
private boolean isFeatureMapMoveFromNonFeatureMapContainment(final Comparison comparison,
final EStructuralFeature feature, final Object diffCandidate, final List<Object> values,
final DifferenceSource source) {
if (isFeatureMapMove(comparison, feature, diffCandidate, values, source)) {
final Object entryValue = ((FeatureMap.Entry)diffCandidate).getValue();
if (entryValue instanceof EObject) {
final EObject leftObject = comparison.getMatch((EObject)entryValue).getLeft();
return !ComparisonUtil.isContainedInFeatureMap(leftObject);
}
}
return false;
}
/**
* Checks if the given candidate is a FeatureMap change of type DifferenceKind.CHANGE or
* DifferenceKind.MOVE.
*
* @param comparison
* The comparison object.
* @param feature
* The feature which values are to be checked.
* @param diffCandidate
* The given candidate for which we search an equivalent value.
* @param values
* The entries in which we search.
* @param source
* The given DifferenceSource of the entry.
* @return true if the given candidate is a FeatureMap change of type DifferenceKind.CHANGE or
* DifferenceKind.MOVE, false otherwise.
*/
private boolean isFeatureMapChangeOrMove(final Comparison comparison, final EStructuralFeature feature,
final Object diffCandidate, final List<Object> values, final DifferenceSource source) {
return isFeatureMapChange(comparison, feature, diffCandidate, values, source)
|| isFeatureMapMove(comparison, feature, diffCandidate, values, source);
}
/**
* Checks if the entry has its value equivalent in the list of entries, with a different key.
*
* @param equality
* Use to compare objects by the org.eclipse.emf.compare.match.IMatchEngine.
* @param entry
* The given FeatureMap.Entry for which we search an equivalent value.
* @param entries
* The entries in which we search.
* @return true if the entry has its value equivalent in the list of entries, with a different key, false
* otherwise.
*/
private boolean isFeatureMapEntryKeyChange(final IEqualityHelper equality, final FeatureMap.Entry entry,
final List<Object> entries) {
final Object entryValue = entry.getValue();
final EStructuralFeature entryKey = entry.getEStructuralFeature();
if (DiffUtil.isContainmentReference(entryKey)) {
for (Object object : entries) {
if (object instanceof FeatureMap.Entry) {
final FeatureMap.Entry featureMapEntry = (FeatureMap.Entry)object;
if (equality.matchingValues(entryValue, featureMapEntry.getValue())) {
return !entryKey.equals(featureMapEntry.getEStructuralFeature());
}
}
}
}
return false;
}
/**
* Checks if the entry's value has its equivalent in the opposite side, and thus is a DifferenceKind.MOVE
* difference. If the FeatureMapEntry is non-contained the method will return {@code false}.
*
* @param comparison
* The comparison object.
* @param entry
* The FeatureMap.Entry which contains the value for which we try to find its equivalent.
* @param side
* The given DifferenceSource of the entry.
* @return {@code true} if the entry's value has its equivalent in the opposite side and is contained
* within the feature map, {@code false} otherwise.
*/
private boolean isFeatureMapValueMove(final Comparison comparison, FeatureMap.Entry entry,
DifferenceSource side) {
final boolean move;
final Object entryValue = entry.getValue();
final EStructuralFeature structuralFeature = entry.getEStructuralFeature();
if (entryValue instanceof EObject && DiffUtil.isContainmentReference(structuralFeature)) {
final Match candidateMatch = comparison.getMatch((EObject)entryValue);
if (candidateMatch == null) {
move = false;
} else {
final EObject value;
if (side == DifferenceSource.LEFT) {
value = candidateMatch.getLeft();
} else {
value = candidateMatch.getRight();
}
final EObject oppositeValue;
if (comparison.isThreeWay()) {
oppositeValue = candidateMatch.getOrigin();
} else {
oppositeValue = candidateMatch.getRight();
}
if (value != null && oppositeValue != null) {
move = !comparison.getEqualityHelper().matchingValues(value.eContainer(),
oppositeValue.eContainer());
} else {
move = false;
}
}
} else {
move = false;
}
return move;
}
/**
* Computes the difference between the sides of the given <code>match</code> for the given single-valued
* <code>attribute</code>.
*
* @param match
* The match which sides we need to check for potential differences.
* @param attribute
* The attribute which values are to be checked.
*/
protected void computeSingleValuedAttributeDifferences(Match match, EAttribute attribute) {
final Comparison comparison = match.getComparison();
final EObject left = match.getLeft();
Object leftValue = UNMATCHED_VALUE;
if (left != null) {
leftValue = safeEGet(left, attribute);
}
final EObject right = match.getRight();
Object rightValue = UNMATCHED_VALUE;
if (right != null) {
rightValue = safeEGet(right, attribute);
}
final IEqualityHelper helper = comparison.getEqualityHelper();
final EObject origin = match.getOrigin();
if (helper.matchingValues(leftValue, rightValue)) {
// Identical values in left and right. The only problematic case is if they do not match the
// origin (and left and right are defined, i.e don't detect attribute change on unmatched)
if (leftValue != UNMATCHED_VALUE && comparison.isThreeWay()) {
final Object originValue;
if (origin == null) {
originValue = null;
} else {
originValue = safeEGet(origin, attribute);
}
final boolean matchingLO = helper.matchingValues(leftValue, originValue);
// if !matchingLO, the same change has been made on both side. This is actually a
// pseudo-conflict.
if (!matchingLO && !isNullOrEmptyString(leftValue)) {
// The same value has been SET or CHANGED on both sides
// (depending on whether origin is null or not)
getDiffProcessor().attributeChange(match, attribute, leftValue, DifferenceKind.CHANGE,
DifferenceSource.LEFT);
getDiffProcessor().attributeChange(match, attribute, rightValue, DifferenceKind.CHANGE,
DifferenceSource.RIGHT);
} else if (!matchingLO) {
// The same value has been UNSET from both sides
getDiffProcessor().attributeChange(match, attribute, originValue, DifferenceKind.CHANGE,
DifferenceSource.LEFT);
getDiffProcessor().attributeChange(match, attribute, originValue, DifferenceKind.CHANGE,
DifferenceSource.RIGHT);
}
}
} else if (origin != null) {
final Object originValue = safeEGet(origin, attribute);
if (helper.matchingValues(leftValue, originValue)) {
Object changedValue = rightValue;
if (isNullOrEmptyString(rightValue)) {
changedValue = originValue;
}
if (rightValue != UNMATCHED_VALUE) {
// Value is in left and origin, but not in the right
getDiffProcessor().attributeChange(match, attribute, changedValue, DifferenceKind.CHANGE,
DifferenceSource.RIGHT);
} else {
// Right is unmatched, left is the same as in the origin. No diff here : the diff is on
// the match itself, not on one of its attributes.
}
} else if (helper.matchingValues(rightValue, originValue)) {
Object changedValue = leftValue;
if (isNullOrEmptyString(leftValue)) {
changedValue = originValue;
}
if (leftValue != UNMATCHED_VALUE) {
// Value is in right and origin, but not in left
getDiffProcessor().attributeChange(match, attribute, changedValue, DifferenceKind.CHANGE,
DifferenceSource.LEFT);
} else {
// Left is unmatched, right is the same as in the origin. No diff here : the diff is on
// the match itself, not on one of its attributes.
}
} else {
/*
* Left and right are different. None match what's in the origin. Those of the two that are
* not unmatched are thus a "change" difference, with a possible conflict.
*/
Object leftChange = leftValue;
if (isNullOrEmptyString(leftValue)) {
leftChange = originValue;
}
Object rightChange = rightValue;
if (isNullOrEmptyString(rightValue)) {
rightChange = originValue;
}
if (leftValue != UNMATCHED_VALUE) {
getDiffProcessor().attributeChange(match, attribute, leftChange, DifferenceKind.CHANGE,
DifferenceSource.LEFT);
}
if (rightValue != UNMATCHED_VALUE) {
getDiffProcessor().attributeChange(match, attribute, rightChange, DifferenceKind.CHANGE,
DifferenceSource.RIGHT);
}
}
} else {
// Left and right values are different, and we have no origin.
if (leftValue != UNMATCHED_VALUE) {
if (isNullOrEmptyString(leftValue)) {
getDiffProcessor().attributeChange(match, attribute, rightValue, DifferenceKind.CHANGE,
DifferenceSource.LEFT);
} else {
getDiffProcessor().attributeChange(match, attribute, leftValue, DifferenceKind.CHANGE,
DifferenceSource.LEFT);
}
}
if (comparison.isThreeWay() && rightValue != UNMATCHED_VALUE) {
if (isNullOrEmptyString(rightValue)) {
getDiffProcessor().attributeChange(match, attribute, leftValue, DifferenceKind.CHANGE,
DifferenceSource.RIGHT);
} else {
getDiffProcessor().attributeChange(match, attribute, rightValue, DifferenceKind.CHANGE,
DifferenceSource.RIGHT);
}
}
}
}
/**
* Returns {@code true} if the given {@code object} is {@code null} or the empty String.
*
* @param object
* The object we need to test.
* @return {@code true} if the given {@code object} is {@code null} or the empty String.
*/
private boolean isNullOrEmptyString(Object object) {
return object == null || "".equals(object); //$NON-NLS-1$
}
/**
* Returns {@code true} if the given {@code object} is {@code null} or the {@link #UNMATCHED_VALUE}.
*
* @param object
* The object we need to test.
* @return {@code true} if the given {@code object} is {@code null} or the {@link #UNMATCHED_VALUE}.
*/
private boolean isNullOrUnmatched(Object object) {
return object == null || object == UNMATCHED_VALUE;
}
/**
* Computes the difference between the sides of the given <code>match</code> for the given single-valued
* <code>reference</code>.
* <p>
* The given {@code reference} cannot be a containment reference.
* </p>
* <p>
* This is only meant for three-way comparisons.
* </p>
*
* @param match
* The match which sides we need to check for potential differences.
* @param reference
* The reference which values are to be checked.
*/
protected void computeSingleValuedReferenceDifferencesThreeWay(Match match, EReference reference) {
final Comparison comparison = match.getComparison();
final EObject left = match.getLeft();
Object leftValue = UNMATCHED_VALUE;
if (left != null) {
leftValue = safeEGet(left, reference);
}
final EObject right = match.getRight();
Object rightValue = UNMATCHED_VALUE;
if (right != null) {
rightValue = safeEGet(right, reference);
}
final EObject origin = match.getOrigin();
Object originValue = UNMATCHED_VALUE;
if (origin != null) {
originValue = safeEGet(origin, reference);
}
boolean distinctValueLO = !comparison.getEqualityHelper().matchingValues(leftValue, originValue);
// consider null and unmatched as the same
distinctValueLO = distinctValueLO
&& !(isNullOrUnmatched(leftValue) && isNullOrUnmatched(originValue));
if (distinctValueLO) {
// Left and origin are distinct
if (leftValue == null || leftValue == UNMATCHED_VALUE) {
// Left has been removed
getDiffProcessor().referenceChange(match, reference, (EObject)originValue,
DifferenceKind.CHANGE, DifferenceSource.LEFT);
} else {
// Left has been set to a new value, or left has been added altogether
getDiffProcessor().referenceChange(match, reference, (EObject)leftValue,
DifferenceKind.CHANGE, DifferenceSource.LEFT);
}
}
boolean distinctValueRO = !comparison.getEqualityHelper().matchingValues(rightValue, originValue);
// consider null and unmatched as the same
distinctValueRO = distinctValueRO
&& !(isNullOrUnmatched(rightValue) && isNullOrUnmatched(originValue));
if (distinctValueRO) {
// Right and origin are distinct
if (rightValue == null || rightValue == UNMATCHED_VALUE) {
// right value is unset, or right has been removed
getDiffProcessor().referenceChange(match, reference, (EObject)originValue,
DifferenceKind.CHANGE, DifferenceSource.RIGHT);
} else {
// Right has been set to a new value, or right has been added altogether
getDiffProcessor().referenceChange(match, reference, (EObject)rightValue,
DifferenceKind.CHANGE, DifferenceSource.RIGHT);
}
}
}
/**
* Computes the difference between the sides of the given <code>match</code> for the given single-valued
* <code>reference</code>.
* <p>
* The given {@code reference} cannot be a containment reference.
* </p>
* <p>
* This is only meant for two-way comparisons.
* </p>
*
* @param match
* The match which sides we need to check for potential differences.
* @param reference
* The reference which values are to be checked.
*/
protected void computeSingleValuedReferenceDifferencesTwoWay(Match match, EReference reference) {
final Comparison comparison = match.getComparison();
final EObject left = match.getLeft();
Object leftValue = UNMATCHED_VALUE;
if (left != null) {
leftValue = safeEGet(left, reference);
}
final EObject right = match.getRight();
Object rightValue = UNMATCHED_VALUE;
if (right != null) {
rightValue = safeEGet(right, reference);
}
boolean distinctValue = !comparison.getEqualityHelper().matchingValues(leftValue, rightValue);
// consider null and unmatched as the same
distinctValue = distinctValue && !(isNullOrUnmatched(leftValue) && isNullOrUnmatched(rightValue));
if (distinctValue) {
if (leftValue == null || leftValue == UNMATCHED_VALUE) {
// left value is unset, or left has been removed
getDiffProcessor().referenceChange(match, reference, (EObject)rightValue,
DifferenceKind.CHANGE, DifferenceSource.LEFT);
} else {
// Left has been set to a new value, or left has been added altogether
getDiffProcessor().referenceChange(match, reference, (EObject)leftValue,
DifferenceKind.CHANGE, DifferenceSource.LEFT);
}
}
}
/**
* This will be used in order to create the {@link FeatureFilter} that should be used by this engine to
* determine the structural features on which it is to try and detect differences.
*
* @return The newly created feature filter.
*/
protected FeatureFilter createFeatureFilter() {
return new FeatureFilter();
}
/**
* Delegates to the diff processor to create the specified feature change.
*
* @param match
* The match on which values we detected a diff.
* @param feature
* The exact feature on which a diff was detected.
* @param value
* The value for which we detected a changed.
* @param kind
* The kind of difference to create.
* @param source
* The source from which originates that diff.
*/
protected void featureChange(Match match, EStructuralFeature feature, Object value, DifferenceKind kind,
DifferenceSource source) {
if (FeatureMapUtil.isFeatureMap(feature)) {
getDiffProcessor().featureMapChange(match, (EAttribute)feature, value, kind, source);
} else if (feature instanceof EAttribute) {
getDiffProcessor().attributeChange(match, (EAttribute)feature, value, kind, source);
} else if (value instanceof EObject) {
getDiffProcessor().referenceChange(match, (EReference)feature, (EObject)value, kind, source);
}
}
/**
* This will return the diff processor that has been created through {@link #createDiffProcessor()} for
* this differencing process.
*
* @return The diff processor to notify of difference detections.
*/
protected final IDiffProcessor getDiffProcessor() {
return diffProcessor;
}
}