/******************************************************************************* * Copyright (c) 2012, 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 - add scope of a comparison to its adapters *******************************************************************************/ package org.eclipse.emf.compare; import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.collect.Iterators; import java.util.Iterator; import java.util.List; import org.apache.log4j.Logger; import org.eclipse.emf.common.notify.Adapter; import org.eclipse.emf.common.notify.Notifier; import org.eclipse.emf.common.util.BasicDiagnostic; import org.eclipse.emf.common.util.BasicMonitor; import org.eclipse.emf.common.util.Diagnostic; import org.eclipse.emf.common.util.DiagnosticChain; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.Monitor; import org.eclipse.emf.compare.conflict.IConflictDetector; import org.eclipse.emf.compare.conflict.MatchBasedConflictDetector; import org.eclipse.emf.compare.diff.DefaultDiffEngine; import org.eclipse.emf.compare.diff.DiffBuilder; import org.eclipse.emf.compare.diff.IDiffEngine; import org.eclipse.emf.compare.equi.DefaultEquiEngine; import org.eclipse.emf.compare.equi.IEquiEngine; import org.eclipse.emf.compare.internal.spec.ComparisonSpec; import org.eclipse.emf.compare.internal.utils.SafeSubMonitor; import org.eclipse.emf.compare.match.IMatchEngine; import org.eclipse.emf.compare.match.impl.MatchEngineFactoryRegistryImpl; import org.eclipse.emf.compare.merge.ResourceChangeAdapter; import org.eclipse.emf.compare.postprocessor.IPostProcessor; import org.eclipse.emf.compare.postprocessor.PostProcessorDescriptorRegistryImpl; import org.eclipse.emf.compare.req.DefaultReqEngine; import org.eclipse.emf.compare.req.IReqEngine; import org.eclipse.emf.compare.scope.DefaultComparisonScope; import org.eclipse.emf.compare.scope.IComparisonScope; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.util.EcoreUtil; /** * This class serves as the main entry point of a comparison. When all that is wanted is a basic comparison of * two or three notifiers, a comparison using all of the default configuration can be launched through * <code>EMFCompare.builder().build().compare(EMFCompare.createDefaultScope(left, right, origin))</code>. * <p> * When in need of a more customized comparison, the API can be used through chained calls. For example, if * you need to compare two notifiers ({@code left} and {@code right}) while ignoring their identifiers, with a * given progress monitor (call it {@code progress}), you can do so through : <code> * EMFCompare.builder().setMatchEngine(DefaultMatchEngine.create(UseIdentifiers.NEVER)).build().compare(EMFCompare.createDefaultScope(left, right), new BasicMonitor()) * </code>. * </p> * * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a> */ public class EMFCompare { /** * The value for diagnostics coming from EMF compare. * * @since 3.2 */ public static final String DIAGNOSTIC_SOURCE = "org.eclipse.emf.compare"; //$NON-NLS-1$ /** Constant for logging. */ private static final String START = " - START"; //$NON-NLS-1$ /** Constant for logging. */ private static final String FINISH = " - FINISH"; //$NON-NLS-1$ /** The logger. */ private static final Logger LOGGER = Logger.getLogger(EMFCompare.class); /** The registry we'll use to create a match engine for this comparison. */ private final IMatchEngine.Factory.Registry matchEngineFactoryRegistry; /** The IDiffEngine to use to compute comparison. */ private final IDiffEngine diffEngine; /** The IReqEngine to use to compute comparison. */ private final IReqEngine reqEngine; /** The IEquiEngine to use to compute comparison. */ private final IEquiEngine equiEngine; /** The IConflictDetector to use to compute comparison. */ private final IConflictDetector conflictDetector; /** The PostProcessorRegistry to use to find an IPostProcessor. */ private final IPostProcessor.Descriptor.Registry<?> postProcessorDescriptorRegistry; /** * Creates a new EMFCompare object able to compare Notifier with the help of given engines. * * @param matchEngineFactoryRegistry * {@link IMatchEngine.Factory.Registry} to use to find a match engine factory to compute * comparison * @param diffEngine * IDiffEngine to use to compute comparison * @param reqEngine * IReqEngine to use to compute comparison * @param equiEngine * IEquiEngine to use to compute comparison * @param conflictDetector * IConflictDetector to use to compute comparison * @param postProcessorFactoryRegistry * PostProcessorRegistry to use to find an IPostProcessor */ protected EMFCompare(IMatchEngine.Factory.Registry matchEngineFactoryRegistry, IDiffEngine diffEngine, IReqEngine reqEngine, IEquiEngine equiEngine, IConflictDetector conflictDetector, IPostProcessor.Descriptor.Registry<?> postProcessorFactoryRegistry) { this.matchEngineFactoryRegistry = checkNotNull(matchEngineFactoryRegistry); this.diffEngine = checkNotNull(diffEngine); this.reqEngine = checkNotNull(reqEngine); this.equiEngine = checkNotNull(equiEngine); this.conflictDetector = conflictDetector; this.postProcessorDescriptorRegistry = checkNotNull(postProcessorFactoryRegistry); } /** * Creates a default comparison scope given its left and right notifiers. * <p> * The default comparison scope covers all proper content of the given notifiers, i.e any element * contained directly under that notifier. * </p> * * @param left * The left notifier of this scope. * @param right * The right notifier of this scope. * @return The newly created scope, as used as default by EMF Compare. * @see DefaultComparisonScope * @deprecated this was only a delegation to the publicly accessible {@link DefaultComparisonScope}... it * will be removed in a subsequent release. */ @Deprecated public static IComparisonScope createDefaultScope(Notifier left, Notifier right) { return new DefaultComparisonScope(left, right, null); } /** * Creates the default comparison scope given its left and right notifiers, along with the common ancestor * of both. * <p> * The default comparison scope covers all proper content of the given notifiers, i.e any element * contained directly under that notifier. * </p> * * @param left * The left notifier of this scope. * @param right * The right notifier of this scope. * @param origin * The common ancestor of {@code left} and {@code right}. * @return The newly created scope, as used as default by EMF Compare. * @see DefaultComparisonScope * @deprecated this was only a delegation to the publicly accessible {@link DefaultComparisonScope}... it * will be removed in a subsequent release. */ @Deprecated public static IComparisonScope createDefaultScope(Notifier left, Notifier right, Notifier origin) { return new DefaultComparisonScope(left, right, origin); } /** * Computes and returns a new Comparison object representation the differences between Notifier in the * give {@code scope}. * * @param scope * the scope to compare. * @return the result of the comparison. */ public Comparison compare(IComparisonScope scope) { return compare(scope, new BasicMonitor()); } /** * Launches the comparison with the given scope and reporting progress to the given {@code monitor}. * * @param scope * the scope to compare, must not be {@code null}. * @param monitor * the monitor to report progress to, must not be {@code null}. {@code done()} will be called * on it. If the monitor is cancelled, the result may be {@code null} (in rare cases) or * contain a Diagnostic that indicates cancellation. <b>Note:</b> The given monitor is expected * to use 10 ticks for 100%. * @return The result of the comparison, which is never null but may be empty if the monitor has been * canceled immediately after entering this method. The returned comparison will contain a * relevant diagnostic indicating if the comparison has been canceled or if problems have occurred * during its computation. Consequently, it is necessary to check the diagnostic of the returned * comparison before using it. * @throws ComparisonCanceledException * If the comparison is cancelled at any time. */ public Comparison compare(IComparisonScope scope, final Monitor monitor) { checkNotNull(scope); checkNotNull(monitor); // Used to compute the time spent in the method long startTime = System.currentTimeMillis(); if (LOGGER.isInfoEnabled()) { LOGGER.info("compare() - START"); //$NON-NLS-1$ } Comparison comparison = null; try { Monitor subMonitor = new SafeSubMonitor(monitor); if (LOGGER.isInfoEnabled()) { LOGGER.info("compare() - starting step: MATCH"); //$NON-NLS-1$ } comparison = matchEngineFactoryRegistry.getHighestRankingMatchEngineFactory(scope) .getMatchEngine().match(scope, subMonitor); installResourceChangeAdapter(comparison, scope); monitor.worked(1); List<IPostProcessor> postProcessors = postProcessorDescriptorRegistry.getPostProcessors(scope); // CHECKSTYLE:OFF Yes, I want to have ifs here and no constant for "post-processor". if (LOGGER.isInfoEnabled()) { LOGGER.info("compare() - starting step: POST-MATCH with " + postProcessors.size() //$NON-NLS-1$ + " post-processors"); //$NON-NLS-1$ } postMatch(comparison, postProcessors, subMonitor); monitor.worked(1); if (!hasToStop(comparison, monitor)) { if (LOGGER.isInfoEnabled()) { LOGGER.info("compare() - starting step: DIFF"); //$NON-NLS-1$ } diffEngine.diff(comparison, subMonitor); monitor.worked(1); if (LOGGER.isInfoEnabled()) { LOGGER.info("compare() - starting step: POST-DIFF with " //$NON-NLS-1$ + postProcessors.size() + " post-processors"); //$NON-NLS-1$ } postDiff(comparison, postProcessors, subMonitor); monitor.worked(1); if (!hasToStop(comparison, monitor)) { if (LOGGER.isInfoEnabled()) { LOGGER.info("compare() - starting step: REQUIREMENTS"); //$NON-NLS-1$ } reqEngine.computeRequirements(comparison, subMonitor); monitor.worked(1); if (LOGGER.isInfoEnabled()) { LOGGER.info("compare() - starting step: POST-REQUIREMENTS with " //$NON-NLS-1$ + postProcessors.size() + " post-processors"); //$NON-NLS-1$ } postRequirements(comparison, postProcessors, subMonitor); monitor.worked(1); if (!hasToStop(comparison, monitor)) { if (LOGGER.isInfoEnabled()) { LOGGER.info("compare() - starting step: EQUIVALENCES"); //$NON-NLS-1$ } equiEngine.computeEquivalences(comparison, subMonitor); monitor.worked(1); if (LOGGER.isInfoEnabled()) { LOGGER.info("compare() - starting step: POST-EQUIVALENCES with " //$NON-NLS-1$ + postProcessors.size() + " post-processors"); //$NON-NLS-1$ } postEquivalences(comparison, postProcessors, subMonitor); monitor.worked(1); if (LOGGER.isInfoEnabled()) { LOGGER.info("compare() - starting step: CONFLICT"); //$NON-NLS-1$ } detectConflicts(comparison, postProcessors, subMonitor); monitor.worked(1); if (LOGGER.isInfoEnabled()) { LOGGER.info("compare() - starting step: POST-COMPARISON with " //$NON-NLS-1$ + postProcessors.size() + " post-processors"); //$NON-NLS-1$ } // CHECKSTYLE:ON postComparison(comparison, postProcessors, subMonitor); } } } } catch (ComparisonCanceledException e) { if (LOGGER.isInfoEnabled()) { LOGGER.info("compare() - Comparison has been canceled"); //$NON-NLS-1$ } if (comparison == null) { comparison = new ComparisonSpec(); } BasicDiagnostic cancelledDiag = new BasicDiagnostic(Diagnostic.CANCEL, DIAGNOSTIC_SOURCE, 0, EMFCompareMessages.getString("ComparisonCancelled"), null); //$NON-NLS-1$ Diagnostic diag = comparison.getDiagnostic(); if (diag != null && diag instanceof DiagnosticChain) { ((DiagnosticChain)diag).merge(cancelledDiag); } else { comparison.setDiagnostic(cancelledDiag); } } finally { monitor.done(); } if (LOGGER.isInfoEnabled()) { logEndOfComparison(comparison, startTime); } // Add scope to the comparison's adapters to make it available throughout the framework if (scope instanceof Adapter) { comparison.eAdapters().add((Adapter)scope); } return comparison; } /** * Install a new {@link ResourceChangeAdapter} on the given comparison and on all the resources on the * left and right side of the scope, unless it's already been done. If a {@link ResourceChangeAdapter} is * already registered on the given comparison, nothing happens. Otherwise, a new * {@link ResourceChangeAdapter} is created and installed on the comparison and on all the * {@link ResourceSet}s and {@link Resource}s of the left and right sides of the scope if its left and * right notifiers are actually {@link ResourceSet}s. * * @param comparison * The comparison * @param scope * The scope */ private void installResourceChangeAdapter(Comparison comparison, IComparisonScope scope) { if (scope.getLeft() instanceof ResourceSet && scope.getRight() instanceof ResourceSet) { Adapter existingAdapter = EcoreUtil.getExistingAdapter(comparison, ResourceChangeAdapter.class); if (existingAdapter == null) { ResourceChangeAdapter adapter = new ResourceChangeAdapter(comparison, scope); comparison.eAdapters().add(adapter); ResourceSet left = (ResourceSet)scope.getLeft(); left.eAdapters().add(adapter); for (Resource r : left.getResources()) { r.eAdapters().add(adapter); } ResourceSet right = (ResourceSet)scope.getRight(); right.eAdapters().add(adapter); for (Resource r : right.getResources()) { r.eAdapters().add(adapter); } } } } /** * Log useful informations at the end of the comparison process. * * @param comparison * The comparison * @param start * The time when the method start */ private void logEndOfComparison(Comparison comparison, long start) { long duration = System.currentTimeMillis() - start; int diffQuantity = comparison.getDifferences().size(); int conflictQuantity = comparison.getConflicts().size(); int matchQuantity = 0; EList<Match> matches = comparison.getMatches(); for (Match match : matches) { matchQuantity++; matchQuantity += Iterators.size(match.getAllSubmatches().iterator()); } LOGGER.info("compare() - FINISH - " + matchQuantity + " matches, " + diffQuantity + " diffs and " //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + conflictQuantity + " conflicts found in " + duration + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ } /** * Launches the conflict detection engine, if any has been given and if the comparison is three way. * * @param comparison * the comparison in which the conflict has to be detected * @param postProcessors * the list of post processors to be used after conflict detection * @param monitor * monitor to report progress to or cancellation */ private void detectConflicts(final Comparison comparison, List<IPostProcessor> postProcessors, final Monitor monitor) { if (!hasToStop(comparison, monitor) && comparison.isThreeWay() && conflictDetector != null) { conflictDetector.detect(comparison, monitor); postConflicts(comparison, postProcessors, monitor); } } /** * Post processes the comparison after the match engine has been applied. * * @param comparison * the comparison on which the post processors has to be applied * @param postProcessors * the list of post processors to be use * @param monitor * monitor to report progress to or cancellation */ private void postMatch(final Comparison comparison, List<IPostProcessor> postProcessors, final Monitor monitor) { Iterator<IPostProcessor> processorsIterator = postProcessors.iterator(); while (!hasToStop(comparison, monitor) && processorsIterator.hasNext()) { final IPostProcessor iPostProcessor = processorsIterator.next(); if (LOGGER.isInfoEnabled()) { LOGGER.info("postMatch with post-processor: " //$NON-NLS-1$ + iPostProcessor.getClass().getName() + START); } iPostProcessor.postMatch(comparison, monitor); if (LOGGER.isInfoEnabled()) { LOGGER.info("postMatch with post-processor: " //$NON-NLS-1$ + iPostProcessor.getClass().getName() + FINISH); } } } /** * Post processes the comparison after the diff engine has been applied. * * @param comparison * the comparison on which the post processors has to be applied * @param postProcessors * the list of post processors to be use * @param monitor * monitor to report progress to or cancellation */ private void postDiff(final Comparison comparison, List<IPostProcessor> postProcessors, final Monitor monitor) { Iterator<IPostProcessor> processorsIterator = postProcessors.iterator(); while (!hasToStop(comparison, monitor) && processorsIterator.hasNext()) { final IPostProcessor iPostProcessor = processorsIterator.next(); if (LOGGER.isInfoEnabled()) { LOGGER.info("postDiff with post-processor: " + iPostProcessor.getClass().getName() //$NON-NLS-1$ + START); } iPostProcessor.postDiff(comparison, monitor); if (LOGGER.isInfoEnabled()) { LOGGER.info("postDiff with post-processor: " + iPostProcessor.getClass().getName() //$NON-NLS-1$ + FINISH); } } } /** * Post processes the comparison after the requirement engine has been applied. * * @param comparison * the comparison on which the post processors has to be applied * @param postProcessors * the list of post processors to be use * @param monitor * monitor to report progress to or cancellation */ private void postRequirements(final Comparison comparison, List<IPostProcessor> postProcessors, final Monitor monitor) { Iterator<IPostProcessor> processorsIterator = postProcessors.iterator(); while (!hasToStop(comparison, monitor) && processorsIterator.hasNext()) { final IPostProcessor iPostProcessor = processorsIterator.next(); if (LOGGER.isInfoEnabled()) { LOGGER.info("postRequirements with post-processor: " //$NON-NLS-1$ + iPostProcessor.getClass().getName() + START); } iPostProcessor.postRequirements(comparison, monitor); if (LOGGER.isInfoEnabled()) { LOGGER.info("postRequirements with post-processor: " //$NON-NLS-1$ + iPostProcessor.getClass().getName() + FINISH); } } } /** * Post processes the comparison after the equivalence engine has been applied. * * @param comparison * the comparison on which the post processors has to be applied * @param postProcessors * the list of post processors to be use * @param monitor * monitor to report progress to or cancellation */ private void postEquivalences(final Comparison comparison, List<IPostProcessor> postProcessors, final Monitor monitor) { Iterator<IPostProcessor> processorsIterator = postProcessors.iterator(); while (!hasToStop(comparison, monitor) && processorsIterator.hasNext()) { final IPostProcessor iPostProcessor = processorsIterator.next(); if (LOGGER.isInfoEnabled()) { LOGGER.info("postEquivalences with post-processor: " //$NON-NLS-1$ + iPostProcessor.getClass().getName() + START); } iPostProcessor.postEquivalences(comparison, monitor); if (LOGGER.isInfoEnabled()) { LOGGER.info("postEquivalences with post-processor: " //$NON-NLS-1$ + iPostProcessor.getClass().getName() + FINISH); } } } /** * Post processes the comparison after the conflict engine has been applied. * * @param comparison * the comparison on which the post processors has to be applied * @param postProcessors * the list of post processors to be use * @param monitor * monitor to report progress to or cancellation */ private void postConflicts(final Comparison comparison, List<IPostProcessor> postProcessors, final Monitor monitor) { Iterator<IPostProcessor> processorsIterator = postProcessors.iterator(); while (!hasToStop(comparison, monitor) && processorsIterator.hasNext()) { final IPostProcessor iPostProcessor = processorsIterator.next(); if (LOGGER.isInfoEnabled()) { LOGGER.info("postConflicts with post-processor: " //$NON-NLS-1$ + iPostProcessor.getClass().getName() + START); } iPostProcessor.postConflicts(comparison, monitor); if (LOGGER.isInfoEnabled()) { LOGGER.info("postConflicts with post-processor: " //$NON-NLS-1$ + iPostProcessor.getClass().getName() + FINISH); } } } /** * Post processes the comparison after it has been computed. * * @param comparison * the comparison on which the post processors has to be applied * @param postProcessors * the list of post processors to be use * @param monitor * monitor to report progress to or cancellation */ private void postComparison(final Comparison comparison, List<IPostProcessor> postProcessors, final Monitor monitor) { Iterator<IPostProcessor> processorsIterator = postProcessors.iterator(); while (!hasToStop(comparison, monitor) && processorsIterator.hasNext()) { final IPostProcessor iPostProcessor = processorsIterator.next(); if (LOGGER.isInfoEnabled()) { LOGGER.info("postComparison with post-processor: " //$NON-NLS-1$ + iPostProcessor.getClass().getName() + START); } iPostProcessor.postComparison(comparison, monitor); if (LOGGER.isInfoEnabled()) { LOGGER.info("postComparison with post-processor: " //$NON-NLS-1$ + iPostProcessor.getClass().getName() + FINISH); } } } /** * It checks if the comparison has to be stopped. * * @param comparison * The comparison. * @param monitor * The monitor. * @return True if the comparison has to be stopped. False otherwise. */ private static boolean hasToStop(Comparison comparison, Monitor monitor) { return monitor.isCanceled() || (comparison.getDiagnostic() != null && comparison.getDiagnostic().getSeverity() >= Diagnostic.ERROR); } /** * Creates a new builder to configure the creation of a new EMFCompare object. * * @return a new builder. */ public static Builder builder() { return new Builder(); } /** * A Builder pattern to instantiate EMFCompare objects. * * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a> */ public static class Builder { /** The registry we'll use to create a match engine for this comparison. */ protected IMatchEngine.Factory.Registry matchEngineFactoryRegistry; /** The IReqEngine to use to compute comparison. */ protected IReqEngine reqEngine; /** The IDiffEngine to use to compute comparison. */ protected IDiffEngine diffEngine; /** The IEquiEngine to use to compute comparison. */ protected IEquiEngine equiEngine; /** The IConflictDetector to use to compute conflicts. */ protected IConflictDetector conflictDetector; /** The PostProcessorRegistry to use to find an IPostProcessor. */ protected IPostProcessor.Descriptor.Registry<?> registry; /** * Creates a new builder object. */ protected Builder() { } /** * Sets the IMatchEngine.Factory.Registry to be used to find a match engine factory to compute * comparison. * * @param mefr * the IMatchEngine.Factory.Registry to be used to find a match engine factory to compute * comparison. * @return this same builder to allow chained call. */ public Builder setMatchEngineFactoryRegistry(IMatchEngine.Factory.Registry mefr) { this.matchEngineFactoryRegistry = checkNotNull(mefr); return this; } /** * Sets the IDiffEngine to be used to compute Diff. * * @param de * the IDiffEngine to be used to compute Diff. * @return this same builder to allow chained call. */ public Builder setDiffEngine(IDiffEngine de) { this.diffEngine = checkNotNull(de); return this; } /** * Sets the IReqEngine to be used to compute dependencies between Diff. * * @param re * the IReqEngine to be used to compute dependencies between Diff. * @return this same builder to allow chained call. */ public Builder setRequirementEngine(IReqEngine re) { this.reqEngine = checkNotNull(re); return this; } /** * Sets the IEquiEngine to be used to compute equivalences between Diff. * * @param ee * the IEquiEngine to be used to compute equivalences between Diff * @return this same builder to allow chained call. */ public Builder setEquivalenceEngine(IEquiEngine ee) { this.equiEngine = checkNotNull(ee); return this; } /** * Sets the IEquiEngine to be used to compute conflicts between Diff. * * @param cd * the IEquiEngine to be used to compute conflicts between Diff. * @return this same builder to allow chained call. */ public Builder setConflictDetector(IConflictDetector cd) { this.conflictDetector = checkNotNull(cd); return this; } /** * Sets the PostProcessor to be used to find the post processor of each comparison steps. * * @param r * the PostProcessor to be used to find the post processor of each comparison steps. * @return this same builder to allow chained call. */ public Builder setPostProcessorRegistry(IPostProcessor.Descriptor.Registry<?> r) { this.registry = checkNotNull(r); return this; } /** * Instantiates and return an EMFCompare object configured with the previously given engines. * * @return an EMFCompare object configured with the previously given engines */ public EMFCompare build() { if (matchEngineFactoryRegistry == null) { matchEngineFactoryRegistry = MatchEngineFactoryRegistryImpl.createStandaloneInstance(); } if (diffEngine == null) { diffEngine = new DefaultDiffEngine(new DiffBuilder()); } if (reqEngine == null) { reqEngine = new DefaultReqEngine(); } if (equiEngine == null) { equiEngine = new DefaultEquiEngine(); } if (registry == null) { registry = new PostProcessorDescriptorRegistryImpl<Object>(); } if (conflictDetector == null) { conflictDetector = new MatchBasedConflictDetector(); } return new EMFCompare(this.matchEngineFactoryRegistry, this.diffEngine, this.reqEngine, this.equiEngine, this.conflictDetector, this.registry); } } }