/******************************************************************************* * Copyright (c) 2013, 2017 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 * Philip Langer - fix use of StorageTraversal.getStorages() * Martin Fleck - bug 512562 *******************************************************************************/ package org.eclipse.emf.compare.ide.ui.internal.logical; import static org.eclipse.emf.compare.ide.utils.ResourceUtil.binaryIdentical; import java.util.Iterator; import java.util.Set; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IStorage; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIMessages; import org.eclipse.emf.compare.ide.ui.logical.IModelMinimizer; import org.eclipse.emf.compare.ide.ui.logical.SynchronizationModel; import org.eclipse.emf.compare.ide.utils.StorageTraversal; /** * Instances of this class will be used by EMF Compare to minimize the scope to parts of a logical model that * can be considered valid candidates for a difference. * <p> * This default implementation will consider that all files that are binary identical between the two (or * three) sides of the comparison can be safely removed from the scope. Likewise, unmatched read-only files * will be removed from the scope. * </p> * * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a> */ public class IdenticalResourceMinimizer implements IModelMinimizer { /** * {@inheritDoc} Specifically, we'll remove all resources that can be seen as binary identical (we match * resources through exact equality of their names). * * @see org.eclipse.emf.compare.ide.ui.logical.IModelMinimizer#minimize(org.eclipse.emf.compare.ide.ui.logical.SynchronizationModel, * org.eclipse.core.runtime.IProgressMonitor) */ public void minimize(SynchronizationModel syncModel, IProgressMonitor monitor) { SubMonitor progess = SubMonitor.convert(monitor, 100); progess.subTask(EMFCompareIDEUIMessages.getString("EMFSynchronizationModel.minimizing")); //$NON-NLS-1$ final StorageTraversal leftTraversal = syncModel.getLeftTraversal(); final StorageTraversal rightTraversal = syncModel.getRightTraversal(); final StorageTraversal originTraversal = syncModel.getOriginTraversal(); // StorageTraversal.getStorages() already creates a mutable copy. To change the underlying set, we // need to use StorageTraversal.removeStorage(). final Set<? extends IStorage> leftCopy = leftTraversal.getStorages(); final Set<? extends IStorage> rightCopy = rightTraversal.getStorages(); final Set<? extends IStorage> originCopy = originTraversal.getStorages(); final boolean threeWay = !originCopy.isEmpty(); SubMonitor subMonitor = progess.newChild(98).setWorkRemaining(leftCopy.size()); for (IStorage left : leftCopy) { final IStorage right = removeLikeNamedStorageFrom(left, rightCopy); if (right != null && threeWay) { final IStorage origin = removeLikeNamedStorageFrom(left, originCopy); if (origin != null && equals(left, right, origin)) { leftTraversal.removeStorage(left); rightTraversal.removeStorage(right); originTraversal.removeStorage(origin); } } else if (right != null && equals(left, right)) { leftTraversal.removeStorage(left); rightTraversal.removeStorage(right); } else if (right == null && isIgnoredStorage(left)) { /* * Left has no match in right and is in plugins, so remove it from the scope. Otherwise, we * would unnecessarily include added models that should be ignored. */ leftTraversal.removeStorage(left); } subMonitor.worked(1); } subMonitor = progess.newChild(1).setWorkRemaining(rightCopy.size()); for (IStorage right : rightCopy) { final IStorage origin = removeLikeNamedStorageFrom(right, originCopy); if (origin != null) { // we had a match in the origin, leave this file in scope (it's been removed from left) } else if (isIgnoredStorage(right)) { /* * This has no match and is in plugins. We would detect an insane number of false positives on * it (every element "removed"), so remove it from the scope. */ rightTraversal.removeStorage(right); } subMonitor.worked(1); } subMonitor = progess.newChild(1).setWorkRemaining(rightCopy.size()); for (IStorage origin : originCopy) { // These have no match on left and right. if (isIgnoredStorage(origin)) { originTraversal.removeStorage(origin); } subMonitor.worked(1); } } /** * {@inheritDoc} Specifically, this minimizer does not consider the selected file and performs the same * operation as it does without the file. * * @see org.eclipse.emf.compare.ide.ui.logical.IModelMinimizer#minimize(IFile, SynchronizationModel, * IProgressMonitor) */ public void minimize(IFile file, SynchronizationModel syncModel, IProgressMonitor monitor) { minimize(syncModel, monitor); } /** * Checks whether the three given (non-<code>null</code>) resources are identical. This default * implementation only checks that the three are identical binary-wise. * <p> * Identical resources will be filtered out of the comparison scope. * </p> * * @param left * Left of the resources to consider. * @param right * Right of the resources to consider. * @param origin * Common ancestor of the left and right resources. * @return <code>true</code> if the given resources are to be considered identical, <code>false</code> * otherwise. */ protected boolean equals(IStorage left, IStorage right, IStorage origin) { return binaryIdentical(left, right, origin); } /** * Checks whether the two given (non-<code>null</code>) resources are identical. This default * implementation only checks that the two are identical binary-wise. * <p> * Identical resources will be filtered out of the comparison scope. * </p> * * @param left * Left of the resources to consider. * @param rightRight * of the resources to consider. * @return <code>true</code> if the given resources are to be considered identical, <code>false</code> * otherwise. */ protected boolean equals(IStorage left, IStorage right) { return binaryIdentical(left, right); } /** * Looks up into the {@code candidates} set for a storage which name matches that of the {@code reference} * storage, removing it if there is one. * * @param reference * The storage for which we'll seek a match into {@code candidates}. * @param candidates * The set of candidates into which to look up for a match to {@code reference}. * @return The first storage from the set of candidates that matches the {@code reference}, if any. * <code>null</code> if none match. */ protected IStorage removeLikeNamedStorageFrom(IStorage reference, Set<? extends IStorage> candidates) { final String referenceName = reference.getName(); final Iterator<? extends IStorage> candidatesIterator = candidates.iterator(); while (candidatesIterator.hasNext()) { final IStorage candidate = candidatesIterator.next(); final String candidateName = candidate.getName(); if (referenceName.equals(candidateName)) { candidatesIterator.remove(); return candidate; } } return null; } /** * We will remove from the scope any storage that is located in the plugins (detecting differences on such * files is meaningless). * * @param storage * The storage we need to test. * @return <code>true</code> if this storage should be ignored and removed from this scope. */ private boolean isIgnoredStorage(IStorage storage) { return storage.getFullPath().toString().startsWith("platform:/plugin"); //$NON-NLS-1$ } }