blob: da01232b04f7398e17c1621cf94caac4d50c8bc0 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2014 EclipseSource Muenchen GmbH and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Philip Langer - initial API and implementation
*******************************************************************************/
package org.eclipse.emf.compare.uml2.internal.postprocessor;
import static com.google.common.collect.Iterables.filter;
import com.google.common.base.Predicate;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.Monitor;
import org.eclipse.emf.compare.CompareFactory;
import org.eclipse.emf.compare.Comparison;
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.Match;
import org.eclipse.emf.compare.conflict.DefaultConflictDetector;
import org.eclipse.emf.compare.postprocessor.IPostProcessor;
import org.eclipse.emf.compare.uml2.internal.OpaqueElementBodyChange;
import org.eclipse.emf.compare.uml2.internal.postprocessor.util.UMLCompareUtil;
import org.eclipse.emf.ecore.EObject;
/**
* This post-processor or IConflictDetector adds conflicts specific to opaque element body changes.
*
* @author Philip Langer <planger@eclipsesource.com>
*/
public class OpaqueElementBodyChangePostProcessor extends DefaultConflictDetector implements IPostProcessor {
/**
* A predicate for {@link Diff} that can be used to check whether the {@link Diff} is a
* {@link OpaqueElementBodyChange}.
*/
private static final Predicate<Diff> IS_OPAQUE_ELEMENT_CHANGE = new Predicate<Diff>() {
public boolean apply(Diff diff) {
return diff instanceof OpaqueElementBodyChange;
}
};
/**
* A predicate for {@link Diff} that can be used to check whether the {@link Diff} is a
* {@link OpaqueElementBodyChange} and its {@link DifferenceSource} is LEFT.
*/
private static final Predicate<Diff> IS_LEFT_OPAQUE_ELEMENT_CHANGE = new Predicate<Diff>() {
public boolean apply(Diff diff) {
return DifferenceSource.LEFT.equals(diff.getSource()) && IS_OPAQUE_ELEMENT_CHANGE.apply(diff);
}
};
/**
* A predicate for {@link Diff} that can be used to check whether the {@link Diff} is a
* {@link OpaqueElementBodyChange} and its {@link DifferenceSource} is RIGHT.
*/
private static final Predicate<Diff> IS_RIGHT_OPAQUE_ELEMENT_CHANGE = new Predicate<Diff>() {
public boolean apply(Diff diff) {
return DifferenceSource.RIGHT.equals(diff.getSource()) && IS_OPAQUE_ELEMENT_CHANGE.apply(diff);
}
};
/**
* {@inheritDoc}
*
* @see IPostProcessor#postMatch(Comparison, Monitor)
*/
public void postMatch(Comparison comparison, Monitor monitor) {
// nothing to do
}
/**
* {@inheritDoc}
*
* @see IPostProcessor#postDiff(Comparison, Monitor)
*/
public void postDiff(Comparison comparison, Monitor monitor) {
// nothing to do
}
/**
* {@inheritDoc}
*
* @see IPostProcessor#postRequirements(Comparison, Monitor)
*/
public void postRequirements(Comparison comparison, Monitor monitor) {
// nothing to do
}
/**
* {@inheritDoc}
*
* @see IPostProcessor#postEquivalences(Comparison, Monitor)
*/
public void postEquivalences(Comparison comparison, Monitor monitor) {
// nothing to do
}
/**
* {@inheritDoc}
*
* @see IPostProcessor#postConflicts(Comparison, Monitor)
*/
public void postConflicts(Comparison comparison, Monitor monitor) {
// nothing to do
}
/**
* {@inheritDoc}
*
* @see IPostProcessor#postComparison(Comparison, Monitor)
*/
public void postComparison(Comparison comparison, Monitor monitor) {
detect(comparison, monitor);
}
/**
* Detects and adds conflicts related to changes of the language and bodies of opaque actions, behaviors,
* and expressions.
*
* @param comparison
* The comparison to check for conflicts.
* @param monitor
* The monitor to use for progress reporting.
*/
@Override
public void detect(Comparison comparison, Monitor monitor) {
final EList<Diff> diffs = comparison.getDifferences();
final Iterable<Diff> leftBodyChanges = filter(diffs, IS_LEFT_OPAQUE_ELEMENT_CHANGE);
for (Diff leftDiff : leftBodyChanges) {
final OpaqueElementBodyChange leftBodyChange = (OpaqueElementBodyChange)leftDiff;
final EObject leftOpaqueElement = leftBodyChange.getDiscriminant();
final Match opaqueElementMatch = comparison.getMatch(leftOpaqueElement);
final EList<Diff> rightDiffs = comparison.getDifferences(opaqueElementMatch.getRight());
final Iterable<Diff> rightBodyChanges = filter(rightDiffs, IS_RIGHT_OPAQUE_ELEMENT_CHANGE);
for (Diff rightDiff : rightBodyChanges) {
OpaqueElementBodyChange rightBodyChange = (OpaqueElementBodyChange)rightDiff;
if (isConflicting(leftBodyChange, rightBodyChange)) {
final Conflict conflict = createRealConflict(leftBodyChange, rightBodyChange);
comparison.getConflicts().add(conflict);
}
}
}
}
/**
* Specifies whether the given {@code bodyChange1} is conflicting with the given {@code bodyChange2}. Note
* that the conflict relation among differences is symmetric, that means {@code bodyChange1} and
* {@code bodyChange2} can be switched and this method returns the same result.
*
* @param bodyChange1
* The one {@code OpaqueElementBodyChange} to check.
* @param bodyChange2
* The other {@code OpaqueElementBodyChange} to check.
* @return <code>true</code> if {@code bodyChange1} and {@code bodyChange2} are conflicting,
* <code>false</code> otherwise.
*/
private boolean isConflicting(OpaqueElementBodyChange bodyChange1, OpaqueElementBodyChange bodyChange2) {
boolean areConflicting = false;
if (concernSameLanguage(bodyChange1, bodyChange2)) {
if (areDifferenceKindChange(bodyChange1, bodyChange2)) {
areConflicting = isThreeWayTextConflict(bodyChange1);
} else if (areDifferenceKindChangeAndDelete(bodyChange1, bodyChange2)) {
areConflicting = true;
} else if (areDifferenceKindAdd(bodyChange1, bodyChange2)) {
areConflicting = true;
} else if (areDifferenceKindMove(bodyChange1, bodyChange2)) {
areConflicting = true;
}
}
return areConflicting;
}
/**
* Specifies whether the given {@code bodyChange1} and {@code bodyChange2} concern the same language
* value.
*
* @param bodyChange1
* The one {@code OpaqueElementBodyChange} to check.
* @param bodyChange2
* The other {@code OpaqueElementBodyChange} to check.
* @return <code>true</code> if {@code bodyChange1} and {@code bodyChange2} concern the same language
* value, <code>false</code> otherwise.
*/
private boolean concernSameLanguage(OpaqueElementBodyChange bodyChange1,
OpaqueElementBodyChange bodyChange2) {
return bodyChange1.getLanguage().equals(bodyChange2.getLanguage());
}
/**
* Specifies whether the given {@code bodyChange1} and {@code bodyChange2} both are of
* {@link DifferenceKind} CHANGE.
*
* @param bodyChange1
* The one {@code OpaqueElementBodyChange} to check.
* @param bodyChange2
* The other {@code OpaqueElementBodyChange} to check.
* @return <code>true</code> if {@code bodyChange1} and {@code bodyChange2} both are of
* {@link DifferenceKind} CHANGE, <code>false</code> otherwise.
*/
private boolean areDifferenceKindChange(OpaqueElementBodyChange bodyChange1,
OpaqueElementBodyChange bodyChange2) {
return DifferenceKind.CHANGE.equals(bodyChange1.getKind())
&& DifferenceKind.CHANGE.equals(bodyChange2.getKind());
}
/**
* Specifies whether the given {@code bodyChange1} and {@code bodyChange2} both are of
* {@link DifferenceKind} ADD.
*
* @param bodyChange1
* The one {@code OpaqueElementBodyChange} to check.
* @param bodyChange2
* The other {@code OpaqueElementBodyChange} to check.
* @return <code>true</code> if {@code bodyChange1} and {@code bodyChange2} both are of
* {@link DifferenceKind} ADD, <code>false</code> otherwise.
*/
private boolean areDifferenceKindAdd(OpaqueElementBodyChange bodyChange1,
OpaqueElementBodyChange bodyChange2) {
return DifferenceKind.ADD.equals(bodyChange1.getKind())
&& DifferenceKind.ADD.equals(bodyChange2.getKind());
}
/**
* Specifies whether the given {@code bodyChange1} is of {@link DifferenceKind} CHANGE and
* {@code bodyChange2} of {@link DifferenceKind} DELETE or vice versa.
*
* @param bodyChange1
* The one {@code OpaqueElementBodyChange} to check.
* @param bodyChange2
* The other {@code OpaqueElementBodyChange} to check.
* @return <code>true</code> if {@code bodyChange1} and {@code bodyChange2} are of {@link DifferenceKind}
* CHANGE and {@link DifferenceKind} DELETE or vice versa.
*/
private boolean areDifferenceKindChangeAndDelete(OpaqueElementBodyChange bodyChange1,
OpaqueElementBodyChange bodyChange2) {
return (DifferenceKind.CHANGE.equals(bodyChange1.getKind())
&& DifferenceKind.DELETE.equals(bodyChange2.getKind()))
|| (DifferenceKind.DELETE.equals(bodyChange1.getKind())
&& DifferenceKind.CHANGE.equals(bodyChange2.getKind()));
}
/**
* Specifies whether the given {@code bodyChange1} and {@code bodyChange2} both are of
* {@link DifferenceKind} MOVE.
*
* @param bodyChange1
* The one {@code OpaqueElementBodyChange} to check.
* @param bodyChange2
* The other {@code OpaqueElementBodyChange} to check.
* @return <code>true</code> if {@code bodyChange1} and {@code bodyChange2} both are of
* {@link DifferenceKind} MOVE, <code>false</code> otherwise.
*/
private boolean areDifferenceKindMove(OpaqueElementBodyChange bodyChange1,
OpaqueElementBodyChange bodyChange2) {
return DifferenceKind.MOVE.equals(bodyChange1.getKind())
&& DifferenceKind.MOVE.equals(bodyChange2.getKind());
}
/**
* Specifies whether the given {@code bodyChange} is a non-mergeable text change.
* <p>
* Changes are non-mergeable if they cannot be merged with opposite changes using a line-based three-way
* merge algorithm.
* </p>
*
* @param bodyChange
* The {@code OpaqueElementBodyChange} to check.
* @return <code>true</code> if the change is conflicting (i.e., non-mergeable), <code>false</code>
* otherwise.
*/
private boolean isThreeWayTextConflict(OpaqueElementBodyChange bodyChange) {
if (bodyChange.getMatch().getOrigin() == null) {
return false;
}
final Match match = bodyChange.getMatch();
final EObject originContainer = match.getOrigin();
final EObject leftContainer = match.getLeft();
final EObject rightContainer = match.getRight();
final String language = bodyChange.getLanguage();
final String originBody = UMLCompareUtil.getOpaqueElementBody(originContainer, language);
final String leftBody = UMLCompareUtil.getOpaqueElementBody(leftContainer, language);
final String rightBody = UMLCompareUtil.getOpaqueElementBody(rightContainer, language);
return !isMergeableText(leftBody, rightBody, originBody);
}
/**
* Creates and returns a {@link Conflict} for the two given {@link OpaqueElementBodyChange changes},
* {@code bodyChange1} and {@code bodyChange2}.
*
* @param bodyChange1
* The one {@link OpaqueElementBodyChange} to create a conflict for.
* @param bodyChange2
* The other {@link OpaqueElementBodyChange} to create a conflict for.
* @return The created conflict.
*/
private Conflict createRealConflict(final OpaqueElementBodyChange bodyChange1,
OpaqueElementBodyChange bodyChange2) {
Conflict conflict = CompareFactory.eINSTANCE.createConflict();
conflict.setKind(ConflictKind.REAL);
conflict.getDifferences().add(bodyChange1);
conflict.getDifferences().add(bodyChange2);
return conflict;
}
}