blob: 70e424abe55cbaa064509078bbf3f1b0f86bc047 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012, 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
* Stefan Dirix - bug 441172
* Philip Langer - add containsConflictOfTypes(ConflictKind...)
*******************************************************************************/
package org.eclipse.emf.compare.utils;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Predicates.and;
import static org.eclipse.emf.compare.internal.utils.ComparisonUtil.isDeleteOrUnsetDiff;
import com.google.common.base.Predicate;
import java.util.Arrays;
import java.util.Iterator;
import org.eclipse.emf.compare.AttributeChange;
import org.eclipse.emf.compare.Conflict;
import org.eclipse.emf.compare.ConflictKind;
import org.eclipse.emf.compare.Diff;
import org.eclipse.emf.compare.DifferenceKind;
import org.eclipse.emf.compare.DifferenceSource;
import org.eclipse.emf.compare.DifferenceState;
import org.eclipse.emf.compare.FeatureMapChange;
import org.eclipse.emf.compare.Match;
import org.eclipse.emf.compare.ReferenceChange;
import org.eclipse.emf.compare.ResourceLocationChange;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EGenericType;
import org.eclipse.emf.ecore.ENamedElement;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.util.FeatureMap;
/**
* This class will provide a number of Predicates that can be used to retrieve particular {@link Diff}s from
* an iterable.
*
* @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
*/
public final class EMFComparePredicates {
/**
* This can be used to test whether a given Diff has no conflict object associated.
*
* @since 3.1
*/
public static final Predicate<? super Diff> WITHOUT_CONFLICT = new Predicate<Diff>() {
public boolean apply(Diff input) {
return input != null && input.getConflict() == null;
}
};
/**
* This can be used to check whether a given diff is a containment reference change.
*
* @since 3.1
*/
public static final Predicate<? super Diff> CONTAINMENT_REFERENCE_CHANGE = new Predicate<Diff>() {
public boolean apply(Diff input) {
return input instanceof ReferenceChange
&& ((ReferenceChange)input).getReference().isContainment();
}
};
/**
* Predicate used to know if the given EObject is an EGenericType without eTypeArguments. When an
* EGenericType has arguments, it seems that the mutually derived references are not applicable in this
* case.
*
* @return true, if the given EObject is an EGenericType without eTypeArguments, false otherwise.
*/
public static final Predicate<? super EObject> IS_EGENERIC_TYPE_WITHOUT_PARAMETERS = new Predicate<EObject>() {
public boolean apply(EObject input) {
final boolean isEGenericWithoutParams;
if (input instanceof EGenericType && ((EGenericType)input).getETypeArguments().isEmpty()) {
if (input.eContainer() instanceof EGenericType) {
EGenericType eGenericTypeContainer = (EGenericType)(input.eContainer());
isEGenericWithoutParams = !(eGenericTypeContainer.getETypeArguments().contains(input)
|| input.equals(eGenericTypeContainer.getELowerBound()) || input
.equals(eGenericTypeContainer.getEUpperBound()));
} else {
isEGenericWithoutParams = true;
}
} else {
isEGenericWithoutParams = false;
}
return isEGenericWithoutParams;
}
};
/**
* This class does not need to be instantiated.
*/
private EMFComparePredicates() {
// Hides default constructor
}
/**
* This predicate can be used to check whether a given Diff represents the modification of a single-valued
* reference going by the given {@code referenceName} on an EObject which name matches
* {@code qualifiedName}. This can be used both on three-way and two-way Diffs : if three-way, we'll
* consider that the {@code fromQualifiedName} can be either one of the right or origin values, and the
* {@code toQualifiedName} to be either left or right. on two-way diffs however, {@code fromQualifiedName}
* can only be the right value, and {@code toQualifiedName} will be the left one.
* <p>
* Note that in order for this to work, we expect the EObjects to have a "name" feature returning a String
* for us to compare it with the given qualified name.
* </p>
*
* @param qualifiedName
* Qualified name of the EObject which we expect to present a ReferenceChange.
* @param referenceName
* Name of the single-valued reference on which we expect a change.
* @param fromQualifiedName
* The original value of this reference.
* @param toQualifiedName
* The value to which this reference has been changed.
* @return The created predicate.
*/
@SuppressWarnings("unchecked")
public static Predicate<? super Diff> changedReference(final String qualifiedName,
final String referenceName, final String fromQualifiedName, final String toQualifiedName) {
final Predicate<? super Diff> valuesMatch = new ReferenceValuesMatch(referenceName,
fromQualifiedName, toQualifiedName);
return and(ofKind(DifferenceKind.CHANGE), onEObject(qualifiedName), valuesMatch);
}
/**
* This predicate can be used to check whether a given Diff represents the addition of a value in a
* multi-valued attribute going by {@code attributeName} on an EObject which name matches
* {@code qualifiedName}.
* <p>
* Note that in order for this to work, we expect the EObjects to have a "name" feature returning a String
* for us to compare it with the given qualified name.
* </p>
*
* @param qualifiedName
* Qualified name of the EObject which we expect to present an AttributeChange.
* @param attributeName
* Name of the multi-valued attribute on which we expect a change.
* @param addedValue
* The value we expect to have been added to this attribute.
* @return The created predicate.
*/
@SuppressWarnings("unchecked")
public static Predicate<? super Diff> addedToAttribute(final String qualifiedName,
final String attributeName, final Object addedValue) {
// This is only meant for multi-valued attributes
return and(ofKind(DifferenceKind.ADD), onEObject(qualifiedName), attributeValueMatch(attributeName,
addedValue, true));
}
/**
* This predicate can be used to check whether a given Diff represents the addition of a value in a
* multi-valued reference going by {@code referenceName} on an EObject which name matches
* {@code qualifiedName}.
* <p>
* Note that in order for this to work, we expect the EObjects to have a "name" feature returning a String
* for us to compare it with the given qualified name.
* </p>
*
* @param qualifiedName
* Qualified name of the EObject which we expect to present a ReferenceChange.
* @param referenceName
* Name of the multi-valued reference on which we expect a change.
* @param addedQualifiedName
* Qualified name of the EObject which we expect to have been added to this reference.
* @return The created predicate.
*/
@SuppressWarnings("unchecked")
public static Predicate<? super Diff> addedToReference(final String qualifiedName,
final String referenceName, final String addedQualifiedName) {
// This is only meant for multi-valued references
return and(ofKind(DifferenceKind.ADD), onEObject(qualifiedName), referenceValueMatch(referenceName,
addedQualifiedName, true));
}
/**
* This predicate can be used to check whether a given Diff represents the addition of a value in a
* multi-valued reference going by {@code referenceName} on an EObject which name matches
* {@code qualifiedName}.
* <p>
* Note that in order for this to work, we expect the EObjects to have a "name" feature returning a String
* for us to compare it with the given qualified name.
* </p>
*
* @param qualifiedName
* Qualified name of the EObject which we expect to present a ReferenceChange.
* @param referenceName
* Name of the multi-valued reference on which we expect a change.
* @param addedQualifiedName
* Qualified name of the EObject which we expect to have been added to this reference.
* @param featureDelegateForAddedName
* The optional feature to define the name of the objects which we expect to have been added to
* this reference. May be null.
* @return The created predicate.
*/
@SuppressWarnings("unchecked")
public static Predicate<? super Diff> addedToReference(final String qualifiedName,
final String referenceName, final String addedQualifiedName,
final EStructuralFeature featureDelegateForAddedName) {
// This is only meant for multi-valued references
return and(ofKind(DifferenceKind.ADD), onEObject(qualifiedName), referenceValueMatch(referenceName,
addedQualifiedName, true, featureDelegateForAddedName));
}
/**
* This predicate can be used to check whether a given Diff represents the moving of a value within a
* multi-valued attribute going by {@code attributeName} on an EObject which name matches
* {@code qualifiedName}.
* <p>
* Note that in order for this to work, we expect the EObjects to have a "name" feature returning a String
* for us to compare it with the given qualified name.
* </p>
*
* @param qualifiedName
* Qualified name of the EObject which we expect to present an AttributeChange.
* @param attributeName
* Name of the multi-valued attribute on which we expect a change.
* @param removedValue
* Value which we expect to have been moved within this attribute.
* @return The created predicate.
*/
@SuppressWarnings("unchecked")
public static Predicate<? super Diff> movedInAttribute(final String qualifiedName,
final String attributeName, final Object removedValue) {
// This is only meant for multi-valued attributes
return and(ofKind(DifferenceKind.MOVE), onEObject(qualifiedName), attributeValueMatch(attributeName,
removedValue, true));
}
/**
* This predicate can be used to check whether a given Diff represents the moving of a value within a
* multi-valued reference going by {@code referenceName} on an EObject which name matches
* {@code qualifiedName}.
* <p>
* Note that in order for this to work, we expect the EObjects to have a "name" feature returning a String
* for us to compare it with the given qualified name.
* </p>
*
* @param qualifiedName
* Qualified name of the EObject which we expect to present a ReferenceChange.
* @param referenceName
* Name of the multi-valued reference on which we expect a change.
* @param removedQualifiedName
* Qualified name of the EObject which we expect to have been moved within this reference.
* @return The created predicate.
*/
@SuppressWarnings("unchecked")
public static Predicate<? super Diff> movedInReference(final String qualifiedName,
final String referenceName, final String removedQualifiedName) {
// This is only meant for multi-valued references
return and(ofKind(DifferenceKind.MOVE), onEObject(qualifiedName), referenceValueMatch(referenceName,
removedQualifiedName, true));
}
/**
* This predicate can be used to check whether a given Diff is a {@link ReferenceChange} representing the
* eOpposite of the {@code diff} argument.
*
* @param diff
* The {@link ReferenceChange} against which is checked if an eOpposite relation exists.
* @return The created predicate.
* @since 3.2
*/
public static Predicate<Diff> isDiffOnEOppositeOf(final ReferenceChange diff) {
return new Predicate<Diff>() {
public boolean apply(Diff input) {
return input instanceof ReferenceChange
&& diff.getReference().getEOpposite() == ((ReferenceChange)input).getReference();
}
};
}
/**
* This predicate can be used to check whether a given Diff is equivalent to the {@code diff} argument.
*
* @param diff
* The {@link ReferenceChange} against which is checked if an equivalence relation exists.
* @return The created predicate.
* @since 3.2
*/
public static Predicate<Diff> isEquivalentTo(final Diff diff) {
return new Predicate<Diff>() {
public boolean apply(Diff input) {
return input.getEquivalence() != null
&& input.getEquivalence().getDifferences().contains(diff);
}
};
}
/**
* This predicate can be used to check whether a given Diff is a {@link ReferenceChange} with the same
* reference as the {@code diff} argument.
*
* @param diff
* The {@link ReferenceChange} against which is checked whether it has the same reference.
* @return The created predicate.
* @since 3.2
*/
public static Predicate<Diff> hasSameReferenceAs(final ReferenceChange diff) {
return new Predicate<Diff>() {
public boolean apply(Diff input) {
return input instanceof ReferenceChange
&& diff.getReference() == ((ReferenceChange)input).getReference();
}
};
}
/**
* This predicate can be used to check whether a given Diff represents the deletion of a value from a
* multi-valued attribute going by {@code attributeName} on an EObject which name matches
* {@code qualifiedName}.
* <p>
* Note that in order for this to work, we expect the EObjects to have a "name" feature returning a String
* for us to compare it with the given qualified name.
* </p>
*
* @param qualifiedName
* Qualified name of the EObject which we expect to present an AttributeChange.
* @param attributeName
* Name of the multi-valued attribute on which we expect a change.
* @param removedValue
* Value which we expect to have been removed from this attribute.
* @return The created predicate.
*/
@SuppressWarnings("unchecked")
public static Predicate<? super Diff> removedFromAttribute(final String qualifiedName,
final String attributeName, final Object removedValue) {
// This is only meant for multi-valued attributes
return and(ofKind(DifferenceKind.DELETE), onEObject(qualifiedName), attributeValueMatch(
attributeName, removedValue, true));
}
/**
* This predicate can be used to check whether a given Diff represents the deletion of a value from a
* multi-valued reference going by {@code referenceName} on an EObject which name matches
* {@code qualifiedName}.
* <p>
* Note that in order for this to work, we expect the EObjects to have a "name" feature returning a String
* for us to compare it with the given qualified name.
* </p>
*
* @param qualifiedName
* Qualified name of the EObject which we expect to present a ReferenceChange.
* @param referenceName
* Name of the multi-valued reference on which we expect a change.
* @param removedQualifiedName
* Qualified name of the EObject which we expect to have been removed from this reference.
* @return The created predicate.
*/
@SuppressWarnings("unchecked")
public static Predicate<? super Diff> removedFromReference(final String qualifiedName,
final String referenceName, final String removedQualifiedName) {
// This is only meant for multi-valued references
return and(ofKind(DifferenceKind.DELETE), onEObject(qualifiedName), referenceValueMatch(
referenceName, removedQualifiedName, true));
}
/**
* This predicate can be used to check whether a given Diff represents the deletion of a value from a
* multi-valued reference going by {@code referenceName} on an EObject which name matches
* {@code qualifiedName}.
* <p>
* Note that in order for this to work, we expect the EObjects to have a "name" feature returning a String
* for us to compare it with the given qualified name.
* </p>
*
* @param qualifiedName
* Qualified name of the EObject which we expect to present a ReferenceChange.
* @param referenceName
* Name of the multi-valued reference on which we expect a change.
* @param removedQualifiedName
* Qualified name of the EObject which we expect to have been removed from this reference.
* @param featureDelegateForRemovedName
* The optional feature to define the name of the objects which we expect to have been removed
* from this reference. May be null.
* @return The created predicate.
*/
@SuppressWarnings("unchecked")
public static Predicate<? super Diff> removedFromReference(final String qualifiedName,
final String referenceName, final String removedQualifiedName,
final EStructuralFeature featureDelegateForRemovedName) {
// This is only meant for multi-valued references
return and(ofKind(DifferenceKind.DELETE), onEObject(qualifiedName), referenceValueMatch(
referenceName, removedQualifiedName, true, featureDelegateForRemovedName));
}
/**
* This predicate can be used to check whether a given Diff represents the modification of a single-valued
* attribute going by the given {@code attributeName} on an EObject which name matches
* {@code qualifiedName}. This can be used both on three-way and two-way Diffs : if three-way, we'll
* consider that the {@code fromValue} can be either one of the right or origin values, and the
* {@code toValue} to be either left or right. on two-way diffs however, {@code fromValue} can only be the
* right value, and {@code toValue} will be the left one.
* <p>
* Note that in order for this to work, we expect the EObjects to have a "name" feature returning a String
* for us to compare it with the given qualified name.
* </p>
*
* @param qualifiedName
* Qualified name of the EObject which we expect to present an AttributeChange.
* @param attributeName
* Name of the single-valued attribute on which we expect a change.
* @param fromValue
* The original value of this attribute.
* @param toValue
* The value to which this attribute has been changed.
* @return The created predicate.
*/
@SuppressWarnings("unchecked")
public static Predicate<? super Diff> changedAttribute(final String qualifiedName,
final String attributeName, final Object fromValue, final Object toValue) {
final Predicate<? super Diff> valuesMatch = new AttributeValuesMatch(attributeName, fromValue,
toValue);
return and(ofKind(DifferenceKind.CHANGE), onEObject(qualifiedName), valuesMatch);
}
/**
* This predicate can be used to check whether a given Diff represents the addition of an EObject matching
* the given qualified name. Namely, it will check that that Diff is a ReferenceChange, that one of its
* Match sides correspond to the given qualified name's ancestors, and that its value correspond to the
* given qualified name's last segment.
* <p>
* For example, {@code added("extlibrary.BookCategory.Encyclopedia")} will check that an EObject named
* "Encyclopedia" has been added under the container "extlibrary.BookCategory". Note that
* {@code added("emf.compare.Match")} will <b>not</b> match a difference on the EObject
* "org.eclipse.emf.compare.Match". The qualified name must be absolute.
* </p>
* <p>
* Note that in order for this to work, we expect the EObjects to have a "name" feature returning a
* String.
* </p>
*
* @param qualifiedName
* The qualified name of the EObject we expect to have been added.
* @return The created predicate.
*/
@SuppressWarnings("unchecked")
public static Predicate<? super Diff> added(final String qualifiedName) {
final int parentEndIndex = qualifiedName.lastIndexOf('.');
if (parentEndIndex >= 0) {
final String ancestors = qualifiedName.substring(0, parentEndIndex);
final String objectName = qualifiedName.substring(parentEndIndex + 1);
return and(ofKind(DifferenceKind.ADD), onEObject(ancestors), valueNameMatches(objectName));
}
return and(valueNameMatches(qualifiedName), ofKind(DifferenceKind.ADD));
}
/**
* This predicate can be used to check whether a given Diff represents the move of an EObject matching the
* given qualified name. Namely, it will check that that Diff is a ReferenceChange, that one of its Match
* sides correspond to the given qualified name's ancestors, and that its value correspond to the given
* qualified name's last segment.
* <p>
* Note that in order for this to work, we expect the EObjects to have a "name" feature returning a
* String.
* </p>
*
* @param qualifiedName
* The qualified name of the EObject we expect to have been moved.
* @param referenceName
* Name of the reference in which we expect a child to have been added.
* @return The created predicate.
*/
@SuppressWarnings("unchecked")
public static Predicate<? super Diff> moved(final String qualifiedName, final String referenceName) {
final int parentEndIndex = qualifiedName.lastIndexOf('.');
if (parentEndIndex >= 0) {
final String ancestors = qualifiedName.substring(0, parentEndIndex);
final String objectName = qualifiedName.substring(parentEndIndex + 1);
return and(ofKind(DifferenceKind.MOVE), onEObject(ancestors), onFeature(referenceName),
valueNameMatches(objectName));
}
return and(ofKind(DifferenceKind.MOVE), valueNameMatches(qualifiedName), onFeature(referenceName));
}
/**
* This predicate can be used to check whether a given Diff represents the removal of an EObject matching
* the given qualified name. Namely, it will check that that Diff is a ReferenceChange, that one of its
* Match sides correspond to the given qualified name's ancestors, and that its value correspond to the
* given qualified name's last segment.
* <p>
* For example, {@code removed("extlibrary.BookCategory.Encyclopedia")} will check that an EObject named
* "Encyclopedia" has been removed from the container "extlibrary.BookCategory". Note that
* {@code removed("emf.compare.Match")} will <b>not</b> match a difference on the EObject
* "org.eclipse.emf.compare.Match". The qualified name must be absolute.
* </p>
* <p>
* Note that in order for this to work, we expect the EObjects to have a "name" feature returning a
* String.
* </p>
*
* @param qualifiedName
* The qualified name of the EObject we expect to have been removed.
* @return The created predicate.
*/
@SuppressWarnings("unchecked")
public static Predicate<? super Diff> removed(final String qualifiedName) {
final int parentEndIndex = qualifiedName.lastIndexOf('.');
if (parentEndIndex >= 0) {
final String ancestors = qualifiedName.substring(0, parentEndIndex);
final String objectName = qualifiedName.substring(parentEndIndex + 1);
return and(ofKind(DifferenceKind.DELETE), onEObject(ancestors), valueNameMatches(objectName));
}
return and(valueNameMatches(qualifiedName), ofKind(DifferenceKind.DELETE));
}
/**
* This can be used to check that a given Diff correspond to either an {@link AttributeChange}, a
* {@link FeatureMapChange} or a {@link ReferenceChange}, and that the corresponding reference or
* attribute matches the given {@code featureName}.
*
* @param featureName
* Name of the feature on which we expect a change.
* @return The created predicate.
*/
public static Predicate<? super Diff> onFeature(final String featureName) {
return new Predicate<Diff>() {
public boolean apply(Diff input) {
final EStructuralFeature affectedFeature;
if (input instanceof AttributeChange) {
affectedFeature = ((AttributeChange)input).getAttribute();
} else if (input instanceof ReferenceChange) {
affectedFeature = ((ReferenceChange)input).getReference();
} else if (input instanceof FeatureMapChange) {
affectedFeature = ((FeatureMapChange)input).getAttribute();
} else {
return false;
}
return featureName.equals(affectedFeature.getName());
}
};
}
/**
* Accept only diffs that inherit either AttributeChange, ReferenceChange, or FeatureMapChange that
* concern the given feature.
*
* @param feature
* Feature to deal with
* @return a new predicate that accepts diffs that concern the given feature.
*/
public static Predicate<Diff> onFeature(EStructuralFeature feature) {
return new OnFeature(feature);
}
/**
* This can be used to check that a given Diff originates from the given {@code source} side.
*
* @param source
* The side from which we expect this diff to originate.
* @return The created predicate.
*/
public static Predicate<? super Diff> fromSide(final DifferenceSource source) {
return new Predicate<Diff>() {
public boolean apply(Diff input) {
return input != null && input.getSource() == source;
}
};
}
/**
* This can be used to check that a given Diff originates from the given {@code source} side.
*
* @param diff
* The diff the side of which will be used to filter.
* @return The created predicate.
*/
public static Predicate<Diff> sameSideAs(final Diff diff) {
if (diff == null) {
throw new IllegalArgumentException();
}
return new Predicate<Diff>() {
public boolean apply(Diff input) {
return input != null && input.getSource() == diff.getSource();
}
};
}
/**
* This can be used in order to check that a Diff has been detected on the given EObject.
*
* @param eObject
* The EObject which we expect the diff to concern.
* @return The created predicate.
*/
public static Predicate<? super Diff> onEObject(final EObject eObject) {
return new Predicate<Diff>() {
public boolean apply(Diff input) {
if (input == null) {
return false;
}
final Match match = input.getMatch();
return match.getLeft() == eObject || match.getRight() == eObject
|| match.getOrigin() == eObject;
}
};
}
/**
* This can be used in order to check whether a Diff has been detected on an EObject matching the given
* qualified name.
* <p>
* For this to work, we expect the EObjects to have a feature named "name" returning a String.
* </p>
*
* @param qualifiedName
* The qualified name of the EObject we expect that diff to concern.
* @return The created predicate.
*/
public static Predicate<? super Diff> onEObject(final String qualifiedName) {
return new Predicate<Diff>() {
public boolean apply(Diff input) {
if (input == null || input instanceof ResourceLocationChange) {
return false;
}
final Match match = input.getMatch();
return match(match.getLeft(), qualifiedName, null)
|| match(match.getRight(), qualifiedName, null)
|| match(match.getOrigin(), qualifiedName, null);
}
};
}
/**
* This can be used in order to check whether a Diff has been detected on an EObject matching the given
* qualified name or the qualified name under the given feature.
* <p>
* For this to work, we expect the EObjects to have a feature named "name" returning a String or to have
* the given feature (String or EObject with a feature named "name").
* </p>
*
* @param qualifiedName
* The qualified name of the EObject we expect that diff to concern.
* @param featureDelegate
* The optional feature to define the name of the objects. May be null.
* @return The created predicate.
*/
public static Predicate<? super Diff> onEObject(final String qualifiedName,
final EStructuralFeature featureDelegate) {
return new Predicate<Diff>() {
public boolean apply(Diff input) {
if (input == null) {
return false;
}
final Match match = input.getMatch();
return match(match.getLeft(), qualifiedName, featureDelegate)
|| match(match.getRight(), qualifiedName, featureDelegate)
|| match(match.getOrigin(), qualifiedName, featureDelegate);
}
};
}
/**
* This predicate can be used to check whether a particular diff is of the given {@code kind}. This is
* mainly used to differentiate additions from deletions.
*
* @param kind
* The kind we expect this diff to have.
* @return The created predicate.
*/
public static Predicate<? super Diff> ofKind(final DifferenceKind kind) {
return new Predicate<Diff>() {
public boolean apply(Diff input) {
return input != null && input.getKind() == kind;
}
};
}
/**
* Accept only diffs of the given kinds.
*
* @param kind1
* first kind of diff to accept
* @param kind2
* second kind of diff to accept
* @return The created predicate.
*/
public static Predicate<Diff> ofKind(final DifferenceKind kind1, final DifferenceKind kind2) {
checkNotNull(kind1);
checkNotNull(kind2);
return new Predicate<Diff>() {
public boolean apply(Diff input) {
return input != null && (input.getKind() == kind1 || input.getKind() == kind2);
}
};
}
/**
* Accept only diffs whose value matches the given value.
*
* @param helper
* The helper to match values
* @param value
* The value to match
* @return The created predicate.
*/
public static Predicate<Diff> valueMatches(final IEqualityHelper helper, final Object value) {
return new Predicate<Diff>() {
public boolean apply(Diff input) {
if (input instanceof ReferenceChange) {
return helper.matchingValues(value, ((ReferenceChange)input).getValue());
} else if (input instanceof AttributeChange) {
return helper.matchingValues(value, ((AttributeChange)input).getValue());
} else if (input instanceof FeatureMapChange) {
return helper.matchingValues(value, ((FeatureMap.Entry)((FeatureMapChange)input)
.getValue()).getValue());
}
return false;
}
};
}
/**
* This predicate can be used in order to check that a particular Diff describes either a
* {@link ReferenceChange}, {@link AttributeChange} or {@link FeatureMapChange} for the given
* {@code expectedValue}.
* <p>
* For example, this could be used to check that the given value has indeed been added to a reference or
* attribute, though such checks are more easily performed through {@link #addedIn(EObject, EObject)} or
* {@link #removedFrom(EObject, EObject)}.
* </p>
*
* @param expectedValue
* The value which we expect to have changed and detected through a Diff.
* @return The created predicate.
*/
public static Predicate<? super Diff> valueIs(final Object expectedValue) {
return new Predicate<Diff>() {
public boolean apply(Diff input) {
final Object value;
if (input instanceof ReferenceChange) {
value = ((ReferenceChange)input).getValue();
} else if (input instanceof AttributeChange) {
value = ((AttributeChange)input).getValue();
} else if (input instanceof FeatureMapChange) {
value = ((FeatureMapChange)input).getValue();
} else {
return false;
}
return value == expectedValue || (value != null && value.equals(expectedValue));
}
};
}
/**
* This predicate can be used to check whether a given Diff describes an AttributeChange with the given
* {@code attributeName} and which changed value corresponds to the given {@code expectedValue}.
*
* @param attributeName
* The name of the attribute for which we seek an AttributeChange.
* @param expectedValue
* The value we expect to correspond to this AttributeChange.
* @param multiValued
* Tells us to check for either multi- or single-valued reference changes.
* @return The created predicate.
*/
public static Predicate<? super Diff> attributeValueMatch(final String attributeName,
final Object expectedValue, final boolean multiValued) {
return new Predicate<Diff>() {
public boolean apply(Diff input) {
if (input instanceof AttributeChange
&& ((AttributeChange)input).getAttribute().getName().equals(attributeName)
&& ((AttributeChange)input).getAttribute().isMany() == multiValued) {
final Object value = ((AttributeChange)input).getValue();
return input.getMatch().getComparison().getEqualityHelper().matchingAttributeValues(
value, expectedValue);
}
return false;
}
};
}
/**
* This predicate can be used to check whether a given Diff describes a ReferenceChange with the given
* {@code referenceName} and which changed value corresponds to the given {@code qualifiedName}.
* <p>
* For this to work, we expect the EObject to have a feature named "name" returning a String for us to try
* and match it.
* </p>
*
* @param referenceName
* The reference for which we seek a ReferenceChange.
* @param qualifiedName
* The qualified name of the EObject on which we detected a change.
* @param multiValued
* Tells us to check for either multi- or single-valued reference changes.
* @return The created predicate.
*/
public static Predicate<? super Diff> referenceValueMatch(final String referenceName,
final String qualifiedName, final boolean multiValued) {
return new Predicate<Diff>() {
public boolean apply(Diff input) {
if (input instanceof ReferenceChange
&& ((ReferenceChange)input).getReference().getName().equals(referenceName)
&& ((ReferenceChange)input).getReference().isMany() == multiValued) {
final EObject value = ((ReferenceChange)input).getValue();
return qualifiedName != null && match(value, qualifiedName, null);
}
return false;
}
};
}
/**
* This predicate can be used to check whether a given Diff describes a ReferenceChange with the given
* {@code referenceName} and which changed value corresponds to the given {@code qualifiedName} or the
* qualified name under the given {@code featureDelegate}.
* <p>
* For this to work, we expect the EObject to have a feature named "name" returning a String or to have
* the given feature (String or EObject with a feature named "name") for us to try and match it.
* </p>
*
* @param referenceName
* The reference for which we seek a ReferenceChange.
* @param qualifiedName
* The qualified name of the EObject on which we detected a change.
* @param multiValued
* Tells us to check for either multi- or single-valued reference changes.
* @param featureDelegate
* The optional feature to define the name of the objects. May be null.
* @return The created predicate.
*/
public static Predicate<? super Diff> referenceValueMatch(final String referenceName,
final String qualifiedName, final boolean multiValued, final EStructuralFeature featureDelegate) {
return new Predicate<Diff>() {
public boolean apply(Diff input) {
if (input instanceof ReferenceChange
&& ((ReferenceChange)input).getReference().getName().equals(referenceName)
&& ((ReferenceChange)input).getReference().isMany() == multiValued) {
final EObject value = ((ReferenceChange)input).getValue();
return qualifiedName != null && match(value, qualifiedName, featureDelegate);
}
return false;
}
};
}
/**
* This can be used to check whether a given Diff describes either a {@link ReferenceChange} on an EObject
* which name is {@code expectedName}.
* <p>
* For this to work, we expect the EObject to have a feature named "name" returning a String for us to try
* and match it.
* </p>
*
* @param expectedName
* The name of the EObject which we expect as a changed reference value.
* @return The created predicate.
*/
public static Predicate<? super Diff> valueNameMatches(final String expectedName) {
return new Predicate<Diff>() {
public boolean apply(Diff input) {
final EObject value;
if (input instanceof ReferenceChange) {
value = ((ReferenceChange)input).getValue();
} else {
return false;
}
return internalMatch(value, expectedName, null);
}
};
}
/**
* This can be used to check whether a given Diff describes either a {@link ReferenceChange} on an EObject
* which name is {@code expectedName} or which the given feature provides the {@code expectedName}.
* <p>
* For this to work, we expect the EObject to have a feature named "name" returning a String or to have
* the given feature (String or EObject with a feature named "name") for us to try and match it.
* </p>
*
* @param expectedName
* The name of the EObject which we expect as a changed reference value.
* @param featureDelegate
* The optional feature to define the name of the objects. May be null.
* @return The created predicate.
*/
public static Predicate<? super Diff> valueNameMatches(final String expectedName,
final EStructuralFeature featureDelegate) {
return new Predicate<Diff>() {
public boolean apply(Diff input) {
final EObject value;
if (input instanceof ReferenceChange) {
value = ((ReferenceChange)input).getValue();
} else {
return false;
}
return internalMatch(value, expectedName, featureDelegate);
}
};
}
/**
* This can be used to check whether a given Diff has a conflict of one of the given type.
*
* @param kinds
* Type(s) of the conflict(s) we seek.
* @return The created predicate.
*/
public static Predicate<? super Diff> hasConflict(final ConflictKind... kinds) {
return new Predicate<Diff>() {
public boolean apply(Diff input) {
return input != null && input.getConflict() != null
&& Arrays.asList(kinds).contains(input.getConflict().getKind());
}
};
}
/**
* This can be used to check whether a given Diff is in (one of) the given state(s).
*
* @param states
* State(s) in which we need a Diff to be.
* @return The created predicate.
*/
public static Predicate<? super Diff> hasState(final DifferenceState... states) {
return new Predicate<Diff>() {
public boolean apply(Diff input) {
return input != null && Arrays.asList(states).contains(input.getState());
}
};
}
/**
* Predicate builder for diffs that can conflict with the given diff.
*
* @param diff
* The diff
* @return A predicate that accepts diffs that might conflict with the given diff.
*/
public static Predicate<Diff> possiblyConflictingWith(Diff diff) {
return new ConflictCandidateFilter(diff);
}
/**
* This can be used to check whether a given Conflict is of one of the given kind.
*
* @param kinds
* Type(s) of the conflict(s) we seek.
* @return The created predicate.
*/
public static Predicate<? super Conflict> containsConflictOfTypes(final ConflictKind... kinds) {
return new Predicate<Conflict>() {
public boolean apply(Conflict input) {
return input != null && input.getKind() != null
&& Arrays.asList(kinds).contains(input.getKind());
}
};
}
/**
* This can be used to check whether a given diff is a containment reference change.
*
* @return The created predicate.
* @deprecated use {@link #CONTAINMENT_REFERENCE_CHANGE};
*/
@Deprecated
public static Predicate<? super Diff> containmentReferenceChange() {
return CONTAINMENT_REFERENCE_CHANGE;
}
/**
* This will be used to check that a given {@link EObject} corresponds to the given {@code qualifiedName}.
* <p>
* For example, {@code match("extlibrary.BookCategory.Encyclopedia")} will return {@code true} for an
* EObject named "Encyclopedia" under the container "extlibrary.BookCategory". Note, however that
* {@code match("emf.compare.Match")} will <b>not</b> match the EObject "org.eclipse.emf.compare.Match".
* The qualified name must be absolute.
* </p>
* <p>
* For this to work, we expect the EObject to have a feature named "name" returning a String or to have
* the given feature (String or EObject with a feature named "name") for us to try and match it. See also
* {@link #getNameFeature(EObject)}.
* </p>
*
* @param eObject
* The EObject which qualified name we are to check.
* @param qualifiedName
* The expected, <b>absolute</b> qualified name of the given {@code eObject}.
* @param featureDelegate
* The optional feature to define the name of the objects. May be null.
* @return {@code true} if the given {@code eObject} matches the given {@code qualifiedName},
* {@code false} if not, or if we could not determine the "name" feature of that EObject.
* @see #getNameFeature(EObject)
*/
private static boolean match(EObject eObject, String qualifiedName, EStructuralFeature featureDelegate) {
if (eObject == null || qualifiedName == null || qualifiedName.length() == 0) {
return false;
}
final String[] names = qualifiedName.split("\\."); //$NON-NLS-1$
int current = names.length - 1;
boolean matches = internalMatch(eObject, names[current--], featureDelegate);
if (matches) {
EObject container = eObject.eContainer();
while (matches && container != null && current >= 0) {
matches = internalMatch(container, names[current--], featureDelegate);
container = container.eContainer();
}
// This qualified name does not match if there was still a container "above"
// "emf.compare.Match" does not match the EObject "org.eclipse.emf.compare.Match"
matches = matches && container == null;
}
return matches;
}
/**
* This will be used to check whether a given Object matches the given {@code qualifiedName}, considering
* {@code null} as legal values. Namely, this will return {@code true} in the following cases :
* <ul>
* <li>both {@code eObject} and {@code qualifiedName} are {@code null}</li>
* <li>eObject is an instance of {@link EObject} and its qualified name matches the given
* {@code qualifiedName} according to the semantics of {@link #match(EObject, String)}</li>
* </ul>
*
* @param eObject
* The Object which qualified name we are to check. May be {@code null}.
* @param qualifiedName
* The expected, <b>absolute</b> qualified name of the given {@code eObject}. May be
* {@code null}.
* @return {@code true} if the given {@code eObject} matches the given {@code qualifiedName},
* {@code false} if not, or if we could not determine the "name" feature of that EObject.
* @see #match(EObject, String)
*/
private static boolean matchAllowingNull(Object eObject, String qualifiedName) {
if (eObject == null) {
return qualifiedName == null;
}
return qualifiedName != null && eObject instanceof EObject
&& match((EObject)eObject, qualifiedName, null);
}
/**
* Checks that the given {@code eObject}'s name is equal to {@code name}.
* <p>
* For this to work, we expect the EObject to have a feature named "name" returning a String or to have
* the given feature (String or EObject with a feature named "name") for us to try and match it. See also
* {@link #getNameFeature(EObject)}.
* </p>
*
* @param eObject
* the EObject which name we are to check.
* @param name
* The expected name of {@code eObject}.
* @param featureDelegate
* The optional feature to define the name of the objects. May be null.
* @return {@code true} if the given {@code eObject}'s name is equal to the given {@code name},
* {@code false} if not, or if we could not determine the "name" feature of that EObject.
* @see #getNameFeature(EObject)
*/
private static boolean internalMatch(EObject eObject, String name, EStructuralFeature featureDelegate) {
final EStructuralFeature nameFeature = getNameFeature(eObject);
boolean match = false;
if (nameFeature != null) {
final Object featureValue = eObject.eGet(nameFeature);
if (featureValue instanceof String) {
match = featureValue.equals(name);
}
} else if (featureDelegate != null && !featureDelegate.isMany()) {
final Object featureValue = eObject.eGet(featureDelegate, false);
if (featureValue instanceof String) {
match = featureValue.equals(name);
} else if (featureValue instanceof EObject) {
match = internalMatch((EObject)featureValue, name, null);
}
}
return match;
}
/**
* Tries and determine the "name" feature of the given EObject. By default, we only consider
* {@link ENamedElement#name} or a feature of the given {@code eObject}'s EClass which would be named
* "name".
*
* @param eObject
* The EObject for which we are trying to determine a name.
* @return The name feature of the given EObject if we could find one, {@code null} otherwise.
*/
private static EStructuralFeature getNameFeature(EObject eObject) {
if (eObject instanceof ENamedElement) {
return EcorePackage.eINSTANCE.getENamedElement_Name();
}
EStructuralFeature nameFeature = null;
final Iterator<EStructuralFeature> features = eObject.eClass().getEAllStructuralFeatures().iterator();
while (nameFeature == null && features.hasNext()) {
final EStructuralFeature feature = features.next();
if ("name".equals(feature.getName())) { //$NON-NLS-1$
nameFeature = feature;
}
}
return nameFeature;
}
/**
* This particular predicate will be used to check that a given Diff corresponds to a ReferenceChange on a
* given reference, with known "original" and "changed" values.
*
* @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
*/
private static final class ReferenceValuesMatch implements Predicate<Diff> {
/** Name of the reference we expect to have been changed. */
private final String referenceName;
/** Qualified name of the expected original value of this reference. */
private final String fromQualifiedName;
/** Qualified name of the value to which this reference is expected to have changed. */
private final String toQualifiedName;
/**
* Instantiates this predicate given the values it is meant to match.
*
* @param referenceName
* Name of the single-valued reference on which we expect a change.
* @param fromQualifiedName
* The original value of this reference.
* @param toQualifiedName
* The value to which this reference has been changed.
*/
public ReferenceValuesMatch(String referenceName, String fromQualifiedName, String toQualifiedName) {
this.referenceName = referenceName;
this.fromQualifiedName = fromQualifiedName;
this.toQualifiedName = toQualifiedName;
}
/**
* {@inheritDoc}
*
* @see com.google.common.base.Predicate#apply(java.lang.Object)
*/
public boolean apply(Diff input) {
// Note that this is not meant for many-valued references
if (input instanceof ReferenceChange
&& ((ReferenceChange)input).getReference().getName().equals(referenceName)
&& !((ReferenceChange)input).getReference().isMany()) {
final EReference reference = ((ReferenceChange)input).getReference();
final Match match = input.getMatch();
final Object leftValue;
if (match.getLeft() != null) {
leftValue = match.getLeft().eGet(reference);
} else {
leftValue = null;
}
final Object rightValue;
if (match.getRight() != null) {
rightValue = match.getRight().eGet(reference);
} else {
rightValue = null;
}
final Object originValue;
if (match.getOrigin() != null) {
originValue = match.getOrigin().eGet(reference);
} else {
originValue = null;
}
// "from" is either right or origin
boolean applies = false;
if (matchAllowingNull(originValue, fromQualifiedName)) {
// "from" is origin, "to" can be either left or right
applies = matchAllowingNull(leftValue, toQualifiedName)
|| matchAllowingNull(rightValue, toQualifiedName);
} else if (matchAllowingNull(rightValue, fromQualifiedName)) {
// "from" is right, "to" can only be left
applies = matchAllowingNull(leftValue, toQualifiedName);
}
return applies;
}
return false;
}
}
/**
* This particular predicate will be used to check that a given Diff corresponds to an AttributeChange on
* a given attribute, with known "original" and "changed" values.
*
* @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
*/
private static final class AttributeValuesMatch implements Predicate<Diff> {
/** Name of the attribute we expect to have been changed. */
private final String attributeName;
/** The expected original value of this attribute. */
private final Object fromValue;
/** The value to which this attribute is expected to have changed. */
private final Object toValue;
/**
* Instantiates this predicate given the values it is meant to match.
*
* @param attributeName
* Name of the single-valued attribute on which we expect a change.
* @param fromValue
* The original value of this attribute.
* @param toValue
* The value to which this attribute has been changed.
*/
public AttributeValuesMatch(String attributeName, Object fromValue, Object toValue) {
this.attributeName = attributeName;
this.fromValue = fromValue;
this.toValue = toValue;
}
/**
* Checks whether the two given Objects match : they are either both {@code null}, the same instance,
* or their "equals" returns {@code true}. If neither is {@code true}, we assume that these two
* Objects don't match.
* <p>
* Do note that "unset" values are in fact set to the empty String instead of {@code null}. We will
* thus consider {@code null} equal to the empty String here.
* </p>
*
* @param attributeValue
* The reference value, first of the two Objects to compare.
* @param expectedValue
* The expected value, second of the two Objects to compare.
* @return {@code true} if these two Objects are equal, {@code false} otherwise.
*/
private static boolean equalAttributeValues(Object attributeValue, Object expectedValue) {
// Using == to handle the "null" case
boolean equal = expectedValue == attributeValue || expectedValue != null
&& expectedValue.equals(attributeValue);
// Consider that null is equal to the empty string (unset attributes)
if (!equal) {
equal = "".equals(attributeValue) && expectedValue == null || "".equals(expectedValue) //$NON-NLS-1$ //$NON-NLS-2$
&& attributeValue == null;
}
return equal;
}
/**
* {@inheritDoc}
*
* @see com.google.common.base.Predicate#apply(java.lang.Object)
*/
public boolean apply(Diff input) {
// Note that this is not meant for multi-valued attributes
if (input instanceof AttributeChange
&& ((AttributeChange)input).getAttribute().getName().equals(attributeName)
&& !((AttributeChange)input).getAttribute().isMany()) {
final EAttribute attribute = ((AttributeChange)input).getAttribute();
final Match match = input.getMatch();
final Object leftValue;
if (match.getLeft() != null) {
leftValue = match.getLeft().eGet(attribute);
} else {
leftValue = attribute.getDefaultValue();
}
final Object rightValue;
if (match.getRight() != null) {
rightValue = match.getRight().eGet(attribute);
} else {
rightValue = attribute.getDefaultValue();
}
final Object originValue;
if (match.getOrigin() != null) {
originValue = match.getOrigin().eGet(attribute);
} else {
originValue = attribute.getDefaultValue();
}
final Object actualFrom;
if (fromValue == null) {
actualFrom = attribute.getDefaultValue();
} else {
actualFrom = fromValue;
}
final Object actualTo;
if (toValue == null) {
actualTo = attribute.getDefaultValue();
} else {
actualTo = toValue;
}
// "from" is either right or origin
boolean applies = false;
if (equalAttributeValues(actualFrom, originValue)) {
// "from" is origin, "to" can be either left or right
applies = equalAttributeValues(actualTo, leftValue)
|| equalAttributeValues(actualTo, rightValue);
} else if (equalAttributeValues(actualFrom, rightValue)) {
// "from" is right, "to" can only be left
applies = equalAttributeValues(actualTo, leftValue);
}
return applies;
}
return false;
}
}
/**
* Predicate for diffs taht concern a given feature.
*
* @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
*/
private static class OnFeature implements Predicate<Diff> {
/** The feature. */
private final EStructuralFeature feature;
/**
* Constructor.
*
* @param feature
* the feature
*/
public OnFeature(EStructuralFeature feature) {
this.feature = checkNotNull(feature);
}
/**
* Apply the predicate.
*
* @param input
* The diff to filter.
* @return true if and only if input concerns the given feature.
*/
public boolean apply(Diff input) {
if (input == null) {
return false;
}
boolean apply = false;
if (input instanceof ReferenceChange) {
apply = ((ReferenceChange)input).getReference() == feature;
} else if (input instanceof AttributeChange) {
apply = ((AttributeChange)input).getAttribute() == feature;
} else if (input instanceof FeatureMapChange) {
apply = ((FeatureMapChange)input).getAttribute() == feature;
}
return apply;
}
}
/**
* This will be used to filter out the list of potential candidates for conflict with a given Diff.
*
* @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
*/
private static final class ConflictCandidateFilter implements Predicate<Diff> {
/** The Diff for which we seek conflict candidates. */
private final Diff diff;
/**
* Instantiates our filtering Predicate given the reference Diff for which to seek potential
* conflicts.
*
* @param diff
* The Diff for which we seek conflict candidates, must not be null.
*/
public ConflictCandidateFilter(Diff diff) {
this.diff = checkNotNull(diff);
}
/**
* {@inheritDoc}
*
* @see com.google.common.base.Predicate#apply(java.lang.Object)
*/
public boolean apply(Diff input) {
return !(input instanceof ResourceLocationChange) && canConflictWith(input);
}
/**
* Checks if the given {@link Diff diff1} can be in conflict with the given {@link Diff diff2}.
* <p>
* Notably, we don't need to try and detect a conflict between two diffs if they're one and the same
* or if they have already been detected as a conflicting couple. Likewise, there can be no conflict
* if the two diffs originate from the same side.
* </p>
* <p>
* bug 381143 : we'll also remove any containment deletion diff on other Matches from here.
* </p>
*
* @param other
* candidate difference to consider for conflict detection.
* @return {@code true} if the two given diffs can conflict, {@code false} otherwise.
*/
private boolean canConflictWith(Diff other) {
if (diff == other || diff.getSource() == other.getSource()) {
return false;
}
final Conflict conflict = diff.getConflict();
boolean canConflict = false;
if (conflict == null || !conflict.getDifferences().contains(other)) {
if (diff.getMatch() != other.getMatch() && other instanceof ReferenceChange
&& ((ReferenceChange)other).getReference().isContainment()) {
canConflict = !isDeleteOrUnsetDiff(other);
} else {
canConflict = true;
}
}
return canConflict;
}
}
}