org.eclipse.emf.compare.conflict.DefaultConflictDetector.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.emf.compare.conflict.DefaultConflictDetector.java

Source

/*******************************************************************************
( * 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
 *     Philip Langer - bugs 446947, 479449
 *     Martin Fleck - bug 493527
 *******************************************************************************/
package org.eclipse.emf.compare.conflict;

import static com.google.common.base.Predicates.and;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.isEmpty;
import static org.eclipse.emf.compare.internal.utils.ComparisonUtil.isAddOrSetDiff;
import static org.eclipse.emf.compare.internal.utils.ComparisonUtil.isDeleteOrUnsetDiff;
import static org.eclipse.emf.compare.internal.utils.ComparisonUtil.isFeatureMapContainment;
import static org.eclipse.emf.compare.utils.EMFComparePredicates.CONTAINMENT_REFERENCE_CHANGE;
import static org.eclipse.emf.compare.utils.EMFComparePredicates.hasConflict;
import static org.eclipse.emf.compare.utils.EMFComparePredicates.ofKind;
import static org.eclipse.emf.compare.utils.EMFComparePredicates.onFeature;
import static org.eclipse.emf.compare.utils.EMFComparePredicates.possiblyConflictingWith;
import static org.eclipse.emf.compare.utils.EMFComparePredicates.valueIs;

import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

import java.util.List;

import org.apache.log4j.Logger;
import org.eclipse.emf.common.util.Monitor;
import org.eclipse.emf.compare.AttributeChange;
import org.eclipse.emf.compare.CompareFactory;
import org.eclipse.emf.compare.Comparison;
import org.eclipse.emf.compare.ComparisonCanceledException;
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.EMFCompareMessages;
import org.eclipse.emf.compare.Equivalence;
import org.eclipse.emf.compare.FeatureMapChange;
import org.eclipse.emf.compare.Match;
import org.eclipse.emf.compare.MatchResource;
import org.eclipse.emf.compare.ReferenceChange;
import org.eclipse.emf.compare.ResourceAttachmentChange;
import org.eclipse.emf.compare.internal.ThreeWayTextDiff;
import org.eclipse.emf.compare.internal.conflict.DiffTreeIterator;
import org.eclipse.emf.compare.internal.utils.ComparisonUtil;
import org.eclipse.emf.compare.utils.IEqualityHelper;
import org.eclipse.emf.compare.utils.ReferenceUtil;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.util.FeatureMap;

/**
 * The conflict detector is in charge of refining the Comparison model with all detected Conflict between its
 * differences.
 * <p>
 * This default implementation of {@link IConflictDetector} should detect most generic cases, but is not aimed
 * at detecting conflicts at "business" level. For example, adding two enum literals of the same value but
 * distinct IDs might be seen as a conflict... but that is not the "generic" case.
 * </p>
 * 
 * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
 */
public class DefaultConflictDetector implements IConflictDetector {

    /** The logger. */
    private static final Logger LOGGER = Logger.getLogger(DefaultConflictDetector.class);

    /**
     * This can be used to check whether a given conflict involves add containment reference changes.
     */
    private static final Predicate<? super Conflict> IS_REAL_CONTAINMENT_ADD_CONFLICT = new Predicate<Conflict>() {
        public boolean apply(Conflict input) {
            boolean isRealAddContainmentConflict = false;
            if (input != null && input.getKind() == ConflictKind.REAL) {
                Iterable<Diff> containmentRefs = filter(input.getDifferences(), CONTAINMENT_REFERENCE_CHANGE);
                if (!isEmpty(containmentRefs)) {
                    for (Diff diff : containmentRefs) {
                        if (diff.getKind() != DifferenceKind.ADD) {
                            return false;
                        }
                    }
                    isRealAddContainmentConflict = true;
                }
            }
            return isRealAddContainmentConflict;
        }
    };

    /**
     * {@inheritDoc}
     * 
     * @see org.eclipse.emf.compare.conflict.IConflictDetector#detect(org.eclipse.emf.compare.Comparison,
     *      org.eclipse.emf.common.util.Monitor)
     */
    public void detect(Comparison comparison, Monitor monitor) {
        long start = System.currentTimeMillis();
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("detect conflicts - START"); //$NON-NLS-1$
        }
        final List<Diff> differences = comparison.getDifferences();
        final int diffCount = differences.size();

        for (int i = 0; i < diffCount; i++) {
            if (i % 100 == 0) {
                monitor.subTask(EMFCompareMessages.getString("DefaultConflictDetector.monitor.detect", //$NON-NLS-1$
                        Integer.valueOf(i + 1), Integer.valueOf(diffCount)));
            }
            if (monitor.isCanceled()) {
                throw new ComparisonCanceledException();
            }
            final Diff diff = differences.get(i);

            checkConflict(comparison, diff, Iterables.filter(differences, possiblyConflictingWith(diff)));
        }

        handlePseudoUnderRealAdd(comparison);
        if (LOGGER.isInfoEnabled()) {
            LOGGER.info(String.format("detect conflicts - END - Took %d ms", //$NON-NLS-1$
                    Long.valueOf(System.currentTimeMillis() - start)));
        }
    }

    /**
     * If a real add conflict contains pseudo conflicts, these pseudo conflicts must be changed to real
     * conflicts.
     * 
     * @param comparison
     *            The originating comparison of those diffs.
     */
    private void handlePseudoUnderRealAdd(Comparison comparison) {
        for (Conflict realContainmentAdd : filter(comparison.getConflicts(), IS_REAL_CONTAINMENT_ADD_CONFLICT)) {
            changeKindOfPseudoConflictsUnder(realContainmentAdd);
        }
    }

    /**
     * Change all pseudo conflicts under the given real conflict to real conflicts.
     * 
     * @param conflict
     *            the given conflict.
     */
    private void changeKindOfPseudoConflictsUnder(Conflict conflict) {
        for (Diff diff : conflict.getDifferences()) {
            final Match realConflictMatch = diff.getMatch();
            for (Match subMatch : realConflictMatch.getSubmatches()) {
                for (Diff conflictDiffUnder : filter(subMatch.getDifferences(), hasConflict(ConflictKind.PSEUDO))) {
                    Conflict conflictUnder = conflictDiffUnder.getConflict();
                    conflictUnder.setKind(ConflictKind.REAL);
                    changeKindOfPseudoConflictsUnder(conflictUnder);
                }
            }
        }
    }

    /**
     * This will be called once for each difference in the comparison model.
     * 
     * @param comparison
     *            The originating comparison of those diffs.
     * @param diff
     *            Diff for which we are to try and determine conflicts.
     * @param candidates
     *            An iterable over the Diffs that possible candidates for conflicts.
     */
    protected void checkConflict(Comparison comparison, Diff diff, Iterable<Diff> candidates) {
        // DELETE diffs can conflict with every other if on containment references, only with MOVE or other
        // DELETE otherwise.
        // ADD diffs can only conflict with "DELETE" or "ADD" ones ... Most will be detected on the DELETE.
        // However, ADD diffs on containment reference can conflict with other ADDs on the same match.
        //
        // CHANGE diffs can only conflict with other CHANGE or DELETE ... here again detected on the DELETE
        // MOVE diffs can conflict with DELETE ones, detected on the delete, or with other MOVE diffs.
        if (diff instanceof ReferenceChange && ((ReferenceChange) diff).getReference().isContainment()) {
            checkContainmentConflict(comparison, (ReferenceChange) diff,
                    Iterables.filter(candidates, ReferenceChange.class));
        } else if (diff instanceof ResourceAttachmentChange) {
            // These will be handled about the same way as containment deletions,
            // Though they can also conflict with themselves
            checkResourceAttachmentConflict(comparison, (ResourceAttachmentChange) diff, candidates);
        } else if (isFeatureMapContainment(diff)) {
            checkContainmentFeatureMapConflict(comparison, (FeatureMapChange) diff,
                    Iterables.filter(candidates, FeatureMapChange.class));
        } else {
            switch (diff.getKind()) {
            case DELETE:
                checkFeatureDeleteConflict(comparison, diff, candidates);
                break;
            case CHANGE:
                checkFeatureChangeConflict(comparison, diff, candidates);
                break;
            case MOVE:
                checkFeatureMoveConflict(comparison, diff, candidates);
                break;
            case ADD:
                checkFeatureAddConflict(comparison, diff, candidates);
                break;
            default:
                break;
            }
        }
    }

    /**
     * This will be called once for each ReferenceChange on containment references in the comparison model.
     * 
     * @param comparison
     *            The originating comparison of those diffs.
     * @param diff
     *            The reference change for which we are to try and determine conflicts.
     * @param candidates
     *            An iterable over the ReferenceChanges that are possible candidates for conflicts.
     */
    protected void checkContainmentConflict(Comparison comparison, ReferenceChange diff,
            Iterable<ReferenceChange> candidates) {
        for (ReferenceChange candidate : candidates) {
            if (isMatchingValues(comparison, diff, candidate)) {
                checkContainmentConflict(comparison, diff, candidate);
            } else if (isConflictingAdditionToSingleValuedReference(diff, candidate)) {
                if (comparison.getEqualityHelper().matchingValues(candidate.getValue(), diff.getValue())) {
                    conflictOn(comparison, diff, candidate, ConflictKind.PSEUDO);
                } else {
                    conflictOn(comparison, diff, candidate, ConflictKind.REAL);
                }
            }
        }

        // [381143] Every Diff "under" a containment deletion conflicts with it.
        if (diff.getKind() == DifferenceKind.DELETE) {
            final DiffTreeIterator diffIterator = new DiffTreeIterator(comparison.getMatch(diff.getValue()));
            diffIterator.setFilter(possiblyConflictingWith(diff));
            diffIterator.setPruningFilter(isContainmentDelete());

            while (diffIterator.hasNext()) {
                Diff extendedCandidate = diffIterator.next();
                if (isDeleteOrUnsetDiff(extendedCandidate)) {
                    conflictOn(comparison, diff, extendedCandidate, ConflictKind.PSEUDO);
                } else {
                    conflictOn(comparison, diff, extendedCandidate, ConflictKind.REAL);
                }
            }
        }
    }

    /**
     * Specifies whether the given differences <code>diff</code> and <code>candidate</code> are conflicting
     * additions to single-valued references.
     * <p>
     * They are, if the changed reference is single-valued and both are additions to the same object.
     * </p>
     * 
     * @param diff
     *            The reference diff to check.
     * @param candidate
     *            The candidate diff to check.
     * @return <code>true</code> if they are conflicting additions to single-valued references,
     *         <code>false</code> otherwise.
     */
    private boolean isConflictingAdditionToSingleValuedReference(ReferenceChange diff, ReferenceChange candidate) {
        return (diff.getReference() == candidate.getReference() && !diff.getReference().isMany())
                && (isAddOrSetDiff(diff) && isAddOrSetDiff(candidate)) && diff.getMatch() == candidate.getMatch();
    }

    /**
     * Specifies whether the values of the given differences <code>diff</code> and <code>candidate</code>
     * match.
     * 
     * @param comparison
     *            The originating comparison of those diffs.
     * @param diff
     *            The one difference.
     * @param candidate
     *            The other difference.
     * @return <code>true</code> if the values of the differences match, <code>false</code> otherwise.
     */
    private boolean isMatchingValues(Comparison comparison, ReferenceChange diff, ReferenceChange candidate) {
        final Match valueMatch = comparison.getMatch(diff.getValue());
        final EObject candidateValue = candidate.getValue();
        return valueMatch.getLeft() == candidateValue || valueMatch.getRight() == candidateValue
                || valueMatch.getOrigin() == candidateValue;
    }

    /**
     * This predicate will be <code>true</code> for any Match which represents a containment deletion.
     * 
     * @return A Predicate that will be met by containment deletions.
     */
    private Predicate<? super Match> isContainmentDelete() {
        return new Predicate<Match>() {
            public boolean apply(Match input) {
                return input.getOrigin() != null && (input.getLeft() == null || input.getRight() == null);
            }
        };
    }

    /**
     * For each couple of diffs on the same value in which one is a containment reference change, we will call
     * this in order to check for possible conflicts.
     * <p>
     * Once here, we know that {@code diff} is a containment reference change, and we known that {@code diff}
     * and {@code candidate} are both pointing to the same value. {@code candidate} can be a containment
     * reference change, but that is not a given.
     * </p>
     * 
     * @param comparison
     *            The originating comparison of those diffs.
     * @param diff
     *            Containment reference changes for which we need to check possible conflicts.
     * @param candidate
     *            A reference change that point to the same value as {@code diff}.
     */
    protected void checkContainmentConflict(Comparison comparison, ReferenceChange diff,
            ReferenceChange candidate) {
        final boolean candidateIsDelete = isDeleteOrUnsetDiff(candidate);
        if (candidate.getReference().isContainment()) {
            // The same value has been changed on both sides in containment references
            // This is a conflict, but is it a pseudo-conflict?
            ConflictKind kind = ConflictKind.REAL;
            final boolean diffIsDelete = isDeleteOrUnsetDiff(diff);
            if (diffIsDelete && candidateIsDelete) {
                kind = ConflictKind.PSEUDO;
            } else if (diff.getMatch() == candidate.getMatch() && diff.getReference() == candidate.getReference()) {
                // Same value added in the same container/reference couple
                if (!diffIsDelete && !candidateIsDelete && matchingIndices(comparison, diff.getMatch(),
                        diff.getReference(), diff.getValue(), candidate.getValue())) {
                    kind = ConflictKind.PSEUDO;
                }
            }
            conflictOn(comparison, diff, candidate, kind);
        } else if (diff.getKind() == DifferenceKind.DELETE) {
            /*
             * We removed an element from its containment difference, but it has been used in some way on the
             * other side.
             */
            if (candidateIsDelete) {
                // No conflict here
            } else {
                // Be it added, moved or changed, this is a REAL conflict
                conflictOn(comparison, diff, candidate, ConflictKind.REAL);
            }
        }
    }

    /**
     * This will be called once for each FeatureMapChange on containment values in the comparison model.
     * 
     * @param comparison
     *            The originating comparison of those diffs.
     * @param diff
     *            The feature map change for which we are to try and determine conflicts.
     * @param candidates
     *            An iterable over the FeatureMapChanges that are possible candidates for conflicts.
     * @since 3.2
     */
    protected void checkContainmentFeatureMapConflict(Comparison comparison, FeatureMapChange diff,
            Iterable<FeatureMapChange> candidates) {
        final FeatureMap.Entry entry = (FeatureMap.Entry) diff.getValue();
        final Object value = entry.getValue();
        final Match valueMatch;
        if (value instanceof EObject) {
            valueMatch = comparison.getMatch((EObject) value);
        } else {
            valueMatch = diff.getMatch();
        }

        for (FeatureMapChange candidate : candidates) {
            FeatureMap.Entry candidateEntry = (FeatureMap.Entry) candidate.getValue();
            Object candidateValue = candidateEntry.getValue();
            if (valueMatch.getLeft() == candidateValue || valueMatch.getRight() == candidateValue
                    || valueMatch.getOrigin() == candidateValue) {
                checkContainmentFeatureMapConflict(comparison, diff, candidate);
            }
        }

        // [381143] Every Diff "under" a containment deletion conflicts with it.
        if (diff.getKind() == DifferenceKind.DELETE) {
            final DiffTreeIterator diffIterator = new DiffTreeIterator(valueMatch);
            diffIterator.setFilter(possiblyConflictingWith(diff));
            diffIterator.setPruningFilter(isContainmentDelete());

            while (diffIterator.hasNext()) {
                Diff extendedCandidate = diffIterator.next();
                if (isDeleteOrUnsetDiff(extendedCandidate)) {
                    conflictOn(comparison, diff, extendedCandidate, ConflictKind.PSEUDO);
                } else {
                    conflictOn(comparison, diff, extendedCandidate, ConflictKind.REAL);
                }
            }
        }
    }

    /**
     * For each couple of diffs on the same value in which one is a containment feature map change, we will
     * call this in order to check for possible conflicts.
     * <p>
     * Once here, we know that {@code diff} is a containment feature map change, and we known that
     * {@code diff} and {@code candidate} are both pointing to the same value. {@code candidate} can be a
     * containment feature map change, but that is not a given.
     * </p>
     * 
     * @param comparison
     *            The originating comparison of those diffs.
     * @param diff
     *            Containment feature map changes for which we need to check possible conflicts.
     * @param candidate
     *            A feature map change that point to the same value as {@code diff}.
     * @since 3.2
     */
    protected void checkContainmentFeatureMapConflict(Comparison comparison, FeatureMapChange diff,
            FeatureMapChange candidate) {
        final boolean candidateIsDelete = isDeleteOrUnsetDiff(candidate);
        if (isFeatureMapContainment(candidate)) {
            // The same value has been changed on both sides in containment references
            // This is a conflict, but is it a pseudo-conflict?
            ConflictKind kind = ConflictKind.REAL;
            final boolean diffIsDelete = isDeleteOrUnsetDiff(diff);
            if (diffIsDelete && candidateIsDelete) {
                kind = ConflictKind.PSEUDO;
            } else if (diff.getMatch() == candidate.getMatch() && diff.getAttribute() == candidate.getAttribute()) {
                // Same value added in the same container/reference couple with the same key
                if (!diffIsDelete && !candidateIsDelete && matchingIndices(comparison, diff.getMatch(),
                        diff.getAttribute(), diff.getValue(), candidate.getValue())
                        && haveSameKey(diff, candidate)) {
                    kind = ConflictKind.PSEUDO;
                }
            }
            conflictOn(comparison, diff, candidate, kind);
        } else if (diff.getKind() == DifferenceKind.DELETE) {
            /*
             * We removed an element from its containment difference, but it has been used in some way on the
             * other side.
             */
            if (candidateIsDelete) {
                // No conflict here
            } else {
                // Be it added, moved or changed, this is a REAL conflict
                conflictOn(comparison, diff, candidate, ConflictKind.REAL);
            }
        }
    }

    /**
     * Check if both feature map changes (hosting FeatureMap entries) have entries with same key.
     * 
     * @param left
     *            the left candidate.
     * @param right
     *            the right candiadte.
     * @return true if both feature map changes have entries with same key, false otherwise.
     */
    private boolean haveSameKey(FeatureMapChange left, FeatureMapChange right) {
        FeatureMap.Entry leftEntry = (FeatureMap.Entry) left.getValue();
        FeatureMap.Entry rightEntry = (FeatureMap.Entry) right.getValue();
        return leftEntry.getEStructuralFeature().equals(rightEntry.getEStructuralFeature());
    }

    /**
     * This will be called from {@link #checkConflict(Comparison, Diff, Iterable)} in order to detect
     * conflicts on a Diff that is of type "CHANGE".
     * <p>
     * Those can only conflict with other CHANGE Diffs on the same reference.
     * </p>
     * 
     * @param comparison
     *            The originating comparison of those diffs.
     * @param diff
     *            The diff which we are to check for conflicts.
     * @param candidates
     *            The list of candidates for a conflict. This list only contains Diff from the side opposite
     *            to {@code diff}.
     */
    protected void checkFeatureChangeConflict(Comparison comparison, Diff diff, Iterable<Diff> candidates) {
        final Object changedValue;
        final EStructuralFeature feature;
        if (diff instanceof ReferenceChange) {
            changedValue = ((ReferenceChange) diff).getValue();
            feature = ((ReferenceChange) diff).getReference();
        } else if (diff instanceof AttributeChange) {
            changedValue = ((AttributeChange) diff).getValue();
            feature = ((AttributeChange) diff).getAttribute();
        } else if (diff instanceof FeatureMapChange) {
            changedValue = ((FeatureMap.Entry) ((FeatureMapChange) diff).getValue()).getValue();
            feature = ((FeatureMapChange) diff).getAttribute();
        } else {
            return;
        }

        final Iterable<Diff> refinedCandidates = Iterables.filter(candidates, new Predicate<Diff>() {
            public boolean apply(Diff input) {
                boolean apply = false;
                if (input != null && input.getKind() == DifferenceKind.CHANGE) {
                    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;
            }
        });

        final IEqualityHelper equalityHelper = comparison.getEqualityHelper();

        for (Diff candidate : refinedCandidates) {
            final Object candidateValue;
            if (candidate instanceof ReferenceChange) {
                candidateValue = ((ReferenceChange) candidate).getValue();
            } else if (candidate instanceof AttributeChange) {
                candidateValue = ((AttributeChange) candidate).getValue();
            } else if (candidate instanceof FeatureMapChange) {
                candidateValue = ((FeatureMap.Entry) ((FeatureMapChange) candidate).getValue()).getValue();
            } else {
                candidateValue = null;
            }

            if (diff.getMatch() == candidate.getMatch()) {
                if (equalityHelper.matchingValues(changedValue, candidateValue)) {
                    // Same value added on both side in the same container
                    conflictOn(comparison, diff, candidate, ConflictKind.PSEUDO);
                } else if (!isFeatureMapChangeOrMergeableStringAttributeChange(diff, candidate)) {
                    conflictOn(comparison, diff, candidate, ConflictKind.REAL);
                }
            }
        }
    }

    /**
     * Specifies whether the given {@code diff1} and {@code diff2} are either {@link FeatureMapChange feature
     * map changes} or mergeable {@link AttributeChange attribute changes} of String attributes.
     * 
     * @param diff1
     *            One of the diffs to check.
     * @param diff2
     *            The other diff to check.
     * @return <code>true</code> if it is a {@link FeatureMapChange} or a mergeable {@link AttributeChange},
     *         <code>false</code> otherwise.
     */
    private boolean isFeatureMapChangeOrMergeableStringAttributeChange(Diff diff1, Diff diff2) {
        return isFeatureMapChange(diff1) || areMergeableStringAttributeChanges(diff1, diff2);
    }

    /**
     * Specifies whether the given {@code diff} is a {@link FeatureMapChange}.
     * 
     * @param diff
     *            The diff to check.
     * @return <code>true</code> if it is a {@link FeatureMapChange}, <code>false</code> otherwise.
     */
    private boolean isFeatureMapChange(Diff diff) {
        return diff instanceof FeatureMapChange;
    }

    /**
     * Specifies whether the two given diffs, {@code diff1} and {@code diff2}, are both
     * {@link AttributeChange attribute changes} of String attributes and can be merged with a line-based
     * three-way merge.
     * 
     * @see org.eclipse.emf.compare.internal.ThreeWayTextDiff
     * @param diff1
     *            One of the diffs to check.
     * @param diff2
     *            The other diff to check.
     * @return <code>true</code> if the diffs are mergeable changes of a string attribute, <code>false</code>
     *         otherwise.
     */
    private boolean areMergeableStringAttributeChanges(Diff diff1, Diff diff2) {
        final boolean mergeableStringAttributeChange;
        if (isStringAttributeChange(diff1)) {
            final AttributeChange attributeChange1 = (AttributeChange) diff1;
            final AttributeChange attributeChange2 = (AttributeChange) diff2;
            mergeableStringAttributeChange = isMergeable(attributeChange1, attributeChange2);
        } else {
            mergeableStringAttributeChange = false;
        }
        return mergeableStringAttributeChange;
    }

    /**
     * Specifies whether the given {@code diff} is a {@link AttributeChange} of a String attribute.
     * 
     * @param diff
     *            The diff to check.
     * @return <code>true</code> if it is a {@link AttributeChange} of a String attribute, <code>false</code>
     *         otherwise.
     */
    private boolean isStringAttributeChange(Diff diff) {
        return diff instanceof AttributeChange
                && ((AttributeChange) diff).getAttribute().getEAttributeType().getInstanceClass() == String.class;
    }

    /**
     * Specifies whether the two given attribute changes, {@code diff1} and {@code diff2}, can be merged with
     * a line-based three-way merge.
     * 
     * @see org.eclipse.emf.compare.internal.ThreeWayTextDiff
     * @param diff1
     *            One of the attribute changes to check.
     * @param diff2
     *            The other attribute change to check.
     * @return <code>true</code> if the attribute changes are mergeable, <code>false</code> otherwise.
     */
    private boolean isMergeable(final AttributeChange diff1, final AttributeChange diff2) {
        final String changedValue1 = getChangedValue(diff1);
        final String changedValue2 = getChangedValue(diff2);
        final EObject originalContainer = diff1.getMatch().getOrigin();
        final EAttribute changedAttribute = diff1.getAttribute();
        final String originalValue = (String) ReferenceUtil.safeEGet(originalContainer, changedAttribute);
        return isMergeableText(changedValue1, changedValue2, originalValue);
    }

    /**
     * Specifies whether the given three versions of a text {@code left}, {@code right}, and {@code origin}
     * are mergeable with a line-based three-way merge.
     * 
     * @param left
     *            The left version.
     * @param right
     *            The right version.
     * @param origin
     *            The original version.
     * @return <code>true</code> if they are mergeable, false otherwise.
     * @since 3.2
     */
    protected boolean isMergeableText(final String left, final String right, final String origin) {
        ThreeWayTextDiff textDiff = new ThreeWayTextDiff(origin, left, right);
        return !textDiff.isConflicting();
    }

    /**
     * Returns the changed attribute value denoted by the given {@code diff}.
     * 
     * @param diff
     *            The attribute change for which the changed value is requested.
     * @return The changed attribute value.
     */
    private String getChangedValue(final AttributeChange diff) {
        final String changedValue;
        Match match = diff.getMatch();
        if (DifferenceSource.LEFT.equals(diff.getSource())) {
            changedValue = (String) ReferenceUtil.safeEGet(match.getLeft(), diff.getAttribute());
        } else if (DifferenceSource.RIGHT.equals(diff.getSource())) {
            changedValue = (String) ReferenceUtil.safeEGet(match.getRight(), diff.getAttribute());
        } else {
            changedValue = (String) diff.getValue();
        }
        return changedValue;
    }

    /**
     * This will be called from {@link #checkConflict(Comparison, Diff, Iterable)} in order to detect
     * conflicts on a Diff that is of type "CHANGE" or "MOVE".
     * <p>
     * Those can only conflict with other Diffs of the same type on the same reference.
     * </p>
     * 
     * @param comparison
     *            The originating comparison of those diffs.
     * @param diff
     *            The diff which we are to check for conflicts.
     * @param candidates
     *            The list of candidates for a conflict. This list only contains Diff from the side opposite
     *            to {@code diff}.
     */
    protected void checkFeatureMoveConflict(Comparison comparison, Diff diff, Iterable<Diff> candidates) {
        final Object changedValue;
        final EStructuralFeature feature;
        if (diff instanceof ReferenceChange) {
            changedValue = ((ReferenceChange) diff).getValue();
            feature = ((ReferenceChange) diff).getReference();
        } else if (diff instanceof AttributeChange) {
            changedValue = ((AttributeChange) diff).getValue();
            feature = ((AttributeChange) diff).getAttribute();
        } else if (diff instanceof FeatureMapChange) {
            changedValue = ((FeatureMap.Entry) ((FeatureMapChange) diff).getValue()).getValue();
            feature = ((FeatureMapChange) diff).getAttribute();
        } else {
            return;
        }

        final Iterable<Diff> refinedCandidates = Iterables.filter(candidates, new Predicate<Diff>() {
            public boolean apply(Diff input) {
                boolean apply = false;
                if (input != null && input.getKind() == DifferenceKind.MOVE) {
                    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;
            }
        });

        for (Diff candidate : refinedCandidates) {
            final Object candidateValue;
            if (candidate instanceof ReferenceChange) {
                candidateValue = ((ReferenceChange) candidate).getValue();
            } else if (candidate instanceof AttributeChange) {
                candidateValue = ((AttributeChange) candidate).getValue();
            } else if (candidate instanceof FeatureMapChange) {
                candidateValue = ((FeatureMap.Entry) ((FeatureMapChange) candidate).getValue()).getValue();
            } else {
                candidateValue = null;
            }

            if (diff.getMatch() == candidate.getMatch()
                    && comparison.getEqualityHelper().matchingValues(changedValue, candidateValue)) {
                // Same value moved in both side of the same container
                if (matchingIndices(comparison, diff.getMatch(), feature, changedValue, candidateValue)) {
                    conflictOn(comparison, diff, candidate, ConflictKind.PSEUDO);
                } else {
                    conflictOn(comparison, diff, candidate, ConflictKind.REAL);
                }
            }
        }
    }

    /**
     * This will be called from {@link #checkConflict(Comparison, Diff, Iterable)} in order to detect
     * conflicts on a Diff that is of type "DELETE" and which is <b>not</b> a containment reference change.
     * <p>
     * The only potential conflict for such a diff is a "MOVE" of that same value on the opposite side.
     * </p>
     * 
     * @param comparison
     *            The originating comparison of those diffs.
     * @param diff
     *            The diff which we are to check for conflicts.
     * @param candidates
     *            The list of candidates for a conflict. This list only contains Diff from the side opposite
     *            to {@code diff}.
     */
    protected void checkFeatureDeleteConflict(Comparison comparison, Diff diff, Iterable<Diff> candidates) {
        final Object deletedValue;
        final EStructuralFeature feature;
        if (diff instanceof ReferenceChange) {
            deletedValue = ((ReferenceChange) diff).getValue();
            feature = ((ReferenceChange) diff).getReference();
        } else if (diff instanceof AttributeChange) {
            deletedValue = ((AttributeChange) diff).getValue();
            feature = ((AttributeChange) diff).getAttribute();
        } else if (diff instanceof FeatureMapChange) {
            deletedValue = ((FeatureMap.Entry) ((FeatureMapChange) diff).getValue()).getValue();
            feature = ((FeatureMapChange) diff).getAttribute();
        } else {
            return;
        }

        /*
         * The only potential conflict with the deletion of a feature value is a move or delete concerning
         * that value on the opposite side (the "feature" cannot be a containment reference, those are handled
         * through #checkContainmentDeleteConflict).
         */
        final Iterable<Diff> refinedCandidates = Iterables.filter(candidates, new Predicate<Diff>() {
            public boolean apply(Diff input) {
                boolean apply = false;
                if (input != null
                        && (input.getKind() == DifferenceKind.MOVE || input.getKind() == DifferenceKind.DELETE)) {
                    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;
            }
        });

        for (Diff candidate : refinedCandidates) {
            final Object movedValue;
            if (candidate instanceof ReferenceChange) {
                movedValue = ((ReferenceChange) candidate).getValue();
            } else if (candidate instanceof AttributeChange) {
                movedValue = ((AttributeChange) candidate).getValue();
            } else if (candidate instanceof FeatureMapChange) {
                movedValue = ((FeatureMap.Entry) ((FeatureMapChange) candidate).getValue()).getValue();
            } else {
                movedValue = null;
            }

            if (comparison.getEqualityHelper().matchingValues(deletedValue, movedValue)) {
                if (candidate.getKind() == DifferenceKind.MOVE) {
                    conflictOn(comparison, diff, candidate, ConflictKind.REAL);
                } else if (diff.getMatch() == candidate.getMatch()) {
                    conflictOn(comparison, diff, candidate, ConflictKind.PSEUDO);
                }
            }
        }
    }

    /**
     * This will be called from {@link #checkConflict(Comparison, Diff, Iterable)} in order to detect
     * conflicts on a Diff that is of type "ADD" and which is <b>not</b> a containment reference change.
     * <p>
     * These will conflict with Diffs on the other side on the same reference in the same container, of type
     * ADD an on the same value.
     * </p>
     * 
     * @param comparison
     *            The originating comparison of those diffs.
     * @param diff
     *            The diff which we are to check for conflicts.
     * @param candidates
     *            The list of candidates for a conflict. This list only contains Diff from the side opposite
     *            to {@code diff}.
     */
    protected void checkFeatureAddConflict(final Comparison comparison, final Diff diff,
            Iterable<Diff> candidates) {
        final Object addedValue;
        final EStructuralFeature feature;
        if (diff instanceof ReferenceChange) {
            addedValue = ((ReferenceChange) diff).getValue();
            feature = ((ReferenceChange) diff).getReference();
        } else if (diff instanceof AttributeChange) {
            addedValue = ((AttributeChange) diff).getValue();
            feature = ((AttributeChange) diff).getAttribute();
        } else if (diff instanceof FeatureMapChange) {
            addedValue = ((FeatureMap.Entry) ((FeatureMapChange) diff).getValue()).getValue();
            feature = ((FeatureMapChange) diff).getAttribute();
        } else {
            return;
        }

        /*
         * Can only conflict on Diffs : of type ADD, on the opposite side, in the same container and the same
         * reference, with the same added value.
         */
        final Iterable<Diff> refinedCandidates = Iterables.filter(candidates, new Predicate<Diff>() {
            public boolean apply(Diff input) {
                boolean apply = false;
                if (input != null
                        && (input.getKind() == DifferenceKind.ADD && diff.getMatch() == input.getMatch())) {
                    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;
            }
        });

        for (Diff candidate : refinedCandidates) {
            final Object candidateValue;
            if (candidate instanceof ReferenceChange) {
                candidateValue = ((ReferenceChange) candidate).getValue();
            } else if (candidate instanceof AttributeChange) {
                candidateValue = ((AttributeChange) candidate).getValue();
            } else if (candidate instanceof FeatureMapChange) {
                candidateValue = ((FeatureMap.Entry) ((FeatureMapChange) candidate).getValue()).getValue();
            } else {
                candidateValue = null;
            }
            // No diff on non unique features : multiple same values can coexist
            if (feature.isUnique() && comparison.getEqualityHelper().matchingValues(addedValue, candidateValue)) {
                // This is a conflict. Is it real?
                if (diff instanceof FeatureMapChange) {

                    // If the key changed, this is a real conflict
                    EStructuralFeature key1 = ((FeatureMap.Entry) ((FeatureMapChange) diff).getValue())
                            .getEStructuralFeature();
                    EStructuralFeature key2 = ((FeatureMap.Entry) ((FeatureMapChange) candidate).getValue())
                            .getEStructuralFeature();
                    if (key1.equals(key2)) {
                        conflictOn(comparison, diff, candidate, ConflictKind.PSEUDO);
                    } else if (isFeatureMapContainment(diff)) { // If the feature map is non-containment, the
                        // same value can appear twice.
                        conflictOn(comparison, diff, candidate, ConflictKind.REAL);
                    }
                } else if (matchingIndices(comparison, diff.getMatch(), feature, addedValue, candidateValue)) {
                    conflictOn(comparison, diff, candidate, ConflictKind.PSEUDO);
                } else {
                    conflictOn(comparison, diff, candidate, ConflictKind.REAL);
                }
            }
        }
    }

    /**
     * This will be called once for each ResourceAttachmentChange in the comparison model.
     * 
     * @param comparison
     *            The originating comparison of those diffs.
     * @param diff
     *            The "root" difference for which we are to try and determine conflicts.
     * @param candidates
     *            An iterable over the Diffs that are possible candidates for conflicts.
     */
    protected void checkResourceAttachmentConflict(Comparison comparison, ResourceAttachmentChange diff,
            Iterable<Diff> candidates) {
        final Match match = diff.getMatch();
        final EObject leftVal = match.getLeft();
        final EObject rightVal = match.getRight();
        final EObject originVal = match.getOrigin();
        for (Diff candidate : candidates) {
            if (candidate instanceof ReferenceChange) {
                if (diff.getKind() == DifferenceKind.DELETE && match == candidate.getMatch()
                        && getRelatedModelElement(diff) == null) {
                    if (candidate.getKind() != DifferenceKind.DELETE) {
                        if (!ComparisonUtil.isDeleteOrUnsetDiff(candidate)) {
                            // The EObject that owns the changed EReference has been deleted on the other side
                            // [493527] deleted or unset references do not conflict with deleted element
                            conflictOn(comparison, diff, candidate, ConflictKind.REAL);
                        }
                    }
                } else {
                    // Any ReferenceChange that references the affected root is a possible conflict
                    final EObject candidateValue = ((ReferenceChange) candidate).getValue();
                    if (candidateValue == leftVal || candidateValue == rightVal || candidateValue == originVal) {
                        checkResourceAttachmentConflict(comparison, diff, (ReferenceChange) candidate);
                    }
                }
            } else if (candidate instanceof AttributeChange) {
                // The change of an attribute on an EObject that has been removed from a root on the other
                // side is a conflict
                if (diff.getKind() == DifferenceKind.DELETE && match == candidate.getMatch()
                        && getRelatedModelElement(diff) == null) {
                    if (ComparisonUtil.isDeleteOrUnsetDiff(candidate)) {
                        conflictOn(comparison, diff, candidate, ConflictKind.PSEUDO);
                    } else {
                        conflictOn(comparison, diff, candidate, ConflictKind.REAL);
                    }
                }
            } else if (candidate instanceof FeatureMapChange) {
                // The change of a FM on an EObject that has been removed from a root on the other side
                // is a conflict
                if (diff.getKind() == DifferenceKind.DELETE && match == candidate.getMatch()
                        && getRelatedModelElement(diff) == null) {
                    if (ComparisonUtil.isDeleteOrUnsetDiff(candidate)) {
                        conflictOn(comparison, diff, candidate, ConflictKind.PSEUDO);
                    } else {
                        conflictOn(comparison, diff, candidate, ConflictKind.REAL);
                    }
                }
            } else if (candidate instanceof ResourceAttachmentChange && match == candidate.getMatch()) {
                // This can only be a conflict. All we need to know is its kind.
                ConflictKind kind = ConflictKind.REAL;
                if (candidate.getKind() == DifferenceKind.DELETE && diff.getKind() == DifferenceKind.DELETE) {
                    kind = ConflictKind.PSEUDO;
                } else if (candidate.getKind() == DifferenceKind.ADD && diff.getKind() == DifferenceKind.ADD) {
                    final Resource diffRes;
                    final Resource candidateRes;
                    if (diff.getSource() == DifferenceSource.LEFT) {
                        diffRes = match.getLeft().eResource();
                        candidateRes = match.getRight().eResource();
                    } else {
                        diffRes = match.getRight().eResource();
                        candidateRes = match.getLeft().eResource();
                    }
                    if (getMatchResource(comparison, diffRes) == getMatchResource(comparison, candidateRes)) {
                        kind = ConflictKind.PSEUDO;
                    }
                } else if (candidate.getKind() == DifferenceKind.MOVE && diff.getKind() == DifferenceKind.MOVE) {
                    String lhsURI = diff.getResourceURI();
                    String rhsURI = ((ResourceAttachmentChange) candidate).getResourceURI();
                    if (lhsURI.equals(rhsURI)) {
                        kind = ConflictKind.PSEUDO;
                    }
                }
                conflictOn(comparison, diff, candidate, kind);
            }
        }

        // [381143] Every Diff "under" a root deletion conflicts with it.
        if (diff.getKind() == DifferenceKind.DELETE) {
            // [477607] DELETE does not necessarily mean that the element is removed from the model
            EObject o = getRelatedModelElement(diff);
            if (o != null && o.eContainer() == null) {
                for (Diff extendedCandidate : Iterables.filter(match.getAllDifferences(),
                        possiblyConflictingWith(diff))) {
                    if (isDeleteOrUnsetDiff(extendedCandidate)) {
                        conflictOn(comparison, diff, extendedCandidate, ConflictKind.PSEUDO);
                    } else {
                        conflictOn(comparison, diff, extendedCandidate, ConflictKind.REAL);
                    }
                }
            }
        }
    }

    /**
     * Provide the model element the given diff applies to.
     * 
     * @param diff
     *            The change
     * @return The model element of the given diff, or null if it cannot be found.
     */
    private EObject getRelatedModelElement(ResourceAttachmentChange diff) {
        Match m = diff.getMatch();
        EObject o;
        switch (diff.getSource()) {
        case LEFT:
            o = m.getLeft();
            break;
        case RIGHT:
            o = m.getRight();
            break;
        default:
            o = null;
        }
        return o;
    }

    /**
     * Returns the MatchResource corresponding to the given <code>resource</code>.
     * 
     * @param comparison
     *            the comparison to search for a MatchResource.
     * @param resource
     *            Resource for which we need a MatchResource.
     * @return The MatchResource corresponding to the given <code>resource</code>.
     */
    protected MatchResource getMatchResource(Comparison comparison, Resource resource) {
        final List<MatchResource> matchedResources = comparison.getMatchedResources();
        final int size = matchedResources.size();
        MatchResource soughtMatch = null;
        for (int i = 0; i < size && soughtMatch == null; i++) {
            final MatchResource matchRes = matchedResources.get(i);
            if (matchRes.getRight() == resource || matchRes.getLeft() == resource
                    || matchRes.getOrigin() == resource) {
                soughtMatch = matchRes;
            }
        }

        if (soughtMatch == null) {
            // This should never happen
            throw new RuntimeException(EMFCompareMessages.getString("ResourceAttachmentChangeSpec.MissingMatch", //$NON-NLS-1$
                    resource.getURI().lastSegment()));
        }

        return soughtMatch;
    }

    /**
     * This will be called from
     * {@link #checkResourceAttachmentConflict(Comparison, ResourceAttachmentChange, Iterable)} for each
     * ReferenceChange in the comparison model that is on the other side and that impacts the changed root.
     * 
     * @param comparison
     *            The originating comparison of those diffs.
     * @param diff
     *            Resource attachment change for which we need to check possible conflicts.
     * @param candidate
     *            A reference change that point to the same value as {@code diff}.
     */
    protected void checkResourceAttachmentConflict(Comparison comparison, ResourceAttachmentChange diff,
            ReferenceChange candidate) {
        if (candidate.getReference().isContainment()) {
            // The element is a new root on one side, but it has been moved to an EObject container on the
            // other
            conflictOn(comparison, diff, candidate, ConflictKind.REAL);
        } else if (diff.getKind() == DifferenceKind.DELETE) {
            // [477607] DELETE does not necessarily mean that the element is removed from the model
            EObject o = getRelatedModelElement(diff);
            if (o != null && o.eContainer() == null) {
                // The root has been deleted.
                // Anything other than a delete of this value in a reference is a conflict.
                if (candidate.getKind() == DifferenceKind.DELETE) {
                    // No conflict here
                } else {
                    conflictOn(comparison, diff, candidate, ConflictKind.REAL);
                }
            }
        }
    }

    /**
     * This will be used whenever we check for conflictual MOVEs in order to determine whether we have a
     * pseudo conflict or a real conflict.
     * <p>
     * Namely, this will retrieve the value of the given {@code feature} on the right and left sides of the
     * given {@code match}, then check whether the two given values are on the same index.
     * </p>
     * <p>
     * Note that no sanity checks will be made on either the match's sides or the feature.
     * </p>
     * 
     * @param comparison
     *            Provides us with the necessary information to match EObjects.
     * @param match
     *            Match for which we need to check a feature.
     * @param feature
     *            The feature which values we need to check.
     * @param value1
     *            First of the two values which index we are to compare.
     * @param value2
     *            Second of the two values which index we are to compare.
     * @return {@code true} if the two given values are located at the same index in the given feature's
     *         values list, {@code false} otherwise.
     */
    @SuppressWarnings("unchecked")
    private boolean matchingIndices(Comparison comparison, Match match, EStructuralFeature feature, Object value1,
            Object value2) {
        boolean matching = false;
        if (feature.isMany()) {
            final List<Object> leftValues = (List<Object>) ReferenceUtil.safeEGet(match.getLeft(), feature);
            final List<Object> rightValues = (List<Object>) ReferenceUtil.safeEGet(match.getRight(), feature);

            // FIXME the detection _will_ fail for non-unique lists with multiple identical values...
            int leftIndex = -1;
            int rightIndex = -1;
            for (int i = 0; i < leftValues.size(); i++) {
                final Object left = leftValues.get(i);
                if (comparison.getEqualityHelper().matchingValues(left, value1)) {
                    break;
                } else if (hasDiff(match, feature, left) || hasDeleteDiff(match, feature, left)) {
                    // Do not increment.
                } else {
                    leftIndex++;
                }
            }
            for (int i = 0; i < rightValues.size(); i++) {
                final Object right = rightValues.get(i);
                if (comparison.getEqualityHelper().matchingValues(right, value2)) {
                    break;
                } else if (hasDiff(match, feature, right) || hasDeleteDiff(match, feature, right)) {
                    // Do not increment.
                } else {
                    rightIndex++;
                }
            }
            matching = leftIndex == rightIndex;
        } else {
            matching = true;
        }
        return matching;
    }

    /**
     * Checks whether the given {@code match} presents a difference of any kind on the given {@code feature}'s
     * {@code value}.
     * 
     * @param match
     *            The match which differences we'll check.
     * @param feature
     *            The feature on which we expect a difference.
     * @param value
     *            The value we expect to have changed inside {@code feature}.
     * @return <code>true</code> if there is such a Diff on {@code match}, <code>false</code> otherwise.
     */
    private boolean hasDiff(Match match, EStructuralFeature feature, Object value) {
        return Iterables.any(match.getDifferences(), and(onFeature(feature.getName()), valueIs(value)));
    }

    /**
     * Checks whether the given {@code value} has been deleted from the given {@code feature} of {@code match}
     * .
     * 
     * @param match
     *            The match which differences we'll check.
     * @param feature
     *            The feature on which we expect a difference.
     * @param value
     *            The value we expect to have been removed from {@code feature}.
     * @return <code>true</code> if there is such a Diff on {@code match}, <code>false</code> otherwise.
     */
    @SuppressWarnings("unchecked")
    private boolean hasDeleteDiff(Match match, EStructuralFeature feature, Object value) {
        final Comparison comparison = match.getComparison();
        final Object expectedValue;
        if (value instanceof EObject && comparison.isThreeWay()) {
            final Match valueMatch = comparison.getMatch((EObject) value);
            if (valueMatch != null) {
                expectedValue = valueMatch.getOrigin();
            } else {
                expectedValue = value;
            }
        } else {
            expectedValue = value;
        }
        return Iterables.any(match.getDifferences(),
                and(onFeature(feature.getName()), valueIs(expectedValue), ofKind(DifferenceKind.DELETE)));
    }

    /**
     * This will be called whenever we detect a new conflict in order to create (or update) the actual
     * association.
     * 
     * @param comparison
     *            The originating comparison of those diffs.
     * @param diff1
     *            First of the two differences for which we detected a conflict.
     * @param diff2
     *            Second of the two differences for which we detected a conflict.
     * @param kind
     *            Kind of this conflict.
     */
    protected void conflictOn(Comparison comparison, Diff diff1, Diff diff2, ConflictKind kind) {
        Conflict conflict = null;
        Conflict toBeMerged = null;
        if (diff1.getConflict() != null) {
            conflict = diff1.getConflict();
            if (conflict.getKind() == ConflictKind.PSEUDO && conflict.getKind() != kind) {
                conflict.setKind(kind);
            }
            if (diff2.getConflict() != null) {
                // Merge the two
                toBeMerged = diff2.getConflict();
            }
        } else if (diff2.getConflict() != null) {
            conflict = diff2.getConflict();
            if (conflict.getKind() == ConflictKind.PSEUDO && conflict.getKind() != kind) {
                conflict.setKind(kind);
            }
        } else if (diff1.getEquivalence() != null) {
            Equivalence equivalence = diff1.getEquivalence();
            for (Diff equ : equivalence.getDifferences()) {
                if (equ.getConflict() != null) {
                    conflict = equ.getConflict();
                    if (conflict.getKind() == ConflictKind.PSEUDO && conflict.getKind() != kind) {
                        conflict.setKind(kind);
                    }
                    if (diff2.getConflict() != null) {
                        // Merge the two
                        toBeMerged = diff2.getConflict();
                    }
                    break;
                }
            }
        } else if (diff2.getEquivalence() != null) {
            Equivalence equivalence = diff2.getEquivalence();
            for (Diff equ : equivalence.getDifferences()) {
                if (equ.getConflict() != null) {
                    conflict = equ.getConflict();
                    if (conflict.getKind() == ConflictKind.PSEUDO && conflict.getKind() != kind) {
                        conflict.setKind(kind);
                    }
                    break;
                }
            }
        }

        if (conflict == null) {
            conflict = CompareFactory.eINSTANCE.createConflict();
            conflict.setKind(kind);
            comparison.getConflicts().add(conflict);
        }

        final List<Diff> conflictDiffs = conflict.getDifferences();
        if (toBeMerged != null) {
            // These references are opposite. We can't simply iterate
            for (Diff aDiff : Lists.newArrayList(toBeMerged.getDifferences())) {
                if (!conflictDiffs.contains(aDiff)) {
                    conflictDiffs.add(aDiff);
                }
            }
            if (toBeMerged.getKind() == ConflictKind.REAL && conflict.getKind() != ConflictKind.REAL) {
                conflict.setKind(ConflictKind.REAL);
            }
            EcoreUtil.remove(toBeMerged);
            toBeMerged.getDifferences().clear();
        }

        if (!conflict.getDifferences().contains(diff1)) {
            conflict.getDifferences().add(diff1);
        }
        if (!conflict.getDifferences().contains(diff2)) {
            conflict.getDifferences().add(diff2);
        }

        // This diff may have equivalences. These equivalences
    }
}