[462884] Adds and integrates MergeContainedNonConflictingAction into UI

Also incorporates filters that may potentially be applied to tree nodes.
Filtered tree nodes that represent differences will not be included in
the merge action.

Bug: 462884
Change-Id: Ie873dab03eb8d53a70ff70146680f292ef864e52
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/ide_ui_messages.properties b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/ide_ui_messages.properties
index a1c3485..17457cd 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/ide_ui_messages.properties
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/ide_ui_messages.properties
@@ -7,7 +7,7 @@
 # 
 # Contributors:
 #     Obeo - initial API and implementation
-#     Philip Langer - log entries for model merge
+#     Philip Langer - log entries for model merge, bug 462884
 #     Stefan Dirix - bug 456699
 #     Michael Borkowski - bug 467191
 ################################################################################
@@ -31,12 +31,16 @@
 previous.diff.tooltip = Previous Difference
 accept.change.tooltip = Accept Change
 accept.all.changes.tooltip = Accept All Non-Conflicting Changes
+accept.contained.changes.tooltip = Accept Contained Non-Conflicting Changes
 reject.change.tooltip = Reject Change
 reject.all.changes.tooltip = Reject All Non-Conflicting Changes
+reject.contained.changes.tooltip = Reject Contained Non-Conflicting Changes
 merged.to.left.tooltip = Copy Current Change From Right To Left
 merged.to.right.tooltip = Copy Current Change From Left To Right
 merged.all.to.left.tooltip = Copy All Non-Conflicting Changes From Right To Left
 merged.all.to.right.tooltip = Copy All Non-Conflicting Changes From Left To Right
+merged.contained.to.left.tooltip = Apply Contained Non-conflicting Right Changes on the Left-hand Side
+merged.contained.to.right.tooltip = Apply Contained Non-conflicting Left Changes on the Right-hand Side
 dropdown.left.to.right.text = Show consequences of merging from left to right
 dropdown.right.to.left.text = Show consequences of merging from right to left
 dropdown.accept.text = Show consequences of accepting change
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/EMFCompareStructureMergeViewer.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/EMFCompareStructureMergeViewer.java
index 8f8e4f8..e7fedbe 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/EMFCompareStructureMergeViewer.java
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/EMFCompareStructureMergeViewer.java
@@ -8,6 +8,7 @@
  * Contributors:
  *     Obeo - initial API and implementation
  *     Michael Borkowski - bug 467191
+ *     Philip Langer - bug 462884
  *******************************************************************************/
 package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer;
 
@@ -73,6 +74,7 @@
 import org.eclipse.emf.compare.EMFCompare;
 import org.eclipse.emf.compare.EMFCompare.Builder;
 import org.eclipse.emf.compare.Match;
+import org.eclipse.emf.compare.MatchResource;
 import org.eclipse.emf.compare.command.ICompareCopyCommand;
 import org.eclipse.emf.compare.domain.ICompareEditingDomain;
 import org.eclipse.emf.compare.domain.impl.EMFCompareEditingDomain;
@@ -96,6 +98,7 @@
 import org.eclipse.emf.compare.ide.ui.internal.progress.JobProgressMonitorWrapper;
 import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.EMFCompareStructureMergeViewerContentProvider.FetchListener;
 import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.actions.MergeAction;
+import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.actions.MergeContainedNonConflictingAction;
 import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.provider.TreeCompareInputAdapterFactory;
 import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.provider.TreeNodeCompareInput;
 import org.eclipse.emf.compare.ide.ui.internal.util.CompareHandlerService;
@@ -404,7 +407,7 @@
 	 *            the context menu to fill.
 	 */
 	private void fillContextMenu(IMenuManager manager) {
-		if (!isDiffSelected()) {
+		if (!isOneMergeableItemSelected()) {
 			return;
 		}
 		boolean leftEditable = getCompareConfiguration().isLeftEditable();
@@ -418,21 +421,45 @@
 		if (rightEditable || leftEditable) {
 			for (MergeMode mode : modes) {
 				IMerger.Registry mergerRegistry = EMFCompareRCPPlugin.getDefault().getMergerRegistry();
-				MergeAction mergeAction = new MergeAction(getCompareConfiguration().getEditingDomain(),
-						mergerRegistry, mode, leftEditable, rightEditable, navigatable,
-						(IStructuredSelection)getSelection());
-				manager.add(mergeAction);
+				if (isOneDiffSelected()) {
+					MergeAction mergeAction = new MergeAction(getCompareConfiguration().getEditingDomain(),
+							mergerRegistry, mode, leftEditable, rightEditable, navigatable,
+							(IStructuredSelection)getSelection());
+					manager.add(mergeAction);
+				} else if (isOneMatchOrResourceMatchSelected()) {
+					final Predicate<TreeNode> filterPredicate = new Predicate<TreeNode>() {
+						public boolean apply(TreeNode input) {
+							return input != null
+									&& JFaceUtil.isFiltered(getViewer(), input, input.getParent());
+						}
+					};
+					MergeContainedNonConflictingAction mergeAction = new MergeContainedNonConflictingAction(
+							getCompareConfiguration().getEditingDomain(), mergerRegistry, mode, leftEditable,
+							rightEditable, navigatable, (IStructuredSelection)getSelection(), filterPredicate);
+					manager.add(mergeAction);
+				}
 			}
 		}
 	}
 
 	/**
-	 * Check if the item selected in this viewer is a single Diff.
+	 * Check if the item selected in this viewer is mergeable; that is, if a {@link Diff}, a {@link Match}, or
+	 * {@link MatchResource} is selected.
 	 * 
-	 * @return true if the item selected is a single Diff, false otherwise.
+	 * @return true if the item selected is mergeable, false otherwise.
 	 */
-	private boolean isDiffSelected() {
-		ISelection selection = getSelection();
+	private boolean isOneMergeableItemSelected() {
+		return isOneDiffSelected() || isOneMatchOrResourceMatchSelected();
+	}
+
+	/**
+	 * Specifies whether the a {@link Match} or a {@link MatchResource} is currently selected in this viewer.
+	 * 
+	 * @return <code>true</code> if an instance of a {@link Match} or a {@link MatchResource} is selected,
+	 *         <code>false</code> otherwise.
+	 */
+	private boolean isOneDiffSelected() {
+		final ISelection selection = getSelection();
 		if (selection instanceof IStructuredSelection && ((IStructuredSelection)selection).size() == 1) {
 			Object element = ((IStructuredSelection)selection).getFirstElement();
 			if (getDataOfTreeNodeOfAdapter(element) instanceof Diff) {
@@ -443,6 +470,35 @@
 	}
 
 	/**
+	 * Specifies whether the a {@link Match} or a {@link MatchResource} is currently selected in this viewer.
+	 * 
+	 * @return <code>true</code> if an instance of a {@link Match} or a {@link MatchResource} is selected,
+	 *         <code>false</code> otherwise.
+	 */
+	private boolean isOneMatchOrResourceMatchSelected() {
+		final ISelection selection = getSelection();
+		if (selection instanceof IStructuredSelection && ((IStructuredSelection)selection).size() == 1) {
+			Object element = ((IStructuredSelection)selection).getFirstElement();
+			if (isMatchOrMatchResource(getDataOfTreeNodeOfAdapter(element))) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Specifies whether the given {@code eObject} is a {@link Match} or a {@link MatchResource}.
+	 * 
+	 * @param eObject
+	 *            The EObject to check.
+	 * @return <code>true</code> if it is an instance a {@link Match} or a {@link MatchResource},
+	 *         <code>false</code> otherwise.
+	 */
+	private boolean isMatchOrMatchResource(EObject eObject) {
+		return eObject instanceof Match || eObject instanceof MatchResource;
+	}
+
+	/**
 	 * {@inheritDoc}
 	 * 
 	 * @see org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.AbstractViewerWrapper#preHookCreateControlAndViewer()
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/actions/MergeAction.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/actions/MergeAction.java
index 930fe3a..43d2b00 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/actions/MergeAction.java
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/actions/MergeAction.java
@@ -51,7 +51,7 @@
  */
 public class MergeAction extends BaseSelectionListenerAction {
 
-	private static final Function<? super Adapter, ? extends Notifier> ADAPTER__TARGET = new Function<Adapter, Notifier>() {
+	protected static final Function<? super Adapter, ? extends Notifier> ADAPTER__TARGET = new Function<Adapter, Notifier>() {
 		public Notifier apply(Adapter adapter) {
 			return adapter.getTarget();
 		}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/actions/MergeAllNonConflictingAction.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/actions/MergeAllNonConflictingAction.java
index 46c0f85..9a26fad 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/actions/MergeAllNonConflictingAction.java
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/actions/MergeAllNonConflictingAction.java
@@ -29,8 +29,7 @@
 import org.eclipse.ui.plugin.AbstractUIPlugin;
 
 /**
- * Abstract Action that manages a merge of a all non-conflicting difference in case of both sides of the
- * comparison are editable.
+ * Action that manages a merge of a all non-conflicting difference.
  * 
  * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
  * @since 3.0
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/actions/MergeContainedNonConflictingAction.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/actions/MergeContainedNonConflictingAction.java
new file mode 100644
index 0000000..a913990
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/actions/MergeContainedNonConflictingAction.java
@@ -0,0 +1,139 @@
+/*******************************************************************************
+ * Copyright (c) 2015 EclipseSource Munich 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:
+ *     Philip Langer - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.actions;
+
+import static com.google.common.collect.Iterables.concat;
+import static com.google.common.collect.Iterables.filter;
+import static com.google.common.collect.Iterables.transform;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
+
+import java.util.List;
+
+import org.eclipse.compare.INavigatable;
+import org.eclipse.emf.common.notify.Adapter;
+import org.eclipse.emf.common.notify.Notifier;
+import org.eclipse.emf.common.util.TreeIterator;
+import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.domain.ICompareEditingDomain;
+import org.eclipse.emf.compare.domain.IMergeRunnable;
+import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIMessages;
+import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIPlugin;
+import org.eclipse.emf.compare.internal.merge.MergeMode;
+import org.eclipse.emf.compare.merge.IMerger.Registry;
+import org.eclipse.emf.compare.rcp.ui.structuremergeviewer.groups.IDifferenceGroup;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.edit.tree.TreeNode;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+
+/**
+ * Action that manages a merge of a contained non-conflicting difference in case the selection is a resource
+ * match or a model element match.
+ * 
+ * @author Philip Langer <planger@eclipsesource.com>
+ * @since 4.1
+ */
+public class MergeContainedNonConflictingAction extends MergeAction {
+
+	/**
+	 * Function for transforming a tree node into all non-filtered leaf differences.
+	 */
+	private static Function<TreeNode, Iterable<Diff>> treeNodesToLeafDiffs(
+			final Predicate<TreeNode> isFiltered) {
+		return new Function<TreeNode, Iterable<Diff>>() {
+			public Iterable<Diff> apply(TreeNode input) {
+				final TreeIterator<EObject> allContents = input.eAllContents();
+				final Builder<Diff> builder = new ImmutableList.Builder<Diff>();
+				while (allContents.hasNext()) {
+					final EObject eObject = allContents.next();
+					if (eObject instanceof TreeNode) {
+						final TreeNode treeNode = (TreeNode)eObject;
+						final EObject data = IDifferenceGroup.TREE_NODE_DATA.apply(treeNode);
+						if (data instanceof Diff && !isFiltered.apply(treeNode)) {
+							builder.add((Diff)data);
+						}
+					}
+				}
+				return builder.build();
+			}
+		};
+	}
+
+	/**
+	 * The predicate to determine whether a tree node is filtered.
+	 */
+	private Predicate<TreeNode> isFiltered;
+
+	/**
+	 * {@inheritDoc}
+	 * 
+	 * @param isFiltered
+	 *            The predicate to use for determining whether a {@link TreeNode} is filtered.
+	 */
+	public MergeContainedNonConflictingAction(ICompareEditingDomain editingDomain, Registry mergerRegistry,
+			MergeMode mode, boolean isLeftEditable, boolean isRightEditable, INavigatable navigatable,
+			IStructuredSelection selection, Predicate<TreeNode> isFiltered) {
+		super(editingDomain, mergerRegistry, mode, isLeftEditable, isRightEditable, navigatable);
+		this.isFiltered = isFiltered;
+		updateSelection(selection);
+	}
+
+	@Override
+	protected void initToolTipAndImage(MergeMode mode) {
+		switch (mode) {
+			case LEFT_TO_RIGHT:
+				setText(EMFCompareIDEUIMessages.getString("merged.contained.to.right.tooltip")); //$NON-NLS-1$
+				setToolTipText(EMFCompareIDEUIMessages.getString("merged.contained.to.right.tooltip")); //$NON-NLS-1$
+				setImageDescriptor(AbstractUIPlugin.imageDescriptorFromPlugin(
+						EMFCompareIDEUIPlugin.PLUGIN_ID, "icons/full/toolb16/merge_all_to_right.gif")); //$NON-NLS-1$
+				break;
+			case RIGHT_TO_LEFT:
+				setText(EMFCompareIDEUIMessages.getString("merged.contained.to.left.tooltip")); //$NON-NLS-1$
+				setToolTipText(EMFCompareIDEUIMessages.getString("merged.contained.to.left.tooltip")); //$NON-NLS-1$
+				setImageDescriptor(AbstractUIPlugin.imageDescriptorFromPlugin(
+						EMFCompareIDEUIPlugin.PLUGIN_ID, "icons/full/toolb16/merge_all_to_left.gif")); //$NON-NLS-1$
+				break;
+			case ACCEPT:
+				setText(EMFCompareIDEUIMessages.getString("accept.contained.changes.tooltip")); //$NON-NLS-1$
+				setToolTipText(EMFCompareIDEUIMessages.getString("accept.contained.changes.tooltip")); //$NON-NLS-1$
+				setImageDescriptor(AbstractUIPlugin.imageDescriptorFromPlugin(
+						EMFCompareIDEUIPlugin.PLUGIN_ID, "icons/full/toolb16/accept_all_changes.gif")); //$NON-NLS-1$
+				break;
+			case REJECT:
+				setText(EMFCompareIDEUIMessages.getString("reject.contained.changes.tooltip")); //$NON-NLS-1$
+				setToolTipText(EMFCompareIDEUIMessages.getString("reject.contained.changes.tooltip")); //$NON-NLS-1$
+				setImageDescriptor(AbstractUIPlugin.imageDescriptorFromPlugin(
+						EMFCompareIDEUIPlugin.PLUGIN_ID, "icons/full/toolb16/reject_all_changes.gif")); //$NON-NLS-1$
+				break;
+			default:
+				throw new IllegalStateException();
+		}
+	}
+
+	@Override
+	protected IMergeRunnable createMergeRunnable(MergeMode mode, boolean isLeftEditable,
+			boolean isRightEditable) {
+		return new MergeNonConflictingRunnable(isLeftEditable, isRightEditable, mode);
+	}
+
+	@Override
+	protected Iterable<Diff> getSelectedDifferences(IStructuredSelection selection) {
+		final List<?> selectedObjects = selection.toList();
+		final Iterable<Adapter> selectedAdapters = filter(selectedObjects, Adapter.class);
+		final Iterable<Notifier> selectedNotifiers = transform(selectedAdapters, ADAPTER__TARGET);
+		final Iterable<TreeNode> selectedTreeNodes = filter(selectedNotifiers, TreeNode.class);
+		return concat(transform(selectedTreeNodes, treeNodesToLeafDiffs(isFiltered)));
+	}
+}