Bug 41353 - [launching] Launch config templates
diff --git a/org.eclipse.debug.core/core/org/eclipse/debug/core/ILaunchConfiguration.java b/org.eclipse.debug.core/core/org/eclipse/debug/core/ILaunchConfiguration.java
index 50a5395..53050c1 100644
--- a/org.eclipse.debug.core/core/org/eclipse/debug/core/ILaunchConfiguration.java
+++ b/org.eclipse.debug.core/core/org/eclipse/debug/core/ILaunchConfiguration.java
@@ -663,6 +663,20 @@
 	public ILaunchConfiguration getTemplate() throws CoreException;
 	
 	/**
+	 * Returns a map of attributes in this launch configuration that are
+	 * different from the specified attributes. Returns an empty map if
+	 * this launch configuration contains equivalent attributes. A <code>null</code>
+	 * value is returned for attributes not contained in this launch configuration.
+	 * 
+	 * @param attributes to compare to
+	 * @return a map of attributes in this launch configuration that 
+	 *  are different from the specified attributes
+	 * @throws CoreException if an exception occurs while comparing
+	 * @since 3.6
+	 */
+	public Map findDifferences(Map attributes) throws CoreException;
+	
+	/**
 	 * Returns whether this configuration is a template.
 	 * 
 	 * @return whether this configuration is a template
diff --git a/org.eclipse.debug.core/core/org/eclipse/debug/internal/core/LaunchConfiguration.java b/org.eclipse.debug.core/core/org/eclipse/debug/internal/core/LaunchConfiguration.java
index acf5a9f..4e5c8a7 100644
--- a/org.eclipse.debug.core/core/org/eclipse/debug/internal/core/LaunchConfiguration.java
+++ b/org.eclipse.debug.core/core/org/eclipse/debug/internal/core/LaunchConfiguration.java
@@ -17,10 +17,13 @@
 import java.io.StringReader;
 import java.net.URI;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.Map.Entry;
 
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
@@ -1017,6 +1020,26 @@
 		}
 		return CONFIGURATION;
 	}
+
+	/* (non-Javadoc)
+	 * @see org.eclipse.debug.core.ILaunchConfiguration#findDifferences(java.util.Map)
+	 */
+	public Map findDifferences(Map attributes) throws CoreException {
+		Map result = new HashMap();
+		Set entries = attributes.entrySet();
+		Iterator iterator = entries.iterator();
+		LaunchConfigurationInfo info = getInfo();
+		while (iterator.hasNext()) {
+			Entry entry = (Entry) iterator.next();
+			String key = (String) entry.getKey();
+			Object attr1 = entry.getValue();
+			Object attr2 = info.getObjectAttribute(key);
+			if (!LaunchConfigurationInfo.compareAttribute(key, attr1, attr2)) {
+				result.put(key, attr2);
+			}
+		}
+		return result;
+	}
 	
 }
 
diff --git a/org.eclipse.debug.core/core/org/eclipse/debug/internal/core/LaunchConfigurationInfo.java b/org.eclipse.debug.core/core/org/eclipse/debug/internal/core/LaunchConfigurationInfo.java
index 6fc01f3..5bbac3f 100644
--- a/org.eclipse.debug.core/core/org/eclipse/debug/internal/core/LaunchConfigurationInfo.java
+++ b/org.eclipse.debug.core/core/org/eclipse/debug/internal/core/LaunchConfigurationInfo.java
@@ -79,6 +79,11 @@
 	private boolean fIsTemplate = false;
 	
 	/**
+	 * Static access to the launch manager.
+	 */
+	private static LaunchManager fgLaunchManager = (LaunchManager)DebugPlugin.getDefault().getLaunchManager(); 
+	
+	/**
 	 * Whether running on Sun 1.4 VM - see bug 110215
 	 */
 	private static boolean fgIsSun14x = false;
@@ -260,6 +265,16 @@
 	}
 	
 	/**
+	 * Returns the raw object from the attribute table or <code>null</code> if none.
+	 * 
+	 * @param key attribute key
+	 * @return raw attribute value 
+	 */
+	protected Object getObjectAttribute(String key) {
+		return getAttributeTable().get(key);
+	}
+	
+	/**
 	 * Returns the <code>java.util.Map</code> attribute with the given key or
 	 * the given default value if undefined.
 	 * 
@@ -736,38 +751,15 @@
 	 * @return whether the two attribute maps are equal
 	 */
 	protected boolean compareAttributes(TreeMap map1, TreeMap map2) {
-		LaunchManager manager = (LaunchManager)DebugPlugin.getDefault().getLaunchManager();
 		if (map1.size() == map2.size()) {
 			Iterator attributes = map1.keySet().iterator();
 			while (attributes.hasNext()) {
 				String key = (String)attributes.next();
 				Object attr1 = map1.get(key);
 				Object attr2 = map2.get(key);
-				if (attr2 == null) {
+				if (!compareAttribute(key, attr1, attr2)) {
 					return false;
 				}
-				Comparator comp = manager.getComparator(key);
-				if (comp == null) {
-					if (fgIsSun14x) {
-						if(attr2 instanceof String & attr1 instanceof String) {
-							// this is a hack for bug 110215, on SUN 1.4.x, \r
-							// is stripped off when the stream is written to the
-							// DOM
-							// this is not the case for 1.5.x, so to be safe we
-							// are stripping \r off all strings before we
-							// compare for equality
-							attr1 = ((String)attr1).replaceAll("\\r", ""); //$NON-NLS-1$ //$NON-NLS-2$
-							attr2 = ((String)attr2).replaceAll("\\r", ""); //$NON-NLS-1$ //$NON-NLS-2$
-						}
-					}
-					if (!attr1.equals(attr2)) {
-						return false;
-					}
-				} else {
-					if (comp.compare(attr1, attr2) != 0) {
-						return false;
-					}
-				}
 			}
 			return true;	
 		}
@@ -775,6 +767,43 @@
 	}
 	
 	/**
+	 * Returns whether the two attributes are equal, considering comparator extensions.
+	 * 
+	 * @param key attribute key
+	 * @param attr1 attribute value
+	 * @param attr2 attribute value to compare to, possibly <code>null</code>
+	 * @return whether equivalent
+	 */
+	protected static boolean compareAttribute(String key, Object attr1, Object attr2) {
+		if (attr2 == null) {
+			return false;
+		}
+		Comparator comp = fgLaunchManager.getComparator(key);
+		if (comp == null) {
+			if (fgIsSun14x) {
+				if(attr2 instanceof String & attr1 instanceof String) {
+					// this is a hack for bug 110215, on SUN 1.4.x, \r
+					// is stripped off when the stream is written to the
+					// DOM
+					// this is not the case for 1.5.x, so to be safe we
+					// are stripping \r off all strings before we
+					// compare for equality
+					attr1 = ((String)attr1).replaceAll("\\r", ""); //$NON-NLS-1$ //$NON-NLS-2$
+					attr2 = ((String)attr2).replaceAll("\\r", ""); //$NON-NLS-1$ //$NON-NLS-2$
+				}
+			}
+			if (!attr1.equals(attr2)) {
+				return false;
+			}
+		} else {
+			if (comp.compare(attr1, attr2) != 0) {
+				return false;
+			}
+		}
+		return true;
+	}
+	
+	/**
 	 * @see java.lang.Object#hashCode()
 	 */
 	public int hashCode() {
diff --git a/org.eclipse.debug.tests/src/org/eclipe/debug/tests/launching/LaunchConfigurationTests.java b/org.eclipse.debug.tests/src/org/eclipe/debug/tests/launching/LaunchConfigurationTests.java
index 455d5bd..c9de10c 100644
--- a/org.eclipse.debug.tests/src/org/eclipe/debug/tests/launching/LaunchConfigurationTests.java
+++ b/org.eclipse.debug.tests/src/org/eclipe/debug/tests/launching/LaunchConfigurationTests.java
@@ -1250,6 +1250,55 @@
 		assertTrue(t2.isTemplate());
 	}
 	
+	/**
+	 * Tests that a template that adds an attribute is not considered to override any template
+	 * values.
+	 * 
+	 * @throws CoreException
+	 */
+	public void testTemplateNoOverride() throws CoreException {
+		ILaunchConfigurationWorkingCopy t1 = newTemplate(null, "2-b-the-same");
+		ILaunchConfiguration template = t1.doSave();
+		ILaunchConfigurationWorkingCopy wc = template.getType().newInstance(null, "will-b-the-same", template);
+		wc.setAttribute("EXTEND-ATTRIBUTES", "EXTEND-ATTRIBUTES");
+		Map dif = wc.findDifferences(template.getAttributes());
+		assertTrue("Should not override any values", dif.isEmpty());
+	}
+	
+	/**
+	 * Tests that a template that changes an attribute is considered to override its template
+	 * values.
+	 * 
+	 * @throws CoreException
+	 */
+	public void testTemplateOverride() throws CoreException {
+		ILaunchConfigurationWorkingCopy t1 = newTemplate(null, "2-b-diff");
+		ILaunchConfiguration template = t1.doSave();
+		ILaunchConfigurationWorkingCopy wc = template.getType().newInstance(null, "will-b-diff", template);
+		wc.setAttribute("String1", "String2"); 
+		Map dif = wc.findDifferences(template.getAttributes());
+		assertEquals("Should override String1 value", 1, dif.size());
+		assertTrue("Should override String1 value", dif.containsKey("String1"));
+		assertEquals("Should override String1 value with String2", "String2", dif.get("String1"));
+	}
+	
+	/**
+	 * Test that a configuration that omits a template value shows the difference with
+	 * a <code>null</code> entry in the difference map.
+	 *  
+	 * @throws CoreException
+	 */
+	public void testMissingTemaplteValue() throws CoreException {
+		ILaunchConfigurationWorkingCopy t1 = newTemplate(null, "2-b-missing");
+		ILaunchConfiguration template = t1.doSave();
+		ILaunchConfigurationWorkingCopy wc = template.getType().newInstance(null, "will-b-diff", template);
+		wc.removeAttribute("String1"); 
+		Map dif = wc.findDifferences(template.getAttributes());
+		assertEquals("Should be missing String1 value", 1, dif.size());
+		assertTrue("Should be missing String1 value", dif.containsKey("String1"));
+		assertNull("Should be null value", dif.get("String1"));
+	}
+	
 }