blob: 200551ad845a02ec08bf5ea381bc71789ffee7a4 [file] [log] [blame]
/*******************************************************************************
* 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 - bugs 461713, 465331, 470268, 476363, 476417, 486940, refactorings
* Alexandra Buzila - bugs 470332, 478620
* Stefan Dirix - bug 507050
*******************************************************************************/
package org.eclipse.emf.compare.ide.ui.internal.logical;
import static com.google.common.base.Predicates.alwaysFalse;
import static org.eclipse.emf.compare.utils.EMFComparePredicates.fromSide;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.io.ByteStreams;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IStorage;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.resources.mapping.ResourceMapping;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.MultiRule;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.notify.impl.AdapterImpl;
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.Monitor;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.compare.Comparison;
import org.eclipse.emf.compare.Conflict;
import org.eclipse.emf.compare.ConflictKind;
import org.eclipse.emf.compare.Diff;
import org.eclipse.emf.compare.DifferenceSource;
import org.eclipse.emf.compare.EMFCompare;
import org.eclipse.emf.compare.EMFCompare.Builder;
import org.eclipse.emf.compare.Match;
import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIMessages;
import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIPlugin;
import org.eclipse.emf.compare.ide.ui.internal.preferences.EMFCompareUIPreferences;
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.ResourceUtil;
import org.eclipse.emf.compare.ide.utils.StorageTraversal;
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.IMerger;
import org.eclipse.emf.compare.merge.IMerger.Registry2;
import org.eclipse.emf.compare.merge.MergeBlockedByConflictException;
import org.eclipse.emf.compare.rcp.EMFCompareRCPPlugin;
import org.eclipse.emf.compare.rcp.internal.extension.impl.EMFCompareBuilderConfigurator;
import org.eclipse.emf.compare.scope.IComparisonScope;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.team.core.diff.IDiff;
import org.eclipse.team.core.mapping.IMergeContext;
import org.eclipse.team.core.mapping.IResourceMappingMerger;
import org.eclipse.team.core.mapping.provider.MergeStatus;
/*
* Illegally implements IResourceMappingMerger, but we need none of the behavior from the abstract
* ResourceMappingMerger. Filtered in the API filters.
*/
/**
* A customized merger for the {@link EMFResourceMapping}s. This will use EMF Compare to recompute the logical
* model of the mappings it needs to merge, then merge everything to the left model if there are no conflicts,
* stopping dead if there is any conflict.
* <p>
* Mapping mergers are usually retrieved through an adapter registered on the ModelProvider. In this case,
* {@code org.eclipse.core.runtime.Platform.getAdapterManager().getAdapter(emfModelProvider, IResourceMappingMerger.class)}
* .
* </p>
*
* @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
* @see EMFLogicalModelAdapterFactory
*/
public class EMFResourceMappingMerger implements IResourceMappingMerger {
/** The merger registry. */
protected static final Registry2 MERGER_REGISTRY = (Registry2)EMFCompareRCPPlugin.getDefault()
.getMergerRegistry();
/** {@inheritDoc} */
public IStatus merge(IMergeContext mergeContext, IProgressMonitor monitor) throws CoreException {
final ResourceMapping[] emfMappings = getEMFMappings(mergeContext);
log(IStatus.OK, "EMFResourceMappingMerger.startingModelMerge", emfMappings); //$NON-NLS-1$
IStatus shortcutStatus = Status.OK_STATUS;
if (emfMappings.length <= 0) {
shortcutStatus = new Status(IStatus.ERROR, EMFCompareIDEUIPlugin.PLUGIN_ID,
EMFCompareIDEUIMessages.getString("EMFResourceMappingMerger.mergeFailedGeneric")); //$NON-NLS-1$
} else {
shortcutStatus = validateMappings(emfMappings);
}
if (shortcutStatus.getSeverity() != IStatus.OK) {
return shortcutStatus;
}
// Use a sub-monitor with 10 ticks per child
// For the time being, Cancel is not supported here because reverting changes is problematic
SubMonitor subMonitor = SubMonitor.convert(monitor, emfMappings.length);
try {
final Set<ResourceMapping> failingMappings = new HashSet<ResourceMapping>();
for (ResourceMapping mapping : emfMappings) {
mergeMapping(mapping, mergeContext, failingMappings, subMonitor.newChild(1));
}
IStatus status = Status.OK_STATUS;
if (!failingMappings.isEmpty()) {
final ResourceMapping[] failingArray = failingMappings
.toArray(new ResourceMapping[failingMappings.size()]);
status = new MergeStatus(EMFCompareIDEUIPlugin.PLUGIN_ID,
EMFCompareIDEUIMessages.getString("EMFResourceMappingMerger.mergeFailedConflicts"), //$NON-NLS-1$
failingArray);
} else {
log(IStatus.OK, "EMFResourceMappingMerger.successfulModelMerge", emfMappings); //$NON-NLS-1$
}
return status;
} finally {
if (monitor != null) {
monitor.done();
}
}
}
/**
* Logs the EMFCompareIDEUIPlugin message for the given {@code key} with the given severity
* {@code statusCode} and {@code emfMappings}.
*
* @param statusCode
* Status code must be one of {@link IStatus}.
* @param key
* Message key for EMFCompareIDEUIPlugin.
* @param emfMappings
* The resource mappings to log.
*/
private void log(int statusCode, String key, ResourceMapping[] emfMappings) {
final List<IResource> iResources = getInvolvedIResources(emfMappings);
final String message = EMFCompareIDEUIMessages.getString(key, String.valueOf(iResources.size()));
log(statusCode, message, iResources);
}
/**
* Logs the given {@code message} and the given {@code iResources} with the given severity
* {@code statusCode}.
* <p>
* The logged status is a {@link MultiStatus}, having the given {@code message} as a parent status and the
* names of the provided {@code iResources} as child statuses.
* </p>
*
* @param statusCode
* Status code must be one of {@link IStatus}.
* @param message
* The message to be logged.
* @param iResources
* The resources to be added as child status.
*/
private void log(int statusCode, String message, Collection<IResource> iResources) {
final MultiStatus multiStatus = new MultiStatus(EMFCompareIDEUIPlugin.PLUGIN_ID, 0, message, null);
for (IResource iResource : iResources) {
final Status childStatus = new Status(statusCode, EMFCompareIDEUIPlugin.PLUGIN_ID,
iResource.getFullPath().toOSString());
multiStatus.add(childStatus);
}
log(multiStatus);
}
/**
* Returns the {@link IResource resources} involved in the given {@code emfMappings}.
*
* @param emfMappings
* The resource mappings to get the involved resources from.
* @return The resources involved in {@code emfMappings}.
*/
private List<IResource> getInvolvedIResources(ResourceMapping[] emfMappings) {
final List<IResource> iResources = new ArrayList<IResource>();
for (ResourceMapping mapping : emfMappings) {
if (mapping instanceof EMFResourceMapping) {
final SynchronizationModel syncModel = ((EMFResourceMapping)mapping).getLatestModel();
for (IResource iResource : syncModel.getResources()) {
iResources.add(iResource);
}
}
}
return iResources;
}
/**
* Logs the given {@code status} to the log of {@link EMFCompareIDEUIPlugin}.
*
* @param status
* The {@link IStatus} to log.
*/
private void log(IStatus status) {
EMFCompareIDEUIPlugin.getDefault().getLog().log(status);
}
/**
* Merges one mapping.
*
* @param mapping
* The mapping to merge
* @param mergeContext
* The merge context
* @param failingMappings
* The set of failing mappings
* @param monitor
* The progress monitor to use, 10 ticks will be consumed
*/
protected void mergeMapping(ResourceMapping mapping, final IMergeContext mergeContext,
final Set<ResourceMapping> failingMappings, IProgressMonitor monitor) throws CoreException {
final SubMonitor subMonitor = SubMonitor.convert(monitor, 10);
// validateMappings() has made sure we only have EMFResourceMappings
final SynchronizationModel syncModel = ((EMFResourceMapping)mapping).getLatestModel();
// we may have non-existing storages in the left traversal, so let's get rid of them
removeNonExistingStorages(syncModel.getLeftTraversal());
// get the involved resources before we run the minimizer
final Set<IResource> resources = Sets.newLinkedHashSet(syncModel.getResources());
final IModelMinimizer minimizer = new IdenticalResourceMinimizer();
minimizer.minimize(syncModel, subMonitor.newChild(1)); // 10%
final IComparisonScope scope = ComparisonScopeBuilder.create(syncModel, subMonitor.newChild(3)); // 40%
final Builder builder = EMFCompare.builder();
EMFCompareBuilderConfigurator.createDefault().configure(builder);
final Comparison comparison = builder.build().compare(scope,
BasicMonitor.toMonitor(SubMonitor.convert(subMonitor.newChild(1), 10))); // 50%
if (hasRealConflict(comparison)) {
failingMappings.add(mapping);
final IPreferenceStore store = EMFCompareIDEUIPlugin.getDefault().getPreferenceStore();
boolean preMerge = store.getBoolean(EMFCompareUIPreferences.PRE_MERGE_MODELS_WHEN_CONFLICT);
if (preMerge) {
final Set<URI> conflictingURIs = performPreMerge(comparison, subMonitor.newChild(3)); // 80%
save(scope.getLeft(), syncModel.getLeftTraversal(), syncModel.getRightTraversal(),
syncModel.getOriginTraversal());
markResourcesAsMerged(mergeContext, resources, conflictingURIs, subMonitor.newChild(2));
}
} else {
final ResourceAdditionAndDeletionTracker resourceTracker = new ResourceAdditionAndDeletionTracker();
try {
scope.getLeft().eAdapters().add(resourceTracker);
performBatchMerge(comparison, subMonitor.newChild(3)); // 80%
save(scope.getLeft(), syncModel.getLeftTraversal(), syncModel.getRightTraversal(),
syncModel.getOriginTraversal());
delegateMergeOfUnmergedResourcesAndMarkDiffsAsMerged(syncModel, mergeContext, resourceTracker,
subMonitor.newChild(2)); // 100%
} finally {
scope.getLeft().eAdapters().remove(resourceTracker);
}
}
subMonitor.setWorkRemaining(0);
}
/**
* Removes storages that do not exist from the specified {@code traversal}.
* <p>
* In the current implementation, the check for existence is based on the assumption that the storage is
* an {@link IFile}. This is fine, since we currently need it on the local side only anyways.
* </p>
*
* @param traversal
* The traversal to remove non-existing storages from.
*/
protected void removeNonExistingStorages(StorageTraversal traversal) {
for (IStorage storage : traversal.getStorages()) {
if (storage instanceof IFile && !((IFile)storage).exists()) {
traversal.removeStorage(storage);
}
}
}
/**
* Performs a pre-merge of the given {@code comparison}.
* <p>
* A pre-merge is a merge that performs all non-conflicting changes but omits conflicting changes or
* changes that depend on conflicting changes.
* </p>
*
* @param comparison
* The comparison to pre-merge.
* @param subMonitor
* The progress monitor to use.
* @return the set of the uri for resources on which conflicts were not auto-mergeable.
*/
private Set<URI> performPreMerge(Comparison comparison, SubMonitor subMonitor) {
final Monitor emfMonitor = BasicMonitor.toMonitor(subMonitor);
final Set<URI> conflictingURIs = new LinkedHashSet<URI>();
for (Diff next : comparison.getDifferences()) {
doMergeForDiff(emfMonitor, conflictingURIs, next);
}
return conflictingURIs;
}
protected void doMergeForDiff(Monitor emfMonitor, Set<URI> conflictingURIs, Diff diff) {
ComputeDiffsToMerge computer = new ComputeDiffsToMerge(true, MERGER_REGISTRY)
.failOnRealConflictUnless(alwaysFalse());
try {
Set<Diff> diffsToMerge = computer.getAllDiffsToMerge(diff);
for (Diff toMerge : diffsToMerge) {
final IMerger merger = MERGER_REGISTRY.getHighestRankingMerger(toMerge);
merger.copyRightToLeft(toMerge, emfMonitor);
}
} catch (MergeBlockedByConflictException e) {
conflictingURIs.addAll(collectConflictingResources(e.getConflictingDiffs().iterator()));
}
}
/**
* Iterates over the given diffs to collect the resources they impact. This will be called in case of
* conflicts in order to know exactly which resources should be marked as conflicting.
*
* @param diffIterator
* Iterator over the conflicting differences and their dependent diffs.
* @return The uris of all resources impacted by conflicting differences.
*/
protected Set<URI> collectConflictingResources(Iterator<Diff> diffIterator) {
final Set<URI> conflictingURIs = new LinkedHashSet<URI>();
while (diffIterator.hasNext()) {
final Diff diff = diffIterator.next();
final ImmutableSet.Builder<Diff> builder = ImmutableSet.builder();
if (diff.getConflict() != null) {
builder.addAll(diff.getConflict().getDifferences()).add(diff);
for (Diff conflictingDiff : builder.build()) {
final Match next = conflictingDiff.getMatch();
final URI leftURI = resourceURIorNull(next.getLeft());
final URI rightURI = resourceURIorNull(next.getRight());
final URI originURI = resourceURIorNull(next.getOrigin());
if (leftURI != null) {
conflictingURIs.add(leftURI);
}
if (rightURI != null) {
conflictingURIs.add(rightURI);
}
if (originURI != null) {
conflictingURIs.add(originURI);
}
}
}
}
return conflictingURIs;
}
/**
* Returns either the URI of the resource containing the given EObject, or <code>null</code> if none.
*
* @param eObject
* The EObject for which we need a resource URI.
* @return The URI of the resource containing the given EObject, <code>null</code> if none.
*/
private URI resourceURIorNull(EObject eObject) {
if (eObject != null) {
final Resource res = eObject.eResource();
if (res != null) {
return res.getURI();
}
}
return null;
}
/**
* Marks the resources as merged if their URIs are not included in the given set of known
* {@code conflictingURIs}.
*
* @param context
* The current merge context.
* @param resources
* The resources to be marked as merged.
* @param conflictingURIs
* The set of known {@code conflictingURIs}.
* @param subMonitor
* Monitor on which to report progress to the user.
*/
protected void markResourcesAsMerged(IMergeContext context, Set<IResource> resources,
Set<URI> conflictingURIs, SubMonitor subMonitor) {
for (IResource resource : resources) {
if (resource instanceof IFile) {
final IFile iFile = (IFile)resource;
final URI uri = ResourceUtil.createURIFor(iFile);
if (!conflictingURIs.contains(uri)) {
markAsMerged(context, resource, subMonitor);
}
}
}
}
/**
* Mark the resource at the given URI as merged.
*
* @param context
* The current merge context.
* @param resource
* The resource to mark as merged.
* @param subMonitor
* Monitor on which to report progress to the user.
*/
private void markAsMerged(IMergeContext context, IResource resource, SubMonitor subMonitor) {
IDiff diff = context.getDiffTree().getDiff(resource);
if (diff != null) {
try {
context.markAsMerged(diff, true, subMonitor);
} catch (CoreException e) {
EMFCompareIDEUIPlugin.getDefault().log(e);
}
}
}
/**
* Performs a batch merge of the given {@code comparison}.
*
* @param comparison
* The comparison to merge.
* @param subMonitor
* The progress monitor to use.
*/
private void performBatchMerge(Comparison comparison, SubMonitor subMonitor) {
final IBatchMerger merger = new BatchMerger(MERGER_REGISTRY, fromSide(DifferenceSource.RIGHT));
merger.copyAllRightToLeft(comparison.getDifferences(), BasicMonitor.toMonitor(subMonitor));
}
/**
* Delegates the merge of so far non-merged resource additions and deletions and marks all other already
* merged resources as merged.
*
* @param syncModel
* The synchronization model to obtain the storages.
* @param mergeContext
* The merge context.
* @param resourceTracker
* The tracker that tracked already merged file additions and deletions.
* @param subMonitor
* The progress monitor to use.
*/
protected void delegateMergeOfUnmergedResourcesAndMarkDiffsAsMerged(SynchronizationModel syncModel,
IMergeContext mergeContext, ResourceAdditionAndDeletionTracker resourceTracker,
SubMonitor subMonitor) throws CoreException {
// mark already deleted files as merged
for (IFile deletedFile : resourceTracker.getDeletedIFiles()) {
final IDiff diff = mergeContext.getDiffTree().getDiff(deletedFile);
markAsMerged(diff, mergeContext, subMonitor);
}
// for all left storages, delegate the merge of a deletion that has not been performed yet and mark
// all already performed diffs as merged
for (IStorage storage : syncModel.getLeftTraversal().getStorages()) {
final IPath fullPath = ResourceUtil.getFixedPath(storage);
if (fullPath == null) {
EMFCompareIDEUIPlugin.getDefault().getLog().log(new Status(IStatus.WARNING,
EMFCompareIDEUIPlugin.PLUGIN_ID,
EMFCompareIDEUIMessages.getString("EMFResourceMappingMerger.mergeIncomplete"))); //$NON-NLS-1$
} else {
final IDiff diff = mergeContext.getDiffTree().getDiff(fullPath);
if (diff != null) {
if (IDiff.REMOVE == diff.getKind()
&& !resourceTracker.containsRemovedResource(fullPath)) {
merge(diff, mergeContext, subMonitor.newChild(1));
} else {
markAsMerged(diff, mergeContext, subMonitor.newChild(1));
}
}
}
}
// delegate all additions from the right storages that have not been performed yet
// or, if they have been merged, mark the diff as merged
for (IStorage rightStorage : syncModel.getRightTraversal().getStorages()) {
final IPath fullPath = ResourceUtil.getFixedPath(rightStorage);
if (fullPath != null) {
final IDiff diff = mergeContext.getDiffTree().getDiff(fullPath);
if (diff != null && IDiff.ADD == diff.getKind()) {
if (!resourceTracker.containsAddedResource(fullPath)) {
IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(fullPath);
IProject project = file.getProject();
if (project.isAccessible()) {
merge(diff, mergeContext, subMonitor.newChild(1));
} else {
// The project that will contain the resource is not accessible.
// We have to copy the file "manually" from the right side to the left side.
try (InputStream inputStream = rightStorage.getContents();
FileOutputStream outputStream = new FileOutputStream(
ResourceUtil.getAbsolutePath(rightStorage).toFile())) {
ByteStreams.copy(inputStream, outputStream);
} catch (IOException e) {
EMFCompareIDEUIPlugin.getDefault().log(e);
// TODO Should we throw the exception here to interrupt the merge ?
}
}
} else {
markAsMerged(diff, mergeContext, subMonitor.newChild(1));
}
}
}
}
}
/**
* Merges the given {@code diff}.
*
* @param diff
* The difference to be merged.
* @param mergeContext
* The merge context.
* @param subMonitor
* The process monitor to use.
*/
protected void merge(IDiff diff, IMergeContext mergeContext, SubMonitor subMonitor) {
try {
mergeContext.merge(diff, false, subMonitor);
} catch (CoreException e) {
EMFCompareIDEUIPlugin.getDefault().log(e);
}
}
/**
* Marks the given {@code diff} as merged.
*
* @param diff
* The difference to be marked as merge.
* @param mergeContext
* The merge context.
* @param subMonitor
* The progress monitor to use.
*/
protected void markAsMerged(final IDiff diff, IMergeContext mergeContext, SubMonitor subMonitor) {
try {
mergeContext.markAsMerged(diff, true, subMonitor);
} catch (CoreException e) {
EMFCompareIDEUIPlugin.getDefault().log(e);
}
}
/**
* Validates the given array of mappings. Basically, this merger can only operate if all of its target
* mappings are instances of EMFResourceMappings that were properly initialized.
*
* @param mappings
* The mappings we are to validate.
* @return {@link Status#OK_STATUS} if the given mappings are valid, a status describing the failure
* otherwise.
*/
private IStatus validateMappings(ResourceMapping[] mappings) {
for (ResourceMapping mapping : mappings) {
if (mapping instanceof EMFResourceMapping
&& mapping.getModelObject() instanceof SynchronizationModel) {
final SynchronizationModel model = (SynchronizationModel)mapping.getModelObject();
if (model.getDiagnostic().getSeverity() >= Diagnostic.WARNING) {
return BasicDiagnostic.toIStatus(model.getDiagnostic());
}
} else {
return new Status(IStatus.ERROR, EMFCompareIDEUIPlugin.PLUGIN_ID, EMFCompareIDEUIMessages
.getString("EMFResourceMappingMerger.mergeFailedInvalidMapping")); //$NON-NLS-1$
}
}
return Status.OK_STATUS;
}
/**
* Saves the given notifier to disk after a successful merge.
*
* @param notifier
* The notifier.
* @param leftTraversal
* The traversal corresponding to the left side.
* @param rightTraversal
* The traversal corresponding to the right side.
* @param originTraversal
* The traversal corresponding to the common ancestor of both other side. Can be
* <code>null</code>.
*/
protected void save(Notifier notifier, StorageTraversal leftTraversal, StorageTraversal rightTraversal,
StorageTraversal originTraversal) {
if (notifier instanceof ResourceSet) {
ResourceUtil.saveAllResources((ResourceSet)notifier,
ImmutableMap.of(Resource.OPTION_SAVE_ONLY_IF_CHANGED,
Resource.OPTION_SAVE_ONLY_IF_CHANGED_MEMORY_BUFFER),
leftTraversal, rightTraversal, originTraversal);
} else if (notifier instanceof Resource) {
ResourceUtil.saveResource((Resource)notifier,
ImmutableMap.of(Resource.OPTION_SAVE_ONLY_IF_CHANGED,
Resource.OPTION_SAVE_ONLY_IF_CHANGED_MEMORY_BUFFER));
}
}
/**
* Checks whether this 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) {
for (Conflict conflict : comparison.getConflicts()) {
if (conflict.getKind() == ConflictKind.REAL) {
return true;
}
}
return false;
}
/** {@inheritDoc} */
public ISchedulingRule getMergeRule(IMergeContext context) {
final ResourceMapping[] emfMappings = getEMFMappings(context);
final Set<IProject> impactedProjects = new LinkedHashSet<IProject>();
for (ResourceMapping mapping : emfMappings) {
impactedProjects.addAll(Arrays.asList(mapping.getProjects()));
}
return MultiRule.combine(impactedProjects.toArray(new ISchedulingRule[impactedProjects.size()]));
}
/**
* Retrieves all mappings from the given merge context that were created from the EMFModelProvider.
*
* @param context
* The context from which to retrieve resource mappings.
* @return All resource mappings from this context that were created from the EMFModelProvider.
*/
private ResourceMapping[] getEMFMappings(IMergeContext context) {
return context.getScope().getMappings(EMFModelProvider.PROVIDER_ID);
}
/** {@inheritDoc} */
public IStatus validateMerge(IMergeContext mergeContext, IProgressMonitor monitor) {
return Status.OK_STATUS;
}
protected static class ResourceAdditionAndDeletionTracker extends AdapterImpl {
private final Set<String> urisOfAddedResources = new HashSet<String>();
private final Set<String> urisOfDeletedResources = new HashSet<String>();
private final Set<IFile> deletedIFiles = new HashSet<IFile>();
@Override
public void notifyChanged(Notification msg) {
if (!isAdditionOrDeletionOfResourceNotification(msg)) {
return;
}
final Resource newResource = (Resource)msg.getNewValue();
final URI uri = newResource.getURI();
if (uri.isPlatformResource()) {
final String path = uri.toPlatformString(true);
final IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(path));
if (msg.getEventType() == Notification.ADD) {
trackResourceAddition(file);
} else if (msg.getEventType() == Notification.REMOVE) {
trackResourceDeletion(file);
}
}
}
private boolean isAdditionOrDeletionOfResourceNotification(Notification msg) {
return (msg.getEventType() == Notification.ADD || msg.getEventType() == Notification.REMOVE)
&& msg.getNewValue() instanceof Resource;
}
private void trackResourceAddition(IFile file) {
urisOfAddedResources.add(getStringRepresentation(file.getFullPath()));
}
private void trackResourceDeletion(IFile file) {
deletedIFiles.add(file);
urisOfDeletedResources.add(getStringRepresentation(file.getFullPath()));
}
private String getStringRepresentation(IPath path) {
return path.toPortableString();
}
public boolean containsAddedResource(IPath path) {
return urisOfAddedResources.contains(getStringRepresentation(path));
}
public boolean containsRemovedResource(IPath path) {
return urisOfDeletedResources.contains(getStringRepresentation(path));
}
public Set<IFile> getDeletedIFiles() {
return Collections.unmodifiableSet(deletedIFiles);
}
}
}