Refactors the EMFResourceMappingMerger

Change-Id: I894c8f1d4016f278308ed369ec99bbbf8985f6fc
Signed-off-by: Philip Langer <planger@eclipsesource.com>
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/EMFResourceMappingMerger.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/EMFResourceMappingMerger.java
index 21e9ad5..50a3f28 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/EMFResourceMappingMerger.java
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/logical/EMFResourceMappingMerger.java
@@ -7,7 +7,7 @@
  * 
  * Contributors:
  *     Obeo - initial API and implementation
- *     Philip Langer - log messages (bug 461713), bug 465331
+ *     Philip Langer - log messages (bug 461713), bug 465331, refactorings
  *******************************************************************************/
 package org.eclipse.emf.compare.ide.ui.internal.logical;
 
@@ -19,6 +19,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -35,7 +36,6 @@
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.MultiStatus;
-import org.eclipse.core.runtime.NullProgressMonitor;
 import org.eclipse.core.runtime.Path;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.core.runtime.SubMonitor;
@@ -68,6 +68,7 @@
 import org.eclipse.emf.compare.merge.BatchMerger;
 import org.eclipse.emf.compare.merge.IBatchMerger;
 import org.eclipse.emf.compare.merge.IMerger;
+import org.eclipse.emf.compare.merge.IMerger.Registry;
 import org.eclipse.emf.compare.rcp.EMFCompareRCPPlugin;
 import org.eclipse.emf.compare.rcp.internal.extension.impl.EMFCompareBuilderConfigurator;
 import org.eclipse.emf.compare.scope.IComparisonScope;
@@ -96,6 +97,10 @@
  * ResourceMappingMerger. Filtered in the API filters.
  */
 public class EMFResourceMappingMerger implements IResourceMappingMerger {
+
+	/** The merger registry. */
+	private static final Registry MERGER_REGISTRY = EMFCompareRCPPlugin.getDefault().getMergerRegistry();
+
 	/** {@inheritDoc} */
 	public IStatus merge(IMergeContext mergeContext, IProgressMonitor monitor) throws CoreException {
 		final ResourceMapping[] emfMappings = getEMFMappings(mergeContext);
@@ -232,80 +237,162 @@
 
 		final Comparison comparison = builder.build().compare(scope,
 				BasicMonitor.toMonitor(SubMonitor.convert(subMonitor.newChild(1), 10))); // 50%
-		final IMerger.Registry mergerRegistry = EMFCompareRCPPlugin.getDefault().getMergerRegistry();
+
 		if (hasRealConflict(comparison)) {
-			// pre-merge what can be
-			final Graph<Diff> differencesGraph = MergeDependenciesUtil.mapDifferences(comparison,
-					mergerRegistry, true, null);
-			final PruningIterator<Diff> iterator = differencesGraph.breadthFirstIterator();
-			final Monitor emfMonitor = BasicMonitor.toMonitor(subMonitor.newChild(5)); // 100%
-
-			while (iterator.hasNext()) {
-				final Diff next = iterator.next();
-				if (hasConflict(ConflictKind.REAL).apply(next)) {
-					iterator.prune();
-				} else {
-					if (next.getState() != DifferenceState.MERGED) {
-						final IMerger merger = mergerRegistry.getHighestRankingMerger(next);
-						merger.copyRightToLeft(next, emfMonitor);
-					}
-				}
-			}
-
+			performPreMerge(comparison, subMonitor.newChild(5)); // 100 %
 			save(scope.getLeft());
-
 			failingMappings.add(mapping);
 		} else {
-			MarkNewResourceAsMergedListener listener = new MarkNewResourceAsMergedListener(mergeContext);
-			TrackAddedAndRemovedResourcesListener resourceTracker = new TrackAddedAndRemovedResourcesListener();
+			final ResourceAdditionAndDeletionTracker resourceTracker = new ResourceAdditionAndDeletionTracker();
 			try {
-				scope.getLeft().eAdapters().add(listener);
 				scope.getLeft().eAdapters().add(resourceTracker);
-				final IBatchMerger merger = new BatchMerger(mergerRegistry, fromSide(DifferenceSource.RIGHT));
-				merger.copyAllRightToLeft(comparison.getDifferences(), BasicMonitor.toMonitor(subMonitor
-						.newChild(3))); // 50%
+				performBatchMerge(comparison, subMonitor.newChild(3)); // 80%
 				save(scope.getLeft());
-
-				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)) {
-								mergeContext.merge(diff, false, subMonitor.newChild(1));
-							} else {
-								mergeContext.markAsMerged(diff, true, subMonitor.newChild(1));
-							}
-						}
-					}
-				}
-
-				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()
-								&& !resourceTracker.containsAddedResource(fullPath)) {
-							mergeContext.merge(diff, false, subMonitor.newChild(1));
-						}
-					}
-				}
-
+				delegateMergeOfUnmergedResourcesAndMarkDiffsAsMerged(syncModel, mergeContext,
+						resourceTracker, subMonitor.newChild(2)); // 100%
 			} finally {
-				scope.getLeft().eAdapters().remove(listener);
 				scope.getLeft().eAdapters().remove(resourceTracker);
 			}
 		}
+
 		subMonitor.setWorkRemaining(0);
 	}
 
 	/**
+	 * 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.
+	 */
+	private void performPreMerge(Comparison comparison, SubMonitor subMonitor) {
+		final Graph<Diff> differencesGraph = MergeDependenciesUtil.mapDifferences(comparison,
+				MERGER_REGISTRY, true, null);
+		final PruningIterator<Diff> iterator = differencesGraph.breadthFirstIterator();
+		final Monitor emfMonitor = BasicMonitor.toMonitor(subMonitor);
+
+		while (iterator.hasNext()) {
+			final Diff next = iterator.next();
+			if (hasConflict(ConflictKind.REAL).apply(next)) {
+				iterator.prune();
+			} else if (next.getState() != DifferenceState.MERGED) {
+				final IMerger merger = MERGER_REGISTRY.getHighestRankingMerger(next);
+				merger.copyRightToLeft(next, emfMonitor);
+			}
+		}
+	}
+
+	/**
+	 * 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.
+	 */
+	private 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
+		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()
+						&& !resourceTracker.containsAddedResource(fullPath)) {
+					merge(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.
+	 */
+	private 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.
+	 */
+	private 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.
 	 * 
@@ -392,71 +479,61 @@
 		return Status.OK_STATUS;
 	}
 
-	private static class MarkNewResourceAsMergedListener extends AdapterImpl {
-		private final IMergeContext mergeContext;
+	private static class ResourceAdditionAndDeletionTracker extends AdapterImpl {
 
-		public MarkNewResourceAsMergedListener(IMergeContext mergeContext) {
-			this.mergeContext = mergeContext;
-		}
+		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 (msg.getEventType() == Notification.ADD && msg.getNewValue() instanceof Resource) {
-				Resource newResource = (Resource)msg.getNewValue();
-				URI uri = newResource.getURI();
-				if (uri.isPlatformResource()) {
-					String path = uri.toPlatformString(true);
-					IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(path));
-					IDiff diff = mergeContext.getDiffTree().getDiff(file);
-					try {
-						mergeContext.markAsMerged(diff, true, new NullProgressMonitor());
-					} catch (CoreException e) {
-						EMFCompareIDEUIPlugin.getDefault().log(e);
-					}
-				}
-			}
-		}
-	}
-
-	private static class TrackAddedAndRemovedResourcesListener extends AdapterImpl {
-
-		private final Set<String> addedResources = new HashSet<String>();
-
-		private final Set<String> removedResources = new HashSet<String>();
-
-		@Override
-		public void notifyChanged(Notification msg) {
-			int notificationType = msg.getEventType();
-			if (notificationType != Notification.ADD && notificationType != Notification.REMOVE) {
+			if (!isAdditionOrDeletionOfResourceNotification(msg)) {
 				return;
 			}
-			if (msg.getNewValue() instanceof Resource) {
-				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));
-					final String stringRepresentation = getStringRepresentation(file.getFullPath());
-					if (msg.getEventType() == Notification.ADD) {
-						addedResources.add(stringRepresentation);
-					} else if (msg.getEventType() == Notification.REMOVE) {
-						removedResources.add(stringRepresentation);
-					}
+
+			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 addedResources.contains(getStringRepresentation(path));
+			return urisOfAddedResources.contains(getStringRepresentation(path));
 		}
 
 		public boolean containsRemovedResource(IPath path) {
-			return removedResources.contains(getStringRepresentation(path));
+			return urisOfDeletedResources.contains(getStringRepresentation(path));
 		}
 
+		public Set<IFile> getDeletedIFiles() {
+			return Collections.unmodifiableSet(deletedIFiles);
+		}
 	}
 }