/*******************************************************************************
* Copyright (c) 2013, 2016 Obeo.
* 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
*******************************************************************************/
package org.eclipse.emf.compare.merge;
import static org.eclipse.emf.compare.ConflictKind.REAL;
import static org.eclipse.emf.compare.DifferenceKind.DELETE;
import static org.eclipse.emf.compare.DifferenceKind.MOVE;
import static org.eclipse.emf.compare.DifferenceSource.LEFT;
import static org.eclipse.emf.compare.DifferenceSource.RIGHT;
import static org.eclipse.emf.compare.DifferenceState.DISCARDED;
import static org.eclipse.emf.compare.merge.IMergeCriterion.NONE;
import java.util.Iterator;
import java.util.Set;
import org.eclipse.emf.common.util.Monitor;
import org.eclipse.emf.compare.Conflict;
import org.eclipse.emf.compare.Diff;
import org.eclipse.emf.compare.ReferenceChange;
import org.eclipse.emf.compare.utils.IEqualityHelper;
/**
* This specific implementation of {@link AbstractMerger} will be used to merge real conflicts.
*
* @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
* @since 3.1
*/
public class ConflictMerger extends AbstractMerger {
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.merge.IMerger#isMergerFor(org.eclipse.emf.compare.Diff)
*/
public boolean isMergerFor(Diff target) {
return target.getConflict() != null && target.getConflict().getKind() == REAL;
}
@Override
public boolean apply(IMergeCriterion criterion) {
return criterion == null || criterion == NONE;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.merge.IMerger#copyLeftToRight(org.eclipse.emf.compare.Diff,
* org.eclipse.emf.common.util.Monitor)
*/
@Override
public void copyLeftToRight(Diff target, Monitor monitor) {
if (isInTerminalState(target)) {
return;
}
if (target.getSource() == LEFT) {
// Call the appropriate merger for each conflicted diff
Conflict conflict = target.getConflict();
for (Diff conflictedDiff : conflict.getDifferences()) {
if (conflictedDiff.getSource() == RIGHT) {
if (isConflictVsMoveAndDelete(target, conflictedDiff, true)) {
conflictedDiff.setState(DISCARDED);
} else {
mergeConflictedDiff(conflictedDiff, true, monitor);
}
}
}
}
// Call the appropriate merger for the current diff
getMergerDelegate(target).copyLeftToRight(target, monitor);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.merge.IMerger#copyRightToLeft(org.eclipse.emf.compare.Diff,
* org.eclipse.emf.common.util.Monitor)
*/
@Override
public void copyRightToLeft(Diff target, Monitor monitor) {
if (isInTerminalState(target)) {
return;
}
if (target.getSource() == RIGHT) {
// Call the appropriate merger for each conflicted diff
Conflict conflict = target.getConflict();
for (Diff conflictedDiff : conflict.getDifferences()) {
if (conflictedDiff.getSource() == LEFT) {
if (isConflictVsMoveAndDelete(target, conflictedDiff, false)) {
conflictedDiff.setState(DISCARDED);
} else {
mergeConflictedDiff(conflictedDiff, false, monitor);
}
}
}
}
// Call the appropriate merger for the current diff
getMergerDelegate(target).copyRightToLeft(target, monitor);
}
@Override
public Set<Diff> getDirectMergeDependencies(Diff diff, boolean rightToLeft) {
Set<Diff> result = super.getDirectMergeDependencies(diff, rightToLeft);
// Add each conflicting diff from the other side
Conflict conflict = diff.getConflict();
if (AbstractMerger.isAccepting(diff, rightToLeft)) {
for (Diff conflictingDiff : conflict.getDifferences()) {
if (conflictingDiff.getSource() != diff.getSource()
&& !isConflictVsMoveAndDelete(conflictingDiff, diff, !rightToLeft)
&& conflictingDiff.getKind() != MOVE) {
result.add(conflictingDiff);
}
}
}
return result;
}
@Override
public Set<Diff> getDirectResultingMerges(Diff diff, boolean rightToLeft) {
Set<Diff> result = super.getDirectResultingMerges(diff, rightToLeft);
Conflict conflict = diff.getConflict();
if (AbstractMerger.isAccepting(diff, rightToLeft)) {
for (Diff conflictingDiff : conflict.getDifferences()) {
if (conflictingDiff.getSource() != diff.getSource()
&& (isConflictVsMoveAndDelete(conflictingDiff, diff, !rightToLeft)
|| conflictingDiff.getKind() != MOVE)) {
result.add(conflictingDiff);
}
}
}
return result;
}
/**
* Detect if the two given diff are diffs on the same object with one move and one delete. The move diff
* must be the one selected by the user for merging.
*
* @param target
* The diff selected for merge by the user
* @param conflictedDiff
* Another diff of the conflict
* @param leftToRight
* The direction of the merge
* @return <code>true</code> if the diff selected by the user is a move and is conflicting with a delete
* of the same element
*/
private boolean isConflictVsMoveAndDelete(Diff target, Diff conflictedDiff, boolean leftToRight) {
boolean result = false;
if (target.getConflict() != null && target.getConflict().getKind() == REAL) {
if (target instanceof ReferenceChange && target.getKind() == MOVE) {
ReferenceChange moveDiff = (ReferenceChange)target;
if (conflictedDiff instanceof ReferenceChange && conflictedDiff.getKind() == DELETE) {
ReferenceChange deleteDiff = (ReferenceChange)conflictedDiff;
IEqualityHelper equalityHelper = target.getMatch().getComparison().getEqualityHelper();
result = equalityHelper.matchingAttributeValues(moveDiff.getValue(),
deleteDiff.getValue());
}
}
}
return result;
}
/**
* Manages the merge of the given conflicted diff.
*
* @param conflictedDiff
* The given diff.
* @param leftToRight
* The way of merge.
* @param monitor
* Monitor.
*/
private void mergeConflictedDiff(Diff conflictedDiff, boolean leftToRight, Monitor monitor) {
if (conflictedDiff.getKind() != MOVE) {
DelegatingMerger delegate = getMergerDelegate(conflictedDiff);
if (leftToRight) {
delegate.copyLeftToRight(conflictedDiff, monitor);
} else {
delegate.copyRightToLeft(conflictedDiff, monitor);
}
} else {
conflictedDiff.setState(DISCARDED);
}
}
@Override
protected DelegatingMerger getMergerDelegate(Diff diff) {
IMergeCriterion criterion = (IMergeCriterion)getMergeOptions()
.get(IMergeCriterion.OPTION_MERGE_CRITERION);
Iterator<IMerger> it = ((Registry2)getRegistry()).getMergersByRankDescending(diff, criterion);
IMerger merger = this;
while (it.hasNext() && merger == this) {
merger = it.next();
}
if (merger == null) {
throw new IllegalStateException("No merger found for diff " + diff.getClass().getSimpleName()); //$NON-NLS-1$
}
return new DelegatingMerger(merger, criterion);
}
}