Merge "Bug 512529 - Papyrus Viewpoint must be supported"
diff --git a/plugins/compare/bundles/org.eclipse.papyrus.compare.diagram.ide.ui/src/org/eclipse/papyrus/compare/diagram/ide/ui/contentmergeviewer/PapyrusTreeContentMergeViewer.java b/plugins/compare/bundles/org.eclipse.papyrus.compare.diagram.ide.ui/src/org/eclipse/papyrus/compare/diagram/ide/ui/contentmergeviewer/PapyrusTreeContentMergeViewer.java
index 597e7a1..70cd426 100644
--- a/plugins/compare/bundles/org.eclipse.papyrus.compare.diagram.ide.ui/src/org/eclipse/papyrus/compare/diagram/ide/ui/contentmergeviewer/PapyrusTreeContentMergeViewer.java
+++ b/plugins/compare/bundles/org.eclipse.papyrus.compare.diagram.ide.ui/src/org/eclipse/papyrus/compare/diagram/ide/ui/contentmergeviewer/PapyrusTreeContentMergeViewer.java
@@ -7,18 +7,18 @@
  * 
  * Contributors:
  *     Stefan Dirix - initial API and implementation
- *     Philip Langer - introduce caching
+ *     Philip Langer - introduce caching and improve selection
  *******************************************************************************/
 package org.eclipse.papyrus.compare.diagram.ide.ui.contentmergeviewer;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
 
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
@@ -195,6 +195,9 @@
 			leftContentComputations = null;
 			rightContentComputations = null;
 			cachedMapForSelection = null;
+		} else {
+			getLeftMergeViewer().refresh();
+			getRightMergeViewer().refresh();
 		}
 
 		IMergeViewerItem leftInitialItem = null;
@@ -262,13 +265,13 @@
 		Object itemValue = item.getSideValue(side);
 		IMergeViewerItem result;
 		if (cachedMapForSelection == null) {
-			cachedMapForSelection = new HashMap<>();
+			cachedMapForSelection = Maps.newHashMap();
 			result = null;
 		} else {
 			result = cachedMapForSelection.get(itemValue);
 		}
 
-		if (result == null) {
+		if (result == null && itemValue != null) {
 			LinkedList<AbstractContentFunction> contentComputations;
 
 			switch (side) {
@@ -302,9 +305,55 @@
 				contentComputations.addAll(0, containerComputations);
 			}
 
-			// compute item
-			while (!contentComputations.isEmpty()) {
-				result = contentComputations.removeFirst().apply(itemValue, containers);
+			result = computeItemToBeSelected(itemValue, containers, contentComputations);
+		}
+
+		return result;
+	}
+
+	/**
+	 * Computes the item to be selected for the given item value, taking into account the containers, using
+	 * the content computations.
+	 * 
+	 * @param itemValue
+	 *            the item for which to base the selection.
+	 * @param containers
+	 *            the containers for that item.
+	 * @param contentComputations
+	 *            the computations for computing content.
+	 * @return the item to be selected.
+	 */
+	private IMergeViewerItem computeItemToBeSelected(Object itemValue, Collection<Object> containers,
+			LinkedList<AbstractContentFunction> contentComputations) {
+		// We try to limit the amount of time we spend looking for selection.
+		// Sometimes the object will not be present in the tree at all,
+		// causing the whole tree to be visited.
+		// So we keep track of which of the contains have not been visited,
+		// removing any containers for which there is already a selection computed.
+		IMergeViewerItem result = null;
+		Collection<Object> unvistedContainers = Sets.newHashSet(containers);
+		unvistedContainers.removeAll(cachedMapForSelection.keySet());
+		long start = System.currentTimeMillis();
+		while (!contentComputations.isEmpty()) {
+			AbstractContentFunction contentFunction = contentComputations.removeFirst();
+
+			// If we've visited all the containers and have been computing for more the 2 seconds,
+			// stop looking and select the container instead.
+			unvistedContainers.remove(contentFunction.getValue());
+			if (unvistedContainers.isEmpty() && (System.currentTimeMillis() - start) > 2 * 1000) {
+				break;
+			}
+
+			result = contentFunction.apply(itemValue, containers);
+			if (result != null) {
+				break;
+			}
+		}
+
+		// If we haven't found the selection, try to find a selection for the nearest container.
+		if (result == null) {
+			for (Object container : containers) {
+				result = cachedMapForSelection.get(container);
 				if (result != null) {
 					break;
 				}
@@ -356,7 +405,7 @@
 	private Collection<Object> getContainers(Object eObject) {
 		Collection<Object> containers;
 		if (eObject instanceof EObject) {
-			containers = new HashSet<>();
+			containers = Sets.newLinkedHashSet();
 			for (EObject container = ((EObject)eObject).eContainer(); container != null; container = container
 					.eContainer()) {
 				containers.add(container);
@@ -396,7 +445,7 @@
 		/**
 		 * The selections being cached.
 		 * 
-		 * @see PapyrusTreeContentMergeViewer#selections
+		 * @see PapyrusTreeContentMergeViewer#cachedMapForSelection
 		 */
 		protected final Map<Object, IMergeViewerItem> selections;
 
diff --git a/plugins/compare/bundles/org.eclipse.papyrus.compare.diagram.ide.ui/src/org/eclipse/papyrus/compare/diagram/ide/ui/contentmergeviewer/provider/PapyrusContextTester.java b/plugins/compare/bundles/org.eclipse.papyrus.compare.diagram.ide.ui/src/org/eclipse/papyrus/compare/diagram/ide/ui/contentmergeviewer/provider/PapyrusContextTester.java
index a10b688..4268fc2 100644
--- a/plugins/compare/bundles/org.eclipse.papyrus.compare.diagram.ide.ui/src/org/eclipse/papyrus/compare/diagram/ide/ui/contentmergeviewer/provider/PapyrusContextTester.java
+++ b/plugins/compare/bundles/org.eclipse.papyrus.compare.diagram.ide.ui/src/org/eclipse/papyrus/compare/diagram/ide/ui/contentmergeviewer/provider/PapyrusContextTester.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2016 EclipseSource Muenchen GmbH and others.
+ * Copyright (c) 2016, 2017 EclipseSource Muenchen GmbH 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
@@ -10,11 +10,13 @@
  *******************************************************************************/
 package org.eclipse.papyrus.compare.diagram.ide.ui.contentmergeviewer.provider;
 
+import static org.eclipse.papyrus.compare.diagram.ide.ui.internal.context.PapyrusContextUtil.isPapyrusContext;
+
 import java.util.Map;
+import java.util.WeakHashMap;
 
 import org.eclipse.emf.compare.Comparison;
 import org.eclipse.emf.compare.adapterfactory.context.AbstractContextTester;
-import org.eclipse.papyrus.compare.diagram.ide.ui.internal.context.PapyrusContextUtil;
 
 /**
  * Indicates whether we are in a Papyrus context.
@@ -24,12 +26,22 @@
 public class PapyrusContextTester extends AbstractContextTester {
 
 	/**
+	 * A weak cache of comparisons that have been already been tested.
+	 */
+	private final Map<Comparison, Boolean> cache = new WeakHashMap<>();
+
+	/**
 	 * {@inheritDoc}
 	 */
 	public boolean apply(Map<Object, Object> context) {
 		Comparison comparison = getComparison(context);
-		if (context != null) {
-			return PapyrusContextUtil.isPapyrusContext(comparison);
+		if (comparison != null) {
+			Boolean result = cache.get(comparison);
+			if (result == null) {
+				result = Boolean.valueOf(isPapyrusContext(comparison));
+				cache.put(comparison, result);
+			}
+			return result.booleanValue();
 		}
 		return false;
 	}
diff --git a/plugins/compare/bundles/org.eclipse.papyrus.compare.diagram.ide.ui/src/org/eclipse/papyrus/compare/diagram/ide/ui/internal/context/PapyrusContextUtil.java b/plugins/compare/bundles/org.eclipse.papyrus.compare.diagram.ide.ui/src/org/eclipse/papyrus/compare/diagram/ide/ui/internal/context/PapyrusContextUtil.java
index ffee9ad..75ba760 100644
--- a/plugins/compare/bundles/org.eclipse.papyrus.compare.diagram.ide.ui/src/org/eclipse/papyrus/compare/diagram/ide/ui/internal/context/PapyrusContextUtil.java
+++ b/plugins/compare/bundles/org.eclipse.papyrus.compare.diagram.ide.ui/src/org/eclipse/papyrus/compare/diagram/ide/ui/internal/context/PapyrusContextUtil.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2016 EclipseSource Muenchen GmbH and others.
+ * Copyright (c) 2016, 2017 EclipseSource Muenchen GmbH 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
@@ -36,11 +36,14 @@
  */
 public final class PapyrusContextUtil {
 
+	/** The value ".di". */
+	private static final String DI_FILE_EXTENSION = "." + DiModel.DI_FILE_EXTENSION; //$NON-NLS-1$
+
 	/** Predicate specifying whether the given URI string ends with the Papyrus DI file extension. */
 	private static final Predicate<String> ENDS_WITH_PAPYRUS_EXTENSION = new Predicate<String>() {
 		public boolean apply(String input) {
 			if (input != null) {
-				return input.endsWith(DiModel.DI_FILE_EXTENSION);
+				return input.endsWith(DI_FILE_EXTENSION);
 			}
 			return false;
 		}