Bug 570519 - Fix VariableTemplate offset/buffer

In case the default value is different from name, the insertion would
potentially behave incorrectly, showing variable name where default
value is expected, or inserting at wrong offset, erasing or keeping some
extra characters.

Change-Id: Ia5d4e47841a2272ce66e7e5a61b630eeadeaf13b
Also-By: Christoph Läubrich <laeubi@laeubi-soft.de>
Signed-off-by: Mickael Istria <mistria@redhat.com>
diff --git a/org.eclipse.text.tests/META-INF/MANIFEST.MF b/org.eclipse.text.tests/META-INF/MANIFEST.MF
index 40ec274..05f4275 100644
--- a/org.eclipse.text.tests/META-INF/MANIFEST.MF
+++ b/org.eclipse.text.tests/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %Plugin.name
 Bundle-SymbolicName: org.eclipse.text.tests
-Bundle-Version: 3.12.700.qualifier
+Bundle-Version: 3.12.800.qualifier
 Bundle-Vendor: %Plugin.providerName
 Bundle-Localization: plugin
 Export-Package: 
diff --git a/org.eclipse.text.tests/pom.xml b/org.eclipse.text.tests/pom.xml
index c83c539..0df48e1 100644
--- a/org.eclipse.text.tests/pom.xml
+++ b/org.eclipse.text.tests/pom.xml
@@ -19,7 +19,7 @@
   </parent>
   <groupId>org.eclipse.text</groupId>
   <artifactId>org.eclipse.text.tests</artifactId>
-  <version>3.12.700-SNAPSHOT</version>
+  <version>3.12.800-SNAPSHOT</version>
   <packaging>eclipse-test-plugin</packaging>
   <properties>
   	<testSuite>${project.artifactId}</testSuite>
diff --git a/org.eclipse.text.tests/src/org/eclipse/text/tests/templates/TemplateVariablesWordSelectionTest.java b/org.eclipse.text.tests/src/org/eclipse/text/tests/templates/TemplateVariablesWordSelectionTest.java
index df8be90..efbb97a 100644
--- a/org.eclipse.text.tests/src/org/eclipse/text/tests/templates/TemplateVariablesWordSelectionTest.java
+++ b/org.eclipse.text.tests/src/org/eclipse/text/tests/templates/TemplateVariablesWordSelectionTest.java
@@ -26,6 +26,7 @@
 import org.eclipse.jface.text.templates.TemplateContextType;
 import org.eclipse.jface.text.templates.TemplateTranslator;
 import org.eclipse.jface.text.templates.TemplateVariable;
+import org.eclipse.jface.text.templates.TemplateVariableType;
 
 public class TemplateVariablesWordSelectionTest  {
 
@@ -65,6 +66,29 @@
 		assertBufferStringAndVariables(expected.toString(), buffer);
 	}
 
+	@Test
+	public void testMulti() throws Exception {
+		TemplateBuffer buffer = new TemplateTranslator() {
+
+			@Override
+			protected TemplateVariable createVariable(TemplateVariableType type, String name, int[] offsets) {
+				if ("petType".equals(name)) {
+					String[] pets = new String[] { "cat", "dog", "other" };
+					TemplateVariable variable = new TemplateVariable(type.getName(), name, pets, offsets);
+					variable.setUnambiguous(true);
+					return variable;
+				}
+				return super.createVariable(type, name, offsets);
+			}
+		}.translate("My favorite pet is a ${petType:String}, I love my ${petType}.");
+		assertEquals("My favorite pet is a cat, I love my cat.", buffer.getString());
+		fType.resolve(buffer, fContext);
+
+		StringBuilder expected = new StringBuilder();
+		expected.append("My favorite pet is a cat, I love my cat.");
+		assertBufferStringAndVariables(expected.toString(), buffer);
+	}
+
 	/**
 	 * Ensures that {@link TemplateBuffer#getString()} equals the expected String and that all
 	 * {@link TemplateBuffer#getVariables()} are resolved and unambiguous.
diff --git a/org.eclipse.text/META-INF/MANIFEST.MF b/org.eclipse.text/META-INF/MANIFEST.MF
index c67cfa9..fbcad6f 100644
--- a/org.eclipse.text/META-INF/MANIFEST.MF
+++ b/org.eclipse.text/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %pluginName
 Bundle-SymbolicName: org.eclipse.text
-Bundle-Version: 3.10.400.qualifier
+Bundle-Version: 3.10.500.qualifier
 Bundle-Vendor: %providerName
 Bundle-Localization: plugin
 Export-Package: 
diff --git a/org.eclipse.text/src/org/eclipse/jface/text/templates/TemplateTranslator.java b/org.eclipse.text/src/org/eclipse/jface/text/templates/TemplateTranslator.java
index dcb0015..c56d826 100644
--- a/org.eclipse.text/src/org/eclipse/jface/text/templates/TemplateTranslator.java
+++ b/org.eclipse.text/src/org/eclipse/jface/text/templates/TemplateTranslator.java
@@ -14,10 +14,15 @@
 package org.eclipse.jface.text.templates;
 
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
+import java.util.SortedMap;
+import java.util.TreeMap;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -221,9 +226,39 @@
 		buffer.append(string.substring(complete));
 
 		TemplateVariable[] vars= createVariables(variables);
+		fixOffsetsAndBuffer(buffer, vars);
 		return new TemplateBuffer(buffer.toString(), vars);
 	}
 
+	/**
+	 * In cases default value is provided dynamically when instantiating the variable (so not part
+	 * of the {@link VariableDescription}), the buffer would actually contain incorrect text (name instead
+	 * of default value) and offsets can also be inconsistent if variable name and default value have different
+	 * length. This methods fix both buffer and variable offsets to ensure default value is used.
+	 * @param buffer the default template string
+	 * @param vars variables
+	 */
+	private void fixOffsetsAndBuffer(StringBuilder buffer, TemplateVariable[] vars) {
+		SortedMap<Integer, TemplateVariable> varsByOffset = new TreeMap<>();
+		for (TemplateVariable var : vars) {
+			for (int offset : var.getOffsets()) {
+				varsByOffset.put(Integer.valueOf(offset), var);
+			}
+		}
+		int totalOffsetDelta = 0;
+		Map<TemplateVariable, Collection<Integer>> fixedOffsets = new HashMap<>(vars.length, 1.f);
+		for (Entry<Integer, TemplateVariable> entry : varsByOffset.entrySet()) {
+			final int initialOffset = entry.getKey().intValue();
+			TemplateVariable variable = entry.getValue();
+			final int fixedOffset = initialOffset + totalOffsetDelta;
+			fixedOffsets.computeIfAbsent(variable, v -> new ArrayList<>(v.getOffsets().length)).add(Integer.valueOf(fixedOffset));
+			int currentOffsetDelta = variable.getDefaultValue().length() - variable.getName().length();
+			buffer.replace(fixedOffset, fixedOffset + variable.getName().length(), variable.getDefaultValue());
+			totalOffsetDelta += currentOffsetDelta;
+		}
+		fixedOffsets.forEach((variable, fixs) -> variable.setOffsets(fixs.stream().mapToInt(Integer::valueOf).toArray()));
+	}
+
 	private TemplateVariableType createType(String typeName, String paramString) {
 		if (typeName == null)
 			return null;
@@ -237,7 +272,7 @@
 			String argument= matcher.group();
 			if (argument.charAt(0) == '\'') {
 				// argumentText
-				argument= argument.substring(1, argument.length() - 1).replaceAll("''", "'"); //$NON-NLS-1$ //$NON-NLS-2$
+				argument= argument.substring(1, argument.length() - 1).replace("''", "'"); //$NON-NLS-1$ //$NON-NLS-2$
 			}
 
 			params.add(argument);
@@ -260,10 +295,11 @@
 	 * @param name the name of the variable
 	 * @param type the variable type, <code>null</code> for not defined
 	 * @param offset the buffer offset of the variable
+	 * @return the variable description found or created for that name
 	 * @throws TemplateException if merging the type fails
 	 * @since 3.3
 	 */
-	private void updateOrCreateVariable(Map<String, VariableDescription> variables, String name, TemplateVariableType type, int offset) throws TemplateException {
+	private VariableDescription updateOrCreateVariable(Map<String, VariableDescription> variables, String name, TemplateVariableType type, int offset) throws TemplateException {
 		VariableDescription varDesc= variables.get(name);
 		if (varDesc == null) {
 			varDesc= new VariableDescription(name, type);
@@ -272,6 +308,7 @@
 			varDesc.mergeType(type);
 		}
 		varDesc.fOffsets.add(Integer.valueOf(offset));
+		return varDesc;
 	}
 
 	/**