| /******************************************************************************* |
| * Copyright (c) 2014, 2017 Obeo and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Obeo - initial API and implementation |
| * Philip Langer - bug 469355, bug 462884, refactorings |
| * Martin Fleck - bug 507177 |
| * Martin Fleck - bug 514415 |
| *******************************************************************************/ |
| package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.actions; |
| |
| import static com.google.common.base.Preconditions.checkState; |
| import static com.google.common.base.Predicates.alwaysFalse; |
| import static com.google.common.collect.Iterables.any; |
| import static com.google.common.collect.Lists.newArrayList; |
| import static org.eclipse.emf.compare.DifferenceSource.LEFT; |
| import static org.eclipse.emf.compare.DifferenceSource.RIGHT; |
| import static org.eclipse.emf.compare.internal.merge.MergeMode.ACCEPT; |
| import static org.eclipse.emf.compare.internal.merge.MergeMode.LEFT_TO_RIGHT; |
| import static org.eclipse.emf.compare.internal.merge.MergeMode.REJECT; |
| import static org.eclipse.emf.compare.internal.merge.MergeMode.RIGHT_TO_LEFT; |
| import static org.eclipse.emf.compare.internal.merge.MergeOperation.MARK_AS_MERGE; |
| import static org.eclipse.emf.compare.merge.AbstractMerger.isInTerminalState; |
| import static org.eclipse.emf.compare.utils.EMFComparePredicates.containsConflictOfTypes; |
| import static org.eclipse.emf.compare.utils.EMFComparePredicates.fromSide; |
| |
| import com.google.common.base.Predicate; |
| import com.google.common.base.Predicates; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Lists; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.List; |
| |
| import org.eclipse.emf.common.util.BasicMonitor; |
| import org.eclipse.emf.common.util.Monitor; |
| import org.eclipse.emf.compare.Comparison; |
| import org.eclipse.emf.compare.ConflictKind; |
| import org.eclipse.emf.compare.Diff; |
| import org.eclipse.emf.compare.DifferenceSource; |
| import org.eclipse.emf.compare.domain.IMergeRunnable; |
| import org.eclipse.emf.compare.internal.domain.IMergeAllNonConflictingRunnable; |
| import org.eclipse.emf.compare.internal.merge.MergeMode; |
| import org.eclipse.emf.compare.internal.merge.MergeOperation; |
| import org.eclipse.emf.compare.internal.utils.ComparisonUtil; |
| import org.eclipse.emf.compare.merge.BatchMerger; |
| import org.eclipse.emf.compare.merge.ComputeDiffsToMerge; |
| import org.eclipse.emf.compare.merge.IBatchMerger; |
| import org.eclipse.emf.compare.merge.IDiffRelationshipComputer; |
| import org.eclipse.emf.compare.merge.IMerger; |
| import org.eclipse.emf.compare.merge.IMerger.Registry; |
| import org.eclipse.emf.compare.rcp.EMFCompareLogger; |
| |
| /** |
| * Implements the "merge non-conflicting" and "merge all non-conflicting" action. |
| * |
| * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a> |
| */ |
| public class MergeNonConflictingRunnable extends AbstractMergeRunnable implements IMergeAllNonConflictingRunnable, IMergeRunnable { |
| |
| /** The logger. */ |
| private static final EMFCompareLogger LOGGER = new EMFCompareLogger(MergeNonConflictingRunnable.class); |
| |
| /** |
| * Default constructor. |
| * |
| * @param isLeftEditable |
| * Whether the left side of the comparison we're operating on is editable. |
| * @param isRightEditable |
| * Whether the right side of the comparison we're operating on is editable. |
| * @param mergeMode |
| * Merge mode for this operation. |
| * @param diffRelationshipComputer |
| * The diff relationship computer used to find resulting merges and rejections. |
| */ |
| public MergeNonConflictingRunnable(boolean isLeftEditable, boolean isRightEditable, MergeMode mergeMode, |
| IDiffRelationshipComputer diffRelationshipComputer) { |
| super(isLeftEditable, isRightEditable, mergeMode, diffRelationshipComputer); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public Iterable<Diff> merge(Comparison comparison, boolean leftToRight, Registry mergerRegistry) { |
| checkState(getMergeMode().isLeftToRight(isLeftEditable(), isRightEditable()) == leftToRight); |
| return doMergeNonConflicting(comparison.getDifferences(), comparison, leftToRight, mergerRegistry); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * <p> |
| * Differences that are conflicting or that depend on conflicting differences will be left out. |
| * Non-conflicting differences that are implied or required by the given differences will be merged, also |
| * if they are not explicitly included in the given list of {@code differences}. |
| * </p> |
| */ |
| @SuppressWarnings("unchecked") |
| public void merge(List<? extends Diff> differences, boolean leftToRight, Registry mergerRegistry) { |
| checkState(getMergeMode().isLeftToRight(isLeftEditable(), isRightEditable()) == leftToRight); |
| checkState(!differences.isEmpty() && ComparisonUtil.getComparison(differences.get(0)) != null); |
| final Comparison comparison = ComparisonUtil.getComparison(differences.get(0)); |
| doMergeNonConflicting((Collection<Diff>)differences, comparison, leftToRight, mergerRegistry); |
| } |
| |
| /** |
| * Performs the merge of the non-conflicting differences in the given {@code differences}. |
| * |
| * @param differences |
| * The differences to be merged. |
| * @param comparison |
| * The comparison containing the differences to decide on whether conflicts are in play or not |
| * and to determine whether this is a three- or two-way comparison. |
| * @param leftToRight |
| * The direction in which {@code differences} should be merged. |
| * @param mergerRegistry |
| * The registry of mergers. |
| * @return an iterable over the differences that have actually been merged by this operation. |
| */ |
| private Iterable<Diff> doMergeNonConflicting(Collection<Diff> differences, Comparison comparison, |
| boolean leftToRight, Registry mergerRegistry) { |
| final Iterable<Diff> affectedChanges; |
| if (hasRealConflict(comparison)) { |
| // This is a 3-way comparison, pre-merge what can be. |
| affectedChanges = mergeWithConflicts(differences, leftToRight, mergerRegistry); |
| } else if (comparison.isThreeWay()) { |
| // This is a 3-way comparison without conflicts |
| affectedChanges = mergeThreeWayWithoutConflicts(differences, leftToRight, mergerRegistry); |
| } else { |
| // This is a 2-way comparison |
| affectedChanges = mergeTwoWay(differences, leftToRight, mergerRegistry); |
| } |
| return affectedChanges; |
| } |
| |
| /** |
| * Handles the merge of all non-conflicting differences in case of a three-way comparison without |
| * conflicts. |
| * |
| * @param differences |
| * The differences to be merged. |
| * @param leftToRight |
| * The direction in which {@code differences} should be merged. |
| * @param mergerRegistry |
| * The registry of mergers. |
| * @return an iterable over the differences that have actually been merged by this operation. |
| */ |
| private Iterable<Diff> mergeThreeWayWithoutConflicts(Collection<Diff> differences, boolean leftToRight, |
| Registry mergerRegistry) { |
| final List<Diff> affectedDiffs; |
| final IBatchMerger merger = new BatchMerger(getDiffRelationshipComputer(mergerRegistry)); |
| |
| if (getMergeMode() == MergeMode.LEFT_TO_RIGHT) { |
| affectedDiffs = Lists |
| .newArrayList(Iterables.filter(differences, fromSide(DifferenceSource.LEFT))); |
| merger.copyAllLeftToRight(affectedDiffs, new BasicMonitor()); |
| } else if (getMergeMode() == MergeMode.RIGHT_TO_LEFT) { |
| affectedDiffs = Lists |
| .newArrayList(Iterables.filter(differences, fromSide(DifferenceSource.RIGHT))); |
| merger.copyAllRightToLeft(affectedDiffs, new BasicMonitor()); |
| } else if (getMergeMode() == MergeMode.ACCEPT || getMergeMode() == MergeMode.REJECT) { |
| affectedDiffs = acceptOrRejectWithoutConflicts(differences, leftToRight, mergerRegistry, merger); |
| } else { |
| throw new IllegalStateException(); |
| } |
| |
| return affectedDiffs; |
| } |
| |
| /** |
| * Returns the {@link MergeOperation} for the given {@code diff}. |
| * <p> |
| * The merge operation will be different depending on whether the left-hand side and right-hand side are |
| * editable in the current context (i.e., the {@link #getMergeMode() merge mode}. |
| * </p> |
| * |
| * @param diff |
| * The difference to get the merge operation for. |
| * @return The merge operation. |
| */ |
| private MergeOperation getMergeOperation(Diff diff) { |
| return getMergeMode().getMergeAction(diff, isLeftEditable(), isRightEditable()); |
| } |
| |
| /** |
| * Handles the merge of all non-conflicting differences in case of a two-way comparison without conflicts. |
| * |
| * @param differences |
| * The differences to be merged. |
| * @param leftToRight |
| * The direction in which {@code differences} should be merged. |
| * @param mergerRegistry |
| * The registry of mergers. |
| * @return an iterable over the differences that have actually been merged by this operation. |
| */ |
| private Iterable<Diff> mergeTwoWay(Collection<Diff> differences, boolean leftToRight, |
| Registry mergerRegistry) { |
| final List<Diff> affectedDiffs; |
| final IBatchMerger merger = new BatchMerger(getDiffRelationshipComputer(mergerRegistry)); |
| |
| // in two-way comparison, difference source is always LEFT |
| if (getMergeMode() == MergeMode.LEFT_TO_RIGHT) { |
| affectedDiffs = Lists |
| .newArrayList(Iterables.filter(differences, fromSide(DifferenceSource.LEFT))); |
| merger.copyAllLeftToRight(affectedDiffs, new BasicMonitor()); |
| } else if (getMergeMode() == MergeMode.RIGHT_TO_LEFT) { |
| affectedDiffs = Lists |
| .newArrayList(Iterables.filter(differences, fromSide(DifferenceSource.LEFT))); |
| merger.copyAllRightToLeft(affectedDiffs, new BasicMonitor()); |
| } else if (getMergeMode() == MergeMode.ACCEPT || getMergeMode() == MergeMode.REJECT) { |
| affectedDiffs = acceptOrRejectWithoutConflicts(differences, leftToRight, mergerRegistry, merger); |
| } else { |
| throw new IllegalStateException(); |
| } |
| |
| return affectedDiffs; |
| } |
| |
| /** |
| * Handles the merge of all non-conflicting differences in case of a comparison with conflicts. |
| * |
| * @param differences |
| * The differences to be merged. |
| * @param leftToRight |
| * The direction in which {@code differences} should be merged. |
| * @param mergerRegistry |
| * The registry of mergers, must be an instance of Registry2. |
| * @return an iterable over the differences that have actually been merged by this operation. |
| */ |
| private Iterable<Diff> mergeWithConflicts(Collection<Diff> differences, boolean leftToRight, |
| Registry mergerRegistry) { |
| final List<Diff> affectedDiffs = new ArrayList<Diff>(); |
| final Monitor emfMonitor = new BasicMonitor(); |
| ComputeDiffsToMerge computer = new ComputeDiffsToMerge(getMergeMode(), isLeftEditable(), |
| isRightEditable(), getDiffRelationshipComputer(mergerRegistry)) |
| .failOnRealConflictUnless(alwaysFalse()); |
| |
| final Predicate<? super Diff> filter; |
| MergeMode mode = getMergeMode(); |
| if (mode == RIGHT_TO_LEFT) { |
| filter = fromSide(RIGHT); |
| } else if (mode == LEFT_TO_RIGHT) { |
| filter = fromSide(LEFT); |
| } else { |
| filter = Predicates.alwaysTrue(); |
| } |
| |
| for (Diff diff : computer.getAllDiffsToMerge(Iterables.filter(differences, filter))) { |
| doMergeDiffWithConflicts(leftToRight, mergerRegistry, affectedDiffs, emfMonitor, diff); |
| |
| } |
| return affectedDiffs; |
| } |
| |
| protected void doMergeDiffWithConflicts(boolean leftToRight, Registry mergerRegistry, |
| List<Diff> affectedDiffs, Monitor emfMonitor, Diff diff) { |
| if (!isInTerminalState(diff)) { |
| affectedDiffs.add(diff); |
| final IMerger merger = mergerRegistry.getHighestRankingMerger(diff); |
| |
| if (LOGGER.isDebugEnabled()) { |
| LOGGER.debug( |
| "mergeWithConflicts(Collection<Diff>, boolean, Registry) - Selected merger for diff " //$NON-NLS-1$ |
| + diff.hashCode() + ": " + merger.getClass().getSimpleName()); //$NON-NLS-1$ |
| } |
| |
| MergeMode mergeMode = getMergeMode(); |
| if (mergeMode == LEFT_TO_RIGHT) { |
| merger.copyLeftToRight(diff, emfMonitor); |
| } else if (mergeMode == RIGHT_TO_LEFT) { |
| merger.copyRightToLeft(diff, emfMonitor); |
| } else if (mergeMode == ACCEPT || mergeMode == REJECT) { |
| MergeOperation mergeAction = getMergeOperation(diff); |
| if (mergeAction == MARK_AS_MERGE) { |
| markAsMerged(diff, mergeMode, leftToRight, mergerRegistry); |
| } else { |
| if (isLeftEditable() && !leftToRight) { |
| merger.copyRightToLeft(diff, emfMonitor); |
| } else if (isRightEditable() && leftToRight) { |
| merger.copyLeftToRight(diff, emfMonitor); |
| } |
| } |
| } else { |
| throw new IllegalStateException(); |
| } |
| } |
| } |
| |
| /** |
| * Performs an accept or reject operation in a three-way merge without conflicts or in a two-way merge. |
| * |
| * @param differences |
| * The differences to be merged. |
| * @param leftToRight |
| * The direction in which {@code differences} should be merged. |
| * @param mergerRegistry |
| * The registry of mergers. |
| * @param merger |
| * The merger to be used in this operation. |
| * @return an iterable over the differences that have actually been merged by this operation. |
| */ |
| private List<Diff> acceptOrRejectWithoutConflicts(Collection<Diff> differences, boolean leftToRight, |
| Registry mergerRegistry, final IBatchMerger merger) { |
| final List<Diff> diffsToMarkAsMerged = newArrayList(); |
| final List<Diff> diffsToAccept = newArrayList(); |
| final List<Diff> diffsToReject = newArrayList(); |
| |
| for (Diff diff : differences) { |
| final MergeOperation mergeAction = getMergeOperation(diff); |
| if (mergeAction == MergeOperation.MARK_AS_MERGE) { |
| diffsToMarkAsMerged.add(diff); |
| } else { |
| if (isLeftEditable() && leftToRight) { |
| diffsToReject.add(diff); |
| } else { |
| diffsToAccept.add(diff); |
| } |
| } |
| } |
| |
| final Monitor emfMonitor = new BasicMonitor(); |
| mergeAll(diffsToAccept, leftToRight, merger, mergerRegistry, emfMonitor); |
| mergeAll(diffsToReject, !leftToRight, merger, mergerRegistry, emfMonitor); |
| markAllAsMerged(diffsToMarkAsMerged, getMergeMode(), mergerRegistry); |
| |
| final List<Diff> affectedDiffs = Lists.newArrayList(diffsToAccept); |
| affectedDiffs.addAll(diffsToReject); |
| affectedDiffs.addAll(diffsToMarkAsMerged); |
| return affectedDiffs; |
| } |
| |
| /** |
| * Checks whether the given comparison presents a real conflict. |
| * |
| * @param comparison |
| * The comparison to check for conflicts. |
| * @return <code>true</code> if there's at least one {@link ConflictKind#REAL real conflict} within this |
| * comparison. |
| */ |
| private boolean hasRealConflict(Comparison comparison) { |
| return any(comparison.getConflicts(), containsConflictOfTypes(ConflictKind.REAL)); |
| } |
| |
| /** |
| * Merge all given differences in case of an ACCEPT or REJECT MergeMode. |
| * |
| * @param differences |
| * The differences to merge. |
| * @param leftToRight |
| * The direction in which {@code differences} should be merged. |
| * @param merger |
| * The current merger. |
| * @param mergerRegistry |
| * The registry of mergers. |
| * @param emfMonitor |
| * To monitor the process. |
| */ |
| private void mergeAll(Collection<? extends Diff> differences, boolean leftToRight, IBatchMerger merger, |
| Registry mergerRegistry, Monitor emfMonitor) { |
| if (leftToRight) { |
| merger.copyAllLeftToRight(differences, emfMonitor); |
| } else { |
| merger.copyAllRightToLeft(differences, emfMonitor); |
| } |
| } |
| } |