| /******************************************************************************* |
| * 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); |
| } |
| } |
| } |