/******************************************************************************* * Copyright (c) 2014, 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 - bug 462884 * Martin Fleck - bug 507177 *******************************************************************************/ package org.eclipse.emf.compare.internal.merge; import static com.google.common.base.Predicates.and; import static com.google.common.base.Predicates.not; import static org.eclipse.emf.compare.ConflictKind.PSEUDO; import static org.eclipse.emf.compare.utils.EMFComparePredicates.hasConflict; import static org.eclipse.emf.compare.utils.EMFComparePredicates.sameSideAs; import com.google.common.collect.Sets; import com.google.common.collect.Sets.SetView; import java.util.Collections; import java.util.LinkedHashSet; import java.util.Set; import org.eclipse.emf.compare.Diff; import org.eclipse.emf.compare.merge.IMerger; import org.eclipse.emf.compare.merge.IMerger2; /** * Factorizes utilities used throughout EMF Compare to explore merge dependencies. * * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a> */ public final class MergeDependenciesUtil { /** Hides default constructor. */ private MergeDependenciesUtil() { // Hides default constructor } /** * Retrieves the set of all diffs related to the given <code>diff</code> when merging in the given * direction. * <p> * This is expected to return the set of all differences that will be need to merged along when a user * wishes to merge <code>diff</code>, either because they are required by it or because they are implied * by it one way or another. * </p> * <p> * Note that <code>diff</code> will be included in the returned set. * </p> * <p> * Also note that the resulting merges will contain the resulting rejections (diffs from the other side * that will be rejected) * </p> * * @param diff * The difference for which we seek all related ones. * @param mergerRegistry * The {@link IMerger.Registry merger registry} currently in use. * @param rightToLeft * The direction in which we're considering a merge. * @return The set of all diffs related to the given <code>diff</code> when merging in the given * direction. */ public static Set<Diff> getAllResultingMerges(Diff diff, IMerger.Registry mergerRegistry, boolean rightToLeft) { final Set<Diff> resultingMerges = new LinkedHashSet<Diff>(); resultingMerges.add(diff); Set<Diff> relations = internalGetResultingMerges(diff, mergerRegistry, rightToLeft); // We don't want to take in account pseudo conflicts since there is nothing to do with them // and their dependencies may cause incorrect merge dependencies computation. Set<Diff> difference = Sets.filter(Sets.difference(relations, resultingMerges), not(hasConflict(PSEUDO))); while (!difference.isEmpty()) { final Set<Diff> newRelations = new LinkedHashSet<Diff>(difference); resultingMerges.addAll(newRelations); relations = new LinkedHashSet<Diff>(); for (Diff newRelation : newRelations) { Set<Diff> internalResultingMerges = internalGetResultingMerges(newRelation, mergerRegistry, rightToLeft); // We don't want to take in account pseudo conflicts since there is nothing to do with them // and there dependencies may cause incorrect merge dependencies computation. relations.addAll(Sets.filter(internalResultingMerges, not(hasConflict(PSEUDO)))); } difference = Sets.difference(relations, resultingMerges); } // If a pseudo conflict is directly selected, we want to display other diffs of the pseudo conflict as // resulting merge for the user if (diff.getConflict() != null && diff.getConflict().getKind() == PSEUDO) { resultingMerges.addAll(diff.getConflict().getDifferences()); } return resultingMerges; } /** * Returns the set of all differences <b>directly</b> related to the given one, either as dependencies or * as implications. * * @param diff * The difference for which we seek all directly related others. * @param mergerRegistry * The {@link IMerger.Registry merger registry} currently in use. * @param rightToLeft * The direction in which we're considering a merge. * @return The set of all differences <b>directly</b> related to the given one. */ private static Set<Diff> internalGetResultingMerges(Diff diff, IMerger.Registry mergerRegistry, boolean rightToLeft) { final IMerger merger = mergerRegistry.getHighestRankingMerger(diff); final Set<Diff> directParents; final Set<Diff> directImplications; if (merger instanceof IMerger2) { directParents = ((IMerger2)merger).getDirectMergeDependencies(diff, rightToLeft); directImplications = ((IMerger2)merger).getDirectResultingMerges(diff, rightToLeft); } else { directParents = Collections.emptySet(); directImplications = Collections.emptySet(); } final SetView<Diff> directRelated = Sets.union(directParents, directImplications); return directRelated; } /** * Retrieves the set of all diffs that will be rejected if the given <code>diff</code> is merged, either * because of unresolveable conflicts or because of unreachable requirements. * * @param diff * The difference for which we seek all opposite ones. * @param mergerRegistry * The {@link IMerger.Registry merger registry} currently in use. * @param mergeRightToLeft * The direction in which we're considering a merge. * @return The set of all diffs that will be rejected if the given <code>diff</code> is merged in the * given direction. */ public static Set<Diff> getAllResultingRejections(Diff diff, IMerger.Registry mergerRegistry, boolean mergeRightToLeft) { final Set<Diff> resultingRejections = new LinkedHashSet<Diff>(); final Set<Diff> allResultingMerges = getAllResultingMerges(diff, mergerRegistry, mergeRightToLeft); resultingRejections.addAll( Sets.filter(allResultingMerges, and(not(hasConflict(PSEUDO)), not(sameSideAs(diff))))); // Only search rejections caused by diffs on the same side for (Diff resulting : Sets.filter(allResultingMerges, sameSideAs(diff))) { Set<Diff> rejections = internalGetResultingRejections(resulting, mergerRegistry, mergeRightToLeft); // We don't want to take in account pseudo conflicts since there is nothing to do with them // and their dependencies may cause incorrect merge dependencies computation. Set<Diff> difference = Sets.filter(rejections, not(hasConflict(PSEUDO))); while (!difference.isEmpty()) { final Set<Diff> newRejections = new LinkedHashSet<Diff>(difference); resultingRejections.addAll(newRejections); rejections = new LinkedHashSet<Diff>(); for (Diff rejected : newRejections) { final IMerger merger = mergerRegistry.getHighestRankingMerger(rejected); if (merger instanceof IMerger2) { Set<Diff> directMergeDependencies = ((IMerger2)merger) .getDirectMergeDependencies(rejected, mergeRightToLeft); // We don't want to take in account pseudo conflicts since there is nothing to do with // them and their dependencies may cause incorrect merge dependencies computation. // We also don't want to consider diffs on the same side for rejections rejections.addAll(Sets.filter(directMergeDependencies, and(not(hasConflict(PSEUDO)), not(sameSideAs(diff))))); Set<Diff> directResultingMerges = ((IMerger2)merger) .getDirectResultingMerges(rejected, mergeRightToLeft); rejections.addAll(Sets.filter(directResultingMerges, and(not(hasConflict(PSEUDO)), not(sameSideAs(diff))))); } } difference = Sets.difference(rejections, resultingRejections); } } return resultingRejections; } /** * Returns the set of differences directly related to <code>diff</code> that will be rejected if it is * merged. * * @param diff * The difference for which we seek all opposite ones. * @param mergerRegistry * The {@link IMerger.Registry merger registry} currently in use. * @param rightToLeft * The direction in which we're considering a merge. * @return The set of all directly related differences that will be rejected if <code>diff</code> is * merged in the given direction. */ private static Set<Diff> internalGetResultingRejections(Diff diff, IMerger.Registry mergerRegistry, boolean rightToLeft) { final IMerger merger = mergerRegistry.getHighestRankingMerger(diff); if (merger instanceof IMerger2) { return ((IMerger2)merger).getDirectResultingRejections(diff, rightToLeft); } return Collections.emptySet(); } }