Bug 549989: Extract the JavaContextCore and move it to jdt.core.manipulation

- Initialize IJavaContext
- Extract out the JavaContextCore
- Apply JavaContextCore inside JavaContext
- Move MultiVariable, CompilationUnitCompletion, JavaVariable to jdt.core.manipulation

Change-Id: Id6bbe6be5b77ee0a8b5d4f19872c19ea5032ca43
Signed-off-by: Sheng Chen <sheche@microsoft.com>
diff --git a/org.eclipse.jdt.core.manipulation/META-INF/MANIFEST.MF b/org.eclipse.jdt.core.manipulation/META-INF/MANIFEST.MF
index 5ac167a..8479d66 100644
--- a/org.eclipse.jdt.core.manipulation/META-INF/MANIFEST.MF
+++ b/org.eclipse.jdt.core.manipulation/META-INF/MANIFEST.MF
@@ -58,6 +58,7 @@
  org.eclipse.jdt.internal.ui.text;x-friends:="org.eclipse.jdt.ui",
  org.eclipse.jdt.internal.ui.text.correction;x-friends:="org.eclipse.jdt.ui",
  org.eclipse.jdt.internal.ui.text.correction.proposals;x-friends:="org.eclipse.jdt.ui",
+ org.eclipse.jdt.internal.ui.text.template.contentassist;x-friends:="org.eclipse.jdt.ui",
  org.eclipse.jdt.internal.ui.util;x-friends:="org.eclipse.jdt.ui"
 Import-Package: com.ibm.icu.text
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/template/contentassist/MultiVariable.java b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/text/template/contentassist/MultiVariable.java
similarity index 95%
rename from org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/template/contentassist/MultiVariable.java
rename to org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/text/template/contentassist/MultiVariable.java
index 5f58ea4..11070e0 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/template/contentassist/MultiVariable.java
+++ b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/text/template/contentassist/MultiVariable.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2011 IBM Corporation and others.
+ * Copyright (c) 2000, 2019 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -10,6 +10,7 @@
  *
  * Contributors:
  *     IBM Corporation - initial API and implementation
+ *     Microsoft Corporation - moved template related code to jdt.core.manipulation - https://bugs.eclipse.org/549989
  *******************************************************************************/
 package org.eclipse.jdt.internal.ui.text.template.contentassist;
 
diff --git a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/template/java/CompilationUnitCompletion.java b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/template/java/CompilationUnitCompletion.java
similarity index 98%
rename from org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/template/java/CompilationUnitCompletion.java
rename to org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/template/java/CompilationUnitCompletion.java
index e330692..1024de3 100644
--- a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/template/java/CompilationUnitCompletion.java
+++ b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/template/java/CompilationUnitCompletion.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2018 IBM Corporation and others.
+ * Copyright (c) 2000, 2019 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -11,6 +11,7 @@
  * Contributors:
  *     IBM Corporation - initial API and implementation
  *     Lukas Hanke <hanke@yatta.de> - [templates][content assist] Content assist for 'for' loop should suggest member variables - https://bugs.eclipse.org/117215
+ *     Microsoft Corporation - moved template related code to jdt.core.manipulation - https://bugs.eclipse.org/549989
  *******************************************************************************/
 package org.eclipse.jdt.internal.corext.template.java;
 
@@ -37,7 +38,7 @@
 import org.eclipse.jdt.core.Signature;
 import org.eclipse.jdt.core.compiler.IProblem;
 
-import org.eclipse.jdt.internal.ui.JavaPlugin;
+import org.eclipse.jdt.internal.core.manipulation.JavaManipulationPlugin;
 
 /**
  * A completion requester to collect informations on local variables.
@@ -142,7 +143,7 @@
 			String implementorName= SignatureUtil.stripSignatureToFQN(signature);
 			if (implementorName.length() == 0)
 				return false;
-			
+
 			int implementorDims= Signature.getArrayCount(signature);
 			int superDimsIndex= supertype.indexOf("[]"); //$NON-NLS-1$
 			int superDims;
@@ -415,7 +416,7 @@
 			IJavaProject project= fUnit.getJavaProject();
 			IType type= project.findType(superType);
 			if (type == null)
-				throw new JavaModelException(new CoreException(new Status(IStatus.ERROR, JavaPlugin.getPluginId(), IStatus.OK, "No such type", null))); //$NON-NLS-1$
+				throw new JavaModelException(new CoreException(new Status(IStatus.ERROR, JavaManipulationPlugin.getPluginId(), IStatus.OK, "No such type", null))); //$NON-NLS-1$
 			return computeBinding(type, index);
 		}
 
@@ -584,7 +585,7 @@
 				}
 			}
 
-			throw new JavaModelException(new CoreException(new Status(IStatus.ERROR, JavaPlugin.getPluginId(), IStatus.OK, "Illegal hierarchy", null))); //$NON-NLS-1$
+			throw new JavaModelException(new CoreException(new Status(IStatus.ERROR, JavaManipulationPlugin.getPluginId(), IStatus.OK, "Illegal hierarchy", null))); //$NON-NLS-1$
 		}
 
 		/**
@@ -846,7 +847,7 @@
 
 	/**
 	 * Returns all arrays, visible in the current context's scope, in the order that they appear.
-	 * 
+	 *
 	 * @return all visible arrays
 	 */
 	public Variable[] findArraysInCurrentScope() {
@@ -859,7 +860,7 @@
 			if (localVariable.isArray())
 				arrays.add(localVariable);
 		}
-		
+
 		// fields
 		for (ListIterator<Variable> iterator= fFields.listIterator(fFields.size()); iterator.hasPrevious();) {
 			Variable field= iterator.previous();
@@ -915,7 +916,7 @@
 	 * Returns all variables, visible in the current context's scope, implementing
 	 * <code>java.lang.Iterable</code> <em>and</em> all arrays, in the order that they appear. That
 	 * is, the returned variables can be used within the <code>foreach</code> language construct.
-	 * 
+	 *
 	 * @return all visible <code>Iterable</code>s and arrays
 	 */
 	public Variable[] findIterablesInCurrentScope() {
@@ -928,7 +929,7 @@
 			if (localVariable.isArray() || localVariable.isIterable())
 				iterables.add(localVariable);
 		}
-		
+
 		// fields
 		for (ListIterator<Variable> iterator= fFields.listIterator(fFields.size()); iterator.hasPrevious();) {
 			Variable field= iterator.previous();
diff --git a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/template/java/IJavaContext.java b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/template/java/IJavaContext.java
new file mode 100644
index 0000000..1a8e463
--- /dev/null
+++ b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/template/java/IJavaContext.java
@@ -0,0 +1,115 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Microsoft Corporation and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Microsoft Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.corext.template.java;
+
+import org.eclipse.jface.text.templates.TemplateVariable;
+
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.dom.CompilationUnit;
+
+import org.eclipse.jdt.internal.corext.template.java.CompilationUnitCompletion.Variable;
+
+import org.eclipse.jdt.internal.ui.text.template.contentassist.MultiVariable;
+
+public interface IJavaContext {
+
+	/**
+	 * Returns the compilation unit if one is associated with this context,
+	 * <code>null</code> otherwise.
+	 *
+	 * @return the compilation unit of this context or <code>null</code>
+	 */
+	ICompilationUnit getCompilationUnit();
+
+	/**
+	 * Exception handler when generate the template
+	 * @param e Exception
+	 */
+	void handleException(Exception e);
+
+	/**
+	 * Returns the names of arrays available in the current {@link CompilationUnit}'s scope.
+	 *
+	 * @return the names of local arrays available in the current {@link CompilationUnit}'s scope
+	 */
+	Variable[] getArrays();
+
+	/**
+	 * Returns the names of local variables matching <code>type</code>.
+	 *
+	 * @param type the type of the variables
+	 * @return the names of local variables matching <code>type</code>
+	 * @since 3.3
+	 */
+	Variable[] getLocalVariables(String type);
+
+	/**
+	 * Returns the names of fields matching <code>type</code>.
+	 *
+	 * @param type the type of the fields
+	 * @return the names of fields matching <code>type</code>
+	 * @since 3.3
+	 */
+	Variable[] getFields(String type);
+
+	/**
+	 * Returns the names of iterables or arrays available in the current {@link CompilationUnit}'s scope.
+	 *
+	 * @return the names of iterables or arrays available in the current {@link CompilationUnit}'s scope
+	 */
+	Variable[] getIterables();
+
+	/**
+	 * Marks the name as used.
+	 * @param name the name to be marked
+	 */
+	void markAsUsed(String name);
+
+	/**
+	 * Return the suggested names matching <code>type</code>
+	 * @param type the type of the variable
+	 * @return the suggested names matching <code>type</code>
+	 * @throws IllegalArgumentException Exception
+	 */
+	String[] suggestVariableNames(String type) throws IllegalArgumentException;
+
+	/**
+	 * Adds an import for type with type name <code>type</code> if possible.
+	 * Returns a string which can be used to reference the type.
+	 *
+	 * @param type the fully qualified name of the type to import
+	 * @return returns a type to which the type binding can be assigned to.
+	 * 	The returned type contains is unqualified when an import could be added or was already known.
+	 * 	It is fully qualified, if an import conflict prevented the import.
+	 * @since 3.4
+	 */
+	String addImport(String type);
+
+	/**
+	 * Return the template variable matching <code>name</code>
+	 * @param name the variable name
+	 * @return the template variable matching <code>name</code>
+	 */
+	TemplateVariable getTemplateVariable(String name);
+
+	/**
+	 * Adds a multi-variable guess dependency.
+	 *
+	 * @param master the master variable - <code>slave</code> needs to be updated when
+	 *        <code>master</code> changes
+	 * @param slave the dependent variable
+	 * @since 3.3
+	 */
+	void addDependency(MultiVariable master, MultiVariable slave);
+}
diff --git a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/template/java/JavaContextCore.java b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/template/java/JavaContextCore.java
new file mode 100644
index 0000000..a1bf21c
--- /dev/null
+++ b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/template/java/JavaContextCore.java
@@ -0,0 +1,752 @@
+/*******************************************************************************
+ * Copyright (c) 2019 IBM Corporation and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *     Lars Vogel  <lars.vogel@gmail.com> - [templates][content assist] Ctrl+Space without any starting letter shows to no templates - https://bugs.eclipse.org/406463
+ *     Lukas Hanke <hanke@yatta.de> - [templates][content assist] Content assist for 'for' loop should suggest member variables - https://bugs.eclipse.org/117215
+ *     Nicolaj Hoess <nicohoess@gmail.com> - Make some internal methods accessible to help Postfix Code Completion plug-in - https://bugs.eclipse.org/433500
+ *     Microsoft Corporation - moved template related code to jdt.core.manipulation - https://bugs.eclipse.org/549989
+ *******************************************************************************/
+package org.eclipse.jdt.internal.corext.template.java;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.NullProgressMonitor;
+
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.BadPositionCategoryException;
+import org.eclipse.jface.text.DefaultPositionUpdater;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IPositionUpdater;
+import org.eclipse.jface.text.Position;
+import org.eclipse.jface.text.templates.DocumentTemplateContext;
+import org.eclipse.jface.text.templates.Template;
+import org.eclipse.jface.text.templates.TemplateBuffer;
+import org.eclipse.jface.text.templates.TemplateContextType;
+import org.eclipse.jface.text.templates.TemplateException;
+import org.eclipse.jface.text.templates.TemplateTranslator;
+import org.eclipse.jface.text.templates.TemplateVariable;
+import org.eclipse.jface.text.templates.TemplateVariableType;
+
+import org.eclipse.jdt.core.Flags;
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.IField;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IMethod;
+import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.core.NamingConventions;
+import org.eclipse.jdt.core.Signature;
+import org.eclipse.jdt.core.dom.CompilationUnit;
+import org.eclipse.jdt.core.dom.SimpleName;
+import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
+import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext;
+import org.eclipse.jdt.core.manipulation.SharedASTProviderCore;
+import org.eclipse.jdt.core.manipulation.TypeKinds;
+import org.eclipse.jdt.core.manipulation.TypeNameMatchCollector;
+import org.eclipse.jdt.core.search.IJavaSearchConstants;
+import org.eclipse.jdt.core.search.IJavaSearchScope;
+import org.eclipse.jdt.core.search.SearchEngine;
+import org.eclipse.jdt.core.search.SearchPattern;
+import org.eclipse.jdt.core.search.TypeNameMatch;
+
+import org.eclipse.jdt.internal.core.manipulation.JavaManipulationPlugin;
+import org.eclipse.jdt.internal.core.manipulation.StubUtility;
+import org.eclipse.jdt.internal.core.manipulation.dom.ASTResolving;
+import org.eclipse.jdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext;
+import org.eclipse.jdt.internal.corext.template.java.CompilationUnitCompletion.Variable;
+import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
+
+import org.eclipse.jdt.internal.ui.text.template.contentassist.MultiVariable;
+
+
+/**
+ * A context for Java source.
+ */
+public class JavaContextCore extends CompilationUnitContextCore implements IJavaContext {
+
+	/** A code completion requester for guessing local variable names. */
+	private CompilationUnitCompletion fCompletion;
+	/**
+	 * The list of used local names.
+	 * @since 3.3
+	 */
+	private Set<String> fUsedNames= new HashSet<>();
+	private Map<String, MultiVariable> fVariables= new HashMap<>();
+	private ImportRewrite fImportRewrite;
+
+	private Set<String> fCompatibleContextTypeIds;
+
+	private DocumentTemplateContext fContext;
+
+	/**
+	 * Creates a java template context.
+	 *
+	 * @param context the JavaContext instance
+	 * @param type   the context type.
+	 * @param document the document.
+	 * @param completionOffset the completion offset within the document.
+	 * @param completionLength the completion length.
+	 * @param compilationUnit the compilation unit (may be <code>null</code>).
+	 */
+	public JavaContextCore(DocumentTemplateContext context, TemplateContextType type, IDocument document, int completionOffset, int completionLength, ICompilationUnit compilationUnit) {
+		super(type, document, completionOffset, completionLength, compilationUnit);
+		this.fContext = context;
+	}
+
+	/**
+	 * Creates a java template context.
+	 *
+	 * @param type   the context type.
+	 * @param document the document.
+	 * @param completionOffset the completion offset within the document.
+	 * @param completionLength the completion length.
+	 * @param compilationUnit the compilation unit (may be <code>null</code>).
+	 */
+	public JavaContextCore(TemplateContextType type, IDocument document, int completionOffset, int completionLength, ICompilationUnit compilationUnit) {
+		super(type, document, completionOffset, completionLength, compilationUnit);
+		this.fContext = this;
+	}
+
+	/**
+	 * Creates a java template context.
+	 *
+	 * @param context the JavaContext instance
+	 * @param type   the context type.
+	 * @param document the document.
+	 * @param completionPosition the position defining the completion offset and length
+	 * @param compilationUnit the compilation unit (may be <code>null</code>).
+	 * @since 3.2
+	 */
+	public JavaContextCore(DocumentTemplateContext context, TemplateContextType type, IDocument document, Position completionPosition, ICompilationUnit compilationUnit) {
+		super(type, document, completionPosition, compilationUnit);
+		this.fContext = context;
+	}
+
+	/**
+	 * Creates a java template context.
+	 *
+	 * @param type   the context type.
+	 * @param document the document.
+	 * @param completionPosition the position defining the completion offset and length
+	 * @param compilationUnit the compilation unit (may be <code>null</code>).
+	 * @since 3.2
+	 */
+	public JavaContextCore(TemplateContextType type, IDocument document, Position completionPosition, ICompilationUnit compilationUnit) {
+		super(type, document, completionPosition, compilationUnit);
+		this.fContext = this;
+	}
+
+	/**
+	 * Adds a context type that is also compatible. That means the context can also process templates of that context type.
+	 *
+	 * @param contextTypeId the context type to accept
+	 */
+	public void addCompatibleContextType(String contextTypeId) {
+		if (fCompatibleContextTypeIds == null)
+			fCompatibleContextTypeIds= new HashSet<>();
+		fCompatibleContextTypeIds.add(contextTypeId);
+	}
+
+	/*
+	 * @see TemplateContext#evaluate(Template template)
+	 */
+	@Override
+	public TemplateBuffer evaluate(Template template) throws BadLocationException, TemplateException {
+		clear();
+
+		if (!canEvaluate(template))
+			throw new TemplateException(JavaTemplateMessages.Context_error_cannot_evaluate);
+
+		TemplateTranslator translator= new TemplateTranslator() {
+			@Override
+			protected TemplateVariable createVariable(TemplateVariableType type, String name, int[] offsets) {
+//				TemplateVariableResolver resolver= getContextType().getResolver(type.getName());
+//				return resolver.createVariable();
+
+				MultiVariable variable= new JavaVariable(type, name, offsets);
+				fVariables.put(name, variable);
+				return variable;
+			}
+		};
+		TemplateBuffer buffer= translator.translate(template);
+
+		getContextType().resolve(buffer, fContext);
+
+		rewriteImports();
+
+		clear();
+
+		return buffer;
+	}
+
+	private void clear() {
+		fUsedNames.clear();
+		fImportRewrite= null;
+	}
+
+	/*
+	 * @see TemplateContext#canEvaluate(Template templates)
+	 */
+	@Override
+	public boolean canEvaluate(Template template) {
+		if (!hasCompatibleContextType(template))
+			return false;
+
+		if (this.isForceEvaluation())
+			return true;
+
+		String key= getKey().toLowerCase();
+		if (key.length() > 0 || !isAfterDot()) {
+			String templateName= template.getName().toLowerCase();
+			return JavaCore.ENABLED.equals(JavaCore.getOption(JavaCore.CODEASSIST_SUBSTRING_MATCH))
+					? templateName.contains(key)
+					: templateName.startsWith(key);
+		}
+		return false;
+	}
+
+	private boolean isAfterDot() {
+		try {
+			IDocument document= getDocument();
+			int offset= getCompletionOffset();
+			return document.get(offset - 1, 1).charAt(0) == '.';
+		} catch (BadLocationException e) {
+			return false;
+		}
+	}
+
+	private boolean hasCompatibleContextType(Template template) {
+		String key= getKey();
+		if (template.matches(key, getContextType().getId()))
+			return true;
+
+		if (fCompatibleContextTypeIds == null)
+			return false;
+
+		Iterator<String> iter= fCompatibleContextTypeIds.iterator();
+		while (iter.hasNext()) {
+			if (template.matches(key, iter.next()))
+				return true;
+		}
+
+		return false;
+	}
+
+	/*
+	 * @see DocumentTemplateContext#getCompletionPosition();
+	 */
+	@Override
+	public int getStart() {
+
+		if (this.isManaged() && getCompletionLength() > 0)
+			return super.getStart();
+
+		try {
+			IDocument document= getDocument();
+
+			int start= getCompletionOffset();
+			int end= getCompletionOffset() + getCompletionLength();
+
+			while (start != 0 && Character.isUnicodeIdentifierPart(document.getChar(start - 1)))
+				start--;
+
+			while (start != end && Character.isWhitespace(document.getChar(start)))
+				start++;
+
+			if (start == end)
+				start= getCompletionOffset();
+
+				return start;
+
+		} catch (BadLocationException e) {
+			return super.getStart();
+		}
+	}
+
+	/*
+	 * @see org.eclipse.jdt.internal.corext.template.DocumentTemplateContext#getEnd()
+	 */
+	@Override
+	public int getEnd() {
+
+		if (this.isManaged() || getCompletionLength() == 0)
+			return super.getEnd();
+
+		try {
+			IDocument document= getDocument();
+
+			int start= getCompletionOffset();
+			int end= getCompletionOffset() + getCompletionLength();
+
+			while (start != end && Character.isWhitespace(document.getChar(end - 1)))
+				end--;
+
+			return end;
+
+		} catch (BadLocationException e) {
+			return super.getEnd();
+		}
+	}
+
+	/*
+	 * @see org.eclipse.jdt.internal.corext.template.DocumentTemplateContext#getKey()
+	 */
+	@Override
+	public String getKey() {
+
+		if (getCompletionLength() == 0)
+			return super.getKey();
+
+		try {
+			IDocument document= getDocument();
+
+			int start= getStart();
+			int end= getCompletionOffset();
+			return start <= end
+				? document.get(start, end - start)
+				: ""; //$NON-NLS-1$
+
+		} catch (BadLocationException e) {
+			return super.getKey();
+		}
+	}
+
+	/**
+	 * Returns the character before the start position of the completion.
+	 *
+	 * @return the character before the start position of the completion
+	 */
+	public char getCharacterBeforeStart() {
+		int start= getStart();
+
+		try {
+			return start == 0
+				? ' '
+				: getDocument().getChar(start - 1);
+
+		} catch (BadLocationException e) {
+			return ' ';
+		}
+	}
+
+	@Override
+	public void handleException(Exception e) {
+		if (fContext != this && fContext instanceof IJavaContext) {
+			// invoke handleException() in JavaContext
+			((IJavaContext) fContext).handleException(e);
+		} else {
+			JavaManipulationPlugin.log(e);
+		}
+
+	}
+
+	private CompilationUnitCompletion getCompletion() {
+		ICompilationUnit compilationUnit= getCompilationUnit();
+		if (fCompletion == null) {
+			fCompletion= new CompilationUnitCompletion(compilationUnit);
+
+			if (compilationUnit != null) {
+				try {
+					compilationUnit.codeComplete(getStart(), fCompletion);
+				} catch (JavaModelException e) {
+					// ignore
+				}
+			}
+		}
+
+		return fCompletion;
+	}
+
+	/**
+	 * Returns the names of arrays available in the current {@link CompilationUnit}'s scope.
+	 *
+	 * @return the names of local arrays available in the current {@link CompilationUnit}'s scope
+	 */
+	@Override
+	public Variable[] getArrays() {
+		Variable[] arrays= getCompletion().findArraysInCurrentScope();
+		arrange(arrays);
+		return arrays;
+	}
+
+	/**
+	 * Sorts already used variables behind any that are not yet used.
+	 *
+	 * @param variables the variables to sort
+	 * @since 3.3
+	 */
+	private void arrange(Variable[] variables) {
+		Arrays.sort(variables, new Comparator<Variable>() {
+			@Override
+			public int compare(Variable o1, Variable o2) {
+				return rank(o1) - rank(o2);
+			}
+
+			private int rank(Variable l) {
+				return fUsedNames.contains(l.getName()) ? 1 : 0;
+			}
+		});
+	}
+
+	/**
+	 * Returns the names of local variables matching <code>type</code>.
+	 *
+	 * @param type the type of the variables
+	 * @return the names of local variables matching <code>type</code>
+	 * @since 3.3
+	 */
+	@Override
+	public Variable[] getLocalVariables(String type) {
+		Variable[] localVariables= getCompletion().findLocalVariables(type);
+		arrange(localVariables);
+		return localVariables;
+	}
+
+	/**
+	 * Returns the names of fields matching <code>type</code>.
+	 *
+	 * @param type the type of the fields
+	 * @return the names of fields matching <code>type</code>
+	 * @since 3.3
+	 */
+	@Override
+	public Variable[] getFields(String type) {
+		Variable[] fields= getCompletion().findFieldVariables(type);
+		arrange(fields);
+		return fields;
+	}
+
+	/**
+	 * Returns the names of iterables or arrays available in the current {@link CompilationUnit}'s scope.
+	 *
+	 * @return the names of iterables or arrays available in the current {@link CompilationUnit}'s scope
+	 */
+	@Override
+	public Variable[] getIterables() {
+		Variable[] iterables= getCompletion().findIterablesInCurrentScope();
+		arrange(iterables);
+		return iterables;
+	}
+
+	@Override
+	public void markAsUsed(String name) {
+		fUsedNames.add(name);
+	}
+
+	@Override
+	public String[] suggestVariableNames(String type) throws IllegalArgumentException {
+		String[] excludes= computeExcludes();
+		// TODO erasure, arrays, etc.
+		String[] result= suggestVariableName(type, excludes);
+		return result;
+	}
+
+	public String[] computeExcludes() {
+		String[] excludes= getCompletion().getLocalVariableNames();
+		if (!fUsedNames.isEmpty()) {
+			String[] allExcludes= new String[fUsedNames.size() + excludes.length];
+			System.arraycopy(excludes, 0, allExcludes, 0, excludes.length);
+			System.arraycopy(fUsedNames.toArray(), 0, allExcludes, 0, fUsedNames.size());
+			excludes= allExcludes;
+		}
+		return excludes;
+	}
+
+	private String[] suggestVariableName(String type, String[] excludes) throws IllegalArgumentException {
+		int dim=0;
+		while (type.endsWith("[]")) {//$NON-NLS-1$
+			dim++;
+			type= type.substring(0, type.length() - 2);
+		}
+
+		IJavaProject project= getJavaProject();
+		if (project != null)
+			return StubUtility.getVariableNameSuggestions(NamingConventions.VK_LOCAL, project, type, dim, Arrays.asList(excludes), true);
+
+		// fallback if we lack proper context: roll-our own lowercasing
+		return new String[] {Signature.getSimpleName(type).toLowerCase()};
+	}
+
+	/**
+	 * Adds an import for type with type name <code>type</code> if possible.
+	 * Returns a string which can be used to reference the type.
+	 *
+	 * @param type the fully qualified name of the type to import
+	 * @return returns a type to which the type binding can be assigned to.
+	 * 	The returned type contains is unqualified when an import could be added or was already known.
+	 * 	It is fully qualified, if an import conflict prevented the import.
+	 * @since 3.4
+	 */
+	@Override
+	public String addImport(String type) {
+		if (isReadOnly())
+			return type;
+
+		ICompilationUnit cu= getCompilationUnit();
+		if (cu == null)
+			return type;
+
+		try {
+			boolean qualified= type.indexOf('.') != -1;
+			if (!qualified) {
+				IJavaSearchScope searchScope= SearchEngine.createJavaSearchScope(new IJavaElement[] { cu.getJavaProject() });
+				SimpleName nameNode= null;
+				TypeNameMatch[] matches= findAllTypes(type, searchScope, nameNode, null, cu);
+				if (matches.length != 1) // only add import if we have a single match
+					return type;
+				type= matches[0].getFullyQualifiedName();
+			}
+
+			CompilationUnit root= getASTRoot(cu);
+			if (fImportRewrite == null) {
+				if (root == null) {
+					fImportRewrite= StubUtility.createImportRewrite(cu, true);
+				} else {
+					fImportRewrite= StubUtility.createImportRewrite(root, true);
+				}
+			}
+
+			ImportRewriteContext context;
+			if (root == null)
+				context= null;
+			else
+				context= new ContextSensitiveImportRewriteContext(root, getCompletionOffset(), fImportRewrite);
+
+			return fImportRewrite.addImport(type, context);
+		} catch (JavaModelException e) {
+			handleException(e);
+			return type;
+		}
+	}
+
+	/**
+	 * Adds a static import for the member with name <code>qualifiedMemberName</code>. The member is
+	 * either a static field or a static method or a '*' to import all static members of a type.
+	 *
+	 * @param qualifiedMemberName the fully qualified name of the member to import or a qualified type
+	 * 			name plus a '.*' suffix.
+	 * @return returns either the simple member name if the import was successful or else the qualified name.
+	 * @since 3.4
+	 */
+	public String addStaticImport(String qualifiedMemberName) {
+		if (isReadOnly())
+			return qualifiedMemberName;
+
+		ICompilationUnit cu= getCompilationUnit();
+		if (cu == null)
+			return qualifiedMemberName;
+
+		int memberOffset= qualifiedMemberName.lastIndexOf('.');
+		if (memberOffset == -1)
+			return qualifiedMemberName;
+
+		String typeName= qualifiedMemberName.substring(0, memberOffset);
+		String memberName= qualifiedMemberName.substring(memberOffset + 1, qualifiedMemberName.length());
+		try {
+			boolean isField;
+			if ("*".equals(memberName)) { //$NON-NLS-1$
+				isField= true;
+			} else {
+				IJavaProject javaProject= cu.getJavaProject();
+
+				IType type= javaProject.findType(typeName);
+				if (type == null)
+					return qualifiedMemberName;
+
+				IField field= type.getField(memberName);
+				if (field.exists()) {
+					isField= true;
+				} else if (hasMethod(type, memberName)) {
+					isField= false;
+				} else {
+					return qualifiedMemberName;
+				}
+			}
+
+			CompilationUnit root= getASTRoot(cu);
+			if (fImportRewrite == null) {
+				if (root == null) {
+					fImportRewrite= StubUtility.createImportRewrite(cu, true);
+				} else {
+					fImportRewrite= StubUtility.createImportRewrite(root, true);
+				}
+			}
+
+			ImportRewriteContext context;
+			if (root == null)
+				context= null;
+			else
+				context= new ContextSensitiveImportRewriteContext(root, getCompletionOffset(), fImportRewrite);
+
+			return fImportRewrite.addStaticImport(typeName, memberName, isField, context);
+		} catch (JavaModelException e) {
+			handleException(e);
+			return typeName;
+		}
+	}
+
+	/**
+	 * Does <code>type</code> contain a method with <code>name</code>?
+	 *
+	 * @param type the type to inspect
+	 * @param name the name of the method to search for
+	 * @return true if has such a method
+	 * @throws JavaModelException if methods could not be retrieved
+	 * @since 3.4
+	 */
+	private boolean hasMethod(IType type, String name) throws JavaModelException {
+		IMethod[] methods= type.getMethods();
+		for (int i= 0; i < methods.length; i++) {
+			if (name.equals(methods[i].getElementName()))
+				return true;
+		}
+
+		return false;
+	}
+
+	private void rewriteImports() {
+		if (fImportRewrite == null)
+			return;
+
+		if (isReadOnly())
+			return;
+
+		ICompilationUnit cu= getCompilationUnit();
+		if (cu == null)
+			return;
+
+		try {
+			Position position= new Position(getCompletionOffset(), 0);
+			IDocument document= getDocument();
+			final String category= "__template_position_importer" + System.currentTimeMillis(); //$NON-NLS-1$
+			IPositionUpdater updater= new DefaultPositionUpdater(category);
+			document.addPositionCategory(category);
+			document.addPositionUpdater(updater);
+			document.addPosition(position);
+
+			try {
+				JavaModelUtil.applyEdit(cu, fImportRewrite.rewriteImports(null), false, null);
+
+				setCompletionOffset(position.getOffset());
+			} catch (CoreException e) {
+				handleException(e);
+			} finally {
+				document.removePosition(position);
+				document.removePositionUpdater(updater);
+				document.removePositionCategory(category);
+			}
+		} catch (BadLocationException e) {
+			handleException(e);
+		} catch (BadPositionCategoryException e) {
+			handleException(e);
+		}
+	}
+
+	private CompilationUnit getASTRoot(ICompilationUnit compilationUnit) {
+		return SharedASTProviderCore.getAST(compilationUnit, SharedASTProviderCore.WAIT_NO, new NullProgressMonitor());
+	}
+
+	/*
+	 * Finds a type by the simple name. From AddImportsOperation
+	 */
+	private TypeNameMatch[] findAllTypes(String simpleTypeName, IJavaSearchScope searchScope, SimpleName nameNode, IProgressMonitor monitor, ICompilationUnit cu) throws JavaModelException {
+		boolean is50OrHigher= JavaModelUtil.is50OrHigher(cu.getJavaProject());
+
+		int typeKinds= TypeKinds.ALL_TYPES;
+		if (nameNode != null) {
+			typeKinds= ASTResolving.getPossibleTypeKinds(nameNode, is50OrHigher);
+		}
+
+		ArrayList<TypeNameMatch> typeInfos= new ArrayList<>();
+		TypeNameMatchCollector requestor= new TypeNameMatchCollector(typeInfos);
+		new SearchEngine().searchAllTypeNames(null, 0, simpleTypeName.toCharArray(), SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE, getSearchForConstant(typeKinds), searchScope, requestor, IJavaSearchConstants.FORCE_IMMEDIATE_SEARCH, monitor);
+
+		ArrayList<TypeNameMatch> typeRefsFound= new ArrayList<>(typeInfos.size());
+		for (int i= 0, len= typeInfos.size(); i < len; i++) {
+			TypeNameMatch curr= typeInfos.get(i);
+			if (curr.getPackageName().length() > 0) { // do not suggest imports from the default package
+				if (isOfKind(curr, typeKinds, is50OrHigher) && isVisible(curr, cu)) {
+					typeRefsFound.add(curr);
+				}
+			}
+		}
+		return typeRefsFound.toArray(new TypeNameMatch[typeRefsFound.size()]);
+	}
+
+	private int getSearchForConstant(int typeKinds) {
+		final int CLASSES= TypeKinds.CLASSES;
+		final int INTERFACES= TypeKinds.INTERFACES;
+		final int ENUMS= TypeKinds.ENUMS;
+		final int ANNOTATIONS= TypeKinds.ANNOTATIONS;
+
+		switch (typeKinds & (CLASSES | INTERFACES | ENUMS | ANNOTATIONS)) {
+			case CLASSES: return IJavaSearchConstants.CLASS;
+			case INTERFACES: return IJavaSearchConstants.INTERFACE;
+			case ENUMS: return IJavaSearchConstants.ENUM;
+			case ANNOTATIONS: return IJavaSearchConstants.ANNOTATION_TYPE;
+			case CLASSES | INTERFACES: return IJavaSearchConstants.CLASS_AND_INTERFACE;
+			case CLASSES | ENUMS: return IJavaSearchConstants.CLASS_AND_ENUM;
+			default: return IJavaSearchConstants.TYPE;
+		}
+	}
+
+	private boolean isOfKind(TypeNameMatch curr, int typeKinds, boolean is50OrHigher) {
+		int flags= curr.getModifiers();
+		if (Flags.isAnnotation(flags)) {
+			return is50OrHigher && ((typeKinds & TypeKinds.ANNOTATIONS) != 0);
+		}
+		if (Flags.isEnum(flags)) {
+			return is50OrHigher && ((typeKinds & TypeKinds.ENUMS) != 0);
+		}
+		if (Flags.isInterface(flags)) {
+			return (typeKinds & TypeKinds.INTERFACES) != 0;
+		}
+		return (typeKinds & TypeKinds.CLASSES) != 0;
+	}
+
+
+	private boolean isVisible(TypeNameMatch curr, ICompilationUnit cu) {
+		int flags= curr.getModifiers();
+		if (Flags.isPrivate(flags)) {
+			return false;
+		}
+		if (Flags.isPublic(flags) || Flags.isProtected(flags)) {
+			return true;
+		}
+		return curr.getPackageName().equals(cu.getParent().getElementName());
+	}
+
+	@Override
+	public TemplateVariable getTemplateVariable(String name) {
+		TemplateVariable variable= fVariables.get(name);
+		if (variable != null && !variable.isResolved())
+			getContextType().resolve(variable, fContext);
+		return variable;
+	}
+
+	@Override
+	public void addDependency(MultiVariable master, MultiVariable slave) {
+		// Do nothing
+	}
+
+}
diff --git a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/template/java/JavaVariable.java b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/template/java/JavaVariable.java
similarity index 90%
rename from org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/template/java/JavaVariable.java
rename to org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/template/java/JavaVariable.java
index 7d54ce4..639b911 100644
--- a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/template/java/JavaVariable.java
+++ b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/template/java/JavaVariable.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2006, 2011 IBM Corporation and others.
+ * Copyright (c) 2006, 2019 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -10,6 +10,7 @@
  *
  * Contributors:
  *     IBM Corporation - initial API and implementation
+ *     Microsoft Corporation - moved template related code to jdt.core.manipulation - https://bugs.eclipse.org/549989
  *******************************************************************************/
 package org.eclipse.jdt.internal.corext.template.java;
 
diff --git a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/template/java/JavaContext.java b/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/template/java/JavaContext.java
index 5aefdc1..d9062e6 100644
--- a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/template/java/JavaContext.java
+++ b/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/template/java/JavaContext.java
@@ -18,29 +18,14 @@
 package org.eclipse.jdt.internal.corext.template.java;
 
 import java.lang.reflect.InvocationTargetException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-
-import org.eclipse.swt.widgets.Shell;
 
 import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.NullProgressMonitor;
 
 import org.eclipse.jface.dialogs.MessageDialog;
 import org.eclipse.jface.preference.IPreferenceStore;
 
 import org.eclipse.jface.text.BadLocationException;
-import org.eclipse.jface.text.BadPositionCategoryException;
-import org.eclipse.jface.text.DefaultPositionUpdater;
 import org.eclipse.jface.text.IDocument;
-import org.eclipse.jface.text.IPositionUpdater;
 import org.eclipse.jface.text.IRegion;
 import org.eclipse.jface.text.Position;
 import org.eclipse.jface.text.TextUtilities;
@@ -48,40 +33,14 @@
 import org.eclipse.jface.text.templates.TemplateBuffer;
 import org.eclipse.jface.text.templates.TemplateContextType;
 import org.eclipse.jface.text.templates.TemplateException;
-import org.eclipse.jface.text.templates.TemplateTranslator;
 import org.eclipse.jface.text.templates.TemplateVariable;
-import org.eclipse.jface.text.templates.TemplateVariableType;
 
-import org.eclipse.jdt.core.Flags;
 import org.eclipse.jdt.core.ICompilationUnit;
-import org.eclipse.jdt.core.IField;
-import org.eclipse.jdt.core.IJavaElement;
 import org.eclipse.jdt.core.IJavaProject;
-import org.eclipse.jdt.core.IMethod;
-import org.eclipse.jdt.core.IType;
-import org.eclipse.jdt.core.JavaCore;
-import org.eclipse.jdt.core.JavaModelException;
-import org.eclipse.jdt.core.NamingConventions;
-import org.eclipse.jdt.core.Signature;
 import org.eclipse.jdt.core.dom.CompilationUnit;
-import org.eclipse.jdt.core.dom.SimpleName;
-import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
-import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext;
-import org.eclipse.jdt.core.manipulation.SharedASTProviderCore;
-import org.eclipse.jdt.core.manipulation.TypeKinds;
-import org.eclipse.jdt.core.manipulation.TypeNameMatchCollector;
-import org.eclipse.jdt.core.search.IJavaSearchConstants;
-import org.eclipse.jdt.core.search.IJavaSearchScope;
-import org.eclipse.jdt.core.search.SearchEngine;
-import org.eclipse.jdt.core.search.SearchPattern;
-import org.eclipse.jdt.core.search.TypeNameMatch;
 
-import org.eclipse.jdt.internal.core.manipulation.StubUtility;
-import org.eclipse.jdt.internal.core.manipulation.dom.ASTResolving;
 import org.eclipse.jdt.internal.core.manipulation.util.Strings;
-import org.eclipse.jdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext;
 import org.eclipse.jdt.internal.corext.template.java.CompilationUnitCompletion.Variable;
-import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
 
 import org.eclipse.jdt.ui.PreferenceConstants;
 
@@ -94,19 +53,9 @@
 /**
  * A context for Java source.
  */
-public class JavaContext extends CompilationUnitContext {
+public class JavaContext extends CompilationUnitContext implements IJavaContext {
 
-	/** A code completion requester for guessing local variable names. */
-	private CompilationUnitCompletion fCompletion;
-	/**
-	 * The list of used local names.
-	 * @since 3.3
-	 */
-	private Set<String> fUsedNames= new HashSet<>();
-	private Map<String, MultiVariable> fVariables= new HashMap<>();
-	private ImportRewrite fImportRewrite;
-
-	private Set<String> fCompatibleContextTypeIds;
+	private JavaContextCore fJavaContextCore;
 
 	/**
 	 * Creates a java template context.
@@ -119,6 +68,7 @@
 	 */
 	public JavaContext(TemplateContextType type, IDocument document, int completionOffset, int completionLength, ICompilationUnit compilationUnit) {
 		super(type, document, completionOffset, completionLength, compilationUnit);
+		this.fJavaContextCore = new JavaContextCore(this, type, document, completionOffset, completionLength, compilationUnit);
 	}
 
 	/**
@@ -132,6 +82,7 @@
 	 */
 	public JavaContext(TemplateContextType type, IDocument document, Position completionPosition, ICompilationUnit compilationUnit) {
 		super(type, document, completionPosition, compilationUnit);
+		this.fJavaContextCore = new JavaContextCore(this, type, document, completionPosition, compilationUnit);
 	}
 
 	/**
@@ -140,9 +91,7 @@
 	 * @param contextTypeId the context type to accept
 	 */
 	public void addCompatibleContextType(String contextTypeId) {
-		if (fCompatibleContextTypeIds == null)
-			fCompatibleContextTypeIds= new HashSet<>();
-		fCompatibleContextTypeIds.add(contextTypeId);
+		this.fJavaContextCore.addCompatibleContextType(contextTypeId);
 	}
 
 
@@ -169,27 +118,7 @@
 	 */
 	@Override
 	public TemplateBuffer evaluate(Template template) throws BadLocationException, TemplateException {
-		clear();
-
-		if (!canEvaluate(template))
-			throw new TemplateException(JavaTemplateMessages.Context_error_cannot_evaluate);
-
-		TemplateTranslator translator= new TemplateTranslator() {
-			@Override
-			protected TemplateVariable createVariable(TemplateVariableType type, String name, int[] offsets) {
-//				TemplateVariableResolver resolver= getContextType().getResolver(type.getName());
-//				return resolver.createVariable();
-
-				MultiVariable variable= new JavaVariable(type, name, offsets);
-				fVariables.put(name, variable);
-				return variable;
-			}
-		};
-		TemplateBuffer buffer= translator.translate(template);
-
-		getContextType().resolve(buffer, this);
-
-		rewriteImports();
+		TemplateBuffer buffer= this.fJavaContextCore.evaluate(template);
 
 		IPreferenceStore prefs= JavaPlugin.getDefault().getPreferenceStore();
 		boolean useCodeFormatter= prefs.getBoolean(PreferenceConstants.TEMPLATES_USE_CODEFORMATTER);
@@ -198,62 +127,15 @@
 		JavaFormatter formatter= new JavaFormatter(TextUtilities.getDefaultLineDelimiter(getDocument()), getIndentation(), useCodeFormatter, project);
 		formatter.format(buffer, this);
 
-		clear();
-
 		return buffer;
 	}
 
-	private void clear() {
-		fUsedNames.clear();
-		fImportRewrite= null;
-	}
-
 	/*
 	 * @see TemplateContext#canEvaluate(Template templates)
 	 */
 	@Override
 	public boolean canEvaluate(Template template) {
-		if (!hasCompatibleContextType(template))
-			return false;
-
-		if (this.isForceEvaluation())
-			return true;
-
-		String key= getKey().toLowerCase();
-		if (key.length() > 0 || !isAfterDot()) {
-			String templateName= template.getName().toLowerCase();
-			return JavaCore.ENABLED.equals(JavaCore.getOption(JavaCore.CODEASSIST_SUBSTRING_MATCH))
-					? templateName.contains(key)
-					: templateName.startsWith(key);
-		}
-		return false;
-	}
-
-	private boolean isAfterDot() {
-		try {
-			IDocument document= getDocument();
-			int offset= getCompletionOffset();
-			return document.get(offset - 1, 1).charAt(0) == '.';
-		} catch (BadLocationException e) {
-			return false;
-		}
-	}
-
-	private boolean hasCompatibleContextType(Template template) {
-		String key= getKey();
-		if (template.matches(key, getContextType().getId()))
-			return true;
-
-		if (fCompatibleContextTypeIds == null)
-			return false;
-
-		Iterator<String> iter= fCompatibleContextTypeIds.iterator();
-		while (iter.hasNext()) {
-			if (template.matches(key, iter.next()))
-				return true;
-		}
-
-		return false;
+		return this.fJavaContextCore.canEvaluate(template);
 	}
 
 	/*
@@ -261,30 +143,7 @@
 	 */
 	@Override
 	public int getStart() {
-
-		if (this.isManaged() && getCompletionLength() > 0)
-			return super.getStart();
-
-		try {
-			IDocument document= getDocument();
-
-			int start= getCompletionOffset();
-			int end= getCompletionOffset() + getCompletionLength();
-
-			while (start != 0 && Character.isUnicodeIdentifierPart(document.getChar(start - 1)))
-				start--;
-
-			while (start != end && Character.isWhitespace(document.getChar(start)))
-				start++;
-
-			if (start == end)
-				start= getCompletionOffset();
-
-				return start;
-
-		} catch (BadLocationException e) {
-			return super.getStart();
-		}
+		return this.fJavaContextCore.getStart();
 	}
 
 	/*
@@ -292,24 +151,7 @@
 	 */
 	@Override
 	public int getEnd() {
-
-		if (this.isManaged() || getCompletionLength() == 0)
-			return super.getEnd();
-
-		try {
-			IDocument document= getDocument();
-
-			int start= getCompletionOffset();
-			int end= getCompletionOffset() + getCompletionLength();
-
-			while (start != end && Character.isWhitespace(document.getChar(end - 1)))
-				end--;
-
-			return end;
-
-		} catch (BadLocationException e) {
-			return super.getEnd();
-		}
+		return this.fJavaContextCore.getEnd();
 	}
 
 	/*
@@ -317,22 +159,7 @@
 	 */
 	@Override
 	public String getKey() {
-
-		if (getCompletionLength() == 0)
-			return super.getKey();
-
-		try {
-			IDocument document= getDocument();
-
-			int start= getStart();
-			int end= getCompletionOffset();
-			return start <= end
-				? document.get(start, end - start)
-				: ""; //$NON-NLS-1$
-
-		} catch (BadLocationException e) {
-			return super.getKey();
-		}
+		return this.fJavaContextCore.getKey();
 	}
 
 	/**
@@ -341,79 +168,34 @@
 	 * @return the character before the start position of the completion
 	 */
 	public char getCharacterBeforeStart() {
-		int start= getStart();
-
-		try {
-			return start == 0
-				? ' '
-				: getDocument().getChar(start - 1);
-
-		} catch (BadLocationException e) {
-			return ' ';
-		}
+		return this.fJavaContextCore.getCharacterBeforeStart();
 	}
 
-	private static void handleException(Shell shell, Exception e) {
+	@Override
+	public void handleException(Exception e) {
 		String title= JavaTemplateMessages.JavaContext_error_title;
 		if (e instanceof CoreException)
-			ExceptionHandler.handle((CoreException)e, shell, title, null);
+			ExceptionHandler.handle((CoreException)e, null, title, null);
 		else if (e instanceof InvocationTargetException)
-			ExceptionHandler.handle((InvocationTargetException)e, shell, title, null);
+			ExceptionHandler.handle((InvocationTargetException)e, null, title, null);
 		else {
 			JavaPlugin.log(e);
 			String message= e.getMessage();
 			if (message == null) {
 				message= JavaTemplateMessages.JavaContext_unexpected_error_message;
 			}
-			MessageDialog.openError(shell, title, message);
+			MessageDialog.openError(null, title, message);
 		}
 	}
 
-	private CompilationUnitCompletion getCompletion() {
-		ICompilationUnit compilationUnit= getCompilationUnit();
-		if (fCompletion == null) {
-			fCompletion= new CompilationUnitCompletion(compilationUnit);
-
-			if (compilationUnit != null) {
-				try {
-					compilationUnit.codeComplete(getStart(), fCompletion);
-				} catch (JavaModelException e) {
-					// ignore
-				}
-			}
-		}
-
-		return fCompletion;
-	}
-
 	/**
 	 * Returns the names of arrays available in the current {@link CompilationUnit}'s scope.
 	 *
 	 * @return the names of local arrays available in the current {@link CompilationUnit}'s scope
 	 */
+	@Override
 	public Variable[] getArrays() {
-		Variable[] arrays= getCompletion().findArraysInCurrentScope();
-		arrange(arrays);
-		return arrays;
-	}
-
-	/**
-	 * Sorts already used variables behind any that are not yet used.
-	 *
-	 * @param variables the variables to sort
-	 * @since 3.3
-	 */
-	private void arrange(Variable[] variables) {
-		Arrays.sort(variables, new Comparator<Variable>() {
-			@Override
-			public int compare(Variable o1, Variable o2) {
-				return rank(o1) - rank(o2);
-			}
-
-			private int rank(Variable l) {
-				return fUsedNames.contains(l.getName()) ? 1 : 0;
-			}
-		});
+		return this.fJavaContextCore.getArrays();
 	}
 
 	/**
@@ -423,10 +205,9 @@
 	 * @return the names of local variables matching <code>type</code>
 	 * @since 3.3
 	 */
+	@Override
 	public Variable[] getLocalVariables(String type) {
-		Variable[] localVariables= getCompletion().findLocalVariables(type);
-		arrange(localVariables);
-		return localVariables;
+		return this.fJavaContextCore.getLocalVariables(type);
 	}
 
 	/**
@@ -436,10 +217,9 @@
 	 * @return the names of fields matching <code>type</code>
 	 * @since 3.3
 	 */
+	@Override
 	public Variable[] getFields(String type) {
-		Variable[] fields= getCompletion().findFieldVariables(type);
-		arrange(fields);
-		return fields;
+		return this.fJavaContextCore.getFields(type);
 	}
 
 	/**
@@ -447,47 +227,23 @@
 	 *
 	 * @return the names of iterables or arrays available in the current {@link CompilationUnit}'s scope
 	 */
+	@Override
 	public Variable[] getIterables() {
-		Variable[] iterables= getCompletion().findIterablesInCurrentScope();
-		arrange(iterables);
-		return iterables;
+		return this.fJavaContextCore.getIterables();
 	}
 
+	@Override
 	public void markAsUsed(String name) {
-		fUsedNames.add(name);
+		this.fJavaContextCore.markAsUsed(name);
 	}
 
+	@Override
 	public String[] suggestVariableNames(String type) throws IllegalArgumentException {
-		String[] excludes= computeExcludes();
-		// TODO erasure, arrays, etc.
-		String[] result= suggestVariableName(type, excludes);
-		return result;
+		return this.fJavaContextCore.suggestVariableNames(type);
 	}
 
 	String[] computeExcludes() {
-		String[] excludes= getCompletion().getLocalVariableNames();
-		if (!fUsedNames.isEmpty()) {
-			String[] allExcludes= new String[fUsedNames.size() + excludes.length];
-			System.arraycopy(excludes, 0, allExcludes, 0, excludes.length);
-			System.arraycopy(fUsedNames.toArray(), 0, allExcludes, 0, fUsedNames.size());
-			excludes= allExcludes;
-		}
-		return excludes;
-	}
-
-	private String[] suggestVariableName(String type, String[] excludes) throws IllegalArgumentException {
-		int dim=0;
-		while (type.endsWith("[]")) {//$NON-NLS-1$
-			dim++;
-			type= type.substring(0, type.length() - 2);
-		}
-
-		IJavaProject project= getJavaProject();
-		if (project != null)
-			return StubUtility.getVariableNameSuggestions(NamingConventions.VK_LOCAL, project, type, dim, Arrays.asList(excludes), true);
-
-		// fallback if we lack proper context: roll-our own lowercasing
-		return new String[] {Signature.getSimpleName(type).toLowerCase()};
+		return this.fJavaContextCore.computeExcludes();
 	}
 
 	/**
@@ -500,45 +256,9 @@
 	 * 	It is fully qualified, if an import conflict prevented the import.
 	 * @since 3.4
 	 */
+	@Override
 	public String addImport(String type) {
-		if (isReadOnly())
-			return type;
-
-		ICompilationUnit cu= getCompilationUnit();
-		if (cu == null)
-			return type;
-
-		try {
-			boolean qualified= type.indexOf('.') != -1;
-			if (!qualified) {
-				IJavaSearchScope searchScope= SearchEngine.createJavaSearchScope(new IJavaElement[] { cu.getJavaProject() });
-				SimpleName nameNode= null;
-				TypeNameMatch[] matches= findAllTypes(type, searchScope, nameNode, null, cu);
-				if (matches.length != 1) // only add import if we have a single match
-					return type;
-				type= matches[0].getFullyQualifiedName();
-			}
-
-			CompilationUnit root= getASTRoot(cu);
-			if (fImportRewrite == null) {
-				if (root == null) {
-					fImportRewrite= StubUtility.createImportRewrite(cu, true);
-				} else {
-					fImportRewrite= StubUtility.createImportRewrite(root, true);
-				}
-			}
-
-			ImportRewriteContext context;
-			if (root == null)
-				context= null;
-			else
-				context= new ContextSensitiveImportRewriteContext(root, getCompletionOffset(), fImportRewrite);
-
-			return fImportRewrite.addImport(type, context);
-		} catch (JavaModelException e) {
-			handleException(null, e);
-			return type;
-		}
+		return this.fJavaContextCore.addImport(type);
 	}
 
 	/**
@@ -551,198 +271,12 @@
 	 * @since 3.4
 	 */
 	public String addStaticImport(String qualifiedMemberName) {
-		if (isReadOnly())
-			return qualifiedMemberName;
-
-		ICompilationUnit cu= getCompilationUnit();
-		if (cu == null)
-			return qualifiedMemberName;
-
-		int memberOffset= qualifiedMemberName.lastIndexOf('.');
-		if (memberOffset == -1)
-			return qualifiedMemberName;
-
-		String typeName= qualifiedMemberName.substring(0, memberOffset);
-		String memberName= qualifiedMemberName.substring(memberOffset + 1, qualifiedMemberName.length());
-		try {
-			boolean isField;
-			if ("*".equals(memberName)) { //$NON-NLS-1$
-				isField= true;
-			} else {
-				IJavaProject javaProject= cu.getJavaProject();
-
-				IType type= javaProject.findType(typeName);
-				if (type == null)
-					return qualifiedMemberName;
-
-				IField field= type.getField(memberName);
-				if (field.exists()) {
-					isField= true;
-				} else if (hasMethod(type, memberName)) {
-					isField= false;
-				} else {
-					return qualifiedMemberName;
-				}
-			}
-
-			CompilationUnit root= getASTRoot(cu);
-			if (fImportRewrite == null) {
-				if (root == null) {
-					fImportRewrite= StubUtility.createImportRewrite(cu, true);
-				} else {
-					fImportRewrite= StubUtility.createImportRewrite(root, true);
-				}
-			}
-
-			ImportRewriteContext context;
-			if (root == null)
-				context= null;
-			else
-				context= new ContextSensitiveImportRewriteContext(root, getCompletionOffset(), fImportRewrite);
-
-			return fImportRewrite.addStaticImport(typeName, memberName, isField, context);
-		} catch (JavaModelException e) {
-			handleException(null, e);
-			return typeName;
-		}
+		return this.fJavaContextCore.addStaticImport(qualifiedMemberName);
 	}
 
-	/**
-	 * Does <code>type</code> contain a method with <code>name</code>?
-	 *
-	 * @param type the type to inspect
-	 * @param name the name of the method to search for
-	 * @return true if has such a method
-	 * @throws JavaModelException if methods could not be retrieved
-	 * @since 3.4
-	 */
-	private boolean hasMethod(IType type, String name) throws JavaModelException {
-		IMethod[] methods= type.getMethods();
-		for (int i= 0; i < methods.length; i++) {
-			if (name.equals(methods[i].getElementName()))
-				return true;
-		}
-
-		return false;
-	}
-
-	private void rewriteImports() {
-		if (fImportRewrite == null)
-			return;
-
-		if (isReadOnly())
-			return;
-
-		ICompilationUnit cu= getCompilationUnit();
-		if (cu == null)
-			return;
-
-		try {
-			Position position= new Position(getCompletionOffset(), 0);
-			IDocument document= getDocument();
-			final String category= "__template_position_importer" + System.currentTimeMillis(); //$NON-NLS-1$
-			IPositionUpdater updater= new DefaultPositionUpdater(category);
-			document.addPositionCategory(category);
-			document.addPositionUpdater(updater);
-			document.addPosition(position);
-
-			try {
-				JavaModelUtil.applyEdit(cu, fImportRewrite.rewriteImports(null), false, null);
-
-				setCompletionOffset(position.getOffset());
-			} catch (CoreException e) {
-				handleException(null, e);
-			} finally {
-				document.removePosition(position);
-				document.removePositionUpdater(updater);
-				document.removePositionCategory(category);
-			}
-		} catch (BadLocationException e) {
-			handleException(null, e);
-		} catch (BadPositionCategoryException e) {
-			handleException(null, e);
-		}
-	}
-
-	private CompilationUnit getASTRoot(ICompilationUnit compilationUnit) {
-		return SharedASTProviderCore.getAST(compilationUnit, SharedASTProviderCore.WAIT_NO, new NullProgressMonitor());
-	}
-
-	/*
-	 * Finds a type by the simple name. From AddImportsOperation
-	 */
-	private TypeNameMatch[] findAllTypes(String simpleTypeName, IJavaSearchScope searchScope, SimpleName nameNode, IProgressMonitor monitor, ICompilationUnit cu) throws JavaModelException {
-		boolean is50OrHigher= JavaModelUtil.is50OrHigher(cu.getJavaProject());
-
-		int typeKinds= TypeKinds.ALL_TYPES;
-		if (nameNode != null) {
-			typeKinds= ASTResolving.getPossibleTypeKinds(nameNode, is50OrHigher);
-		}
-
-		ArrayList<TypeNameMatch> typeInfos= new ArrayList<>();
-		TypeNameMatchCollector requestor= new TypeNameMatchCollector(typeInfos);
-		new SearchEngine().searchAllTypeNames(null, 0, simpleTypeName.toCharArray(), SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE, getSearchForConstant(typeKinds), searchScope, requestor, IJavaSearchConstants.FORCE_IMMEDIATE_SEARCH, monitor);
-
-		ArrayList<TypeNameMatch> typeRefsFound= new ArrayList<>(typeInfos.size());
-		for (int i= 0, len= typeInfos.size(); i < len; i++) {
-			TypeNameMatch curr= typeInfos.get(i);
-			if (curr.getPackageName().length() > 0) { // do not suggest imports from the default package
-				if (isOfKind(curr, typeKinds, is50OrHigher) && isVisible(curr, cu)) {
-					typeRefsFound.add(curr);
-				}
-			}
-		}
-		return typeRefsFound.toArray(new TypeNameMatch[typeRefsFound.size()]);
-	}
-
-	private int getSearchForConstant(int typeKinds) {
-		final int CLASSES= TypeKinds.CLASSES;
-		final int INTERFACES= TypeKinds.INTERFACES;
-		final int ENUMS= TypeKinds.ENUMS;
-		final int ANNOTATIONS= TypeKinds.ANNOTATIONS;
-
-		switch (typeKinds & (CLASSES | INTERFACES | ENUMS | ANNOTATIONS)) {
-			case CLASSES: return IJavaSearchConstants.CLASS;
-			case INTERFACES: return IJavaSearchConstants.INTERFACE;
-			case ENUMS: return IJavaSearchConstants.ENUM;
-			case ANNOTATIONS: return IJavaSearchConstants.ANNOTATION_TYPE;
-			case CLASSES | INTERFACES: return IJavaSearchConstants.CLASS_AND_INTERFACE;
-			case CLASSES | ENUMS: return IJavaSearchConstants.CLASS_AND_ENUM;
-			default: return IJavaSearchConstants.TYPE;
-		}
-	}
-
-	private boolean isOfKind(TypeNameMatch curr, int typeKinds, boolean is50OrHigher) {
-		int flags= curr.getModifiers();
-		if (Flags.isAnnotation(flags)) {
-			return is50OrHigher && ((typeKinds & TypeKinds.ANNOTATIONS) != 0);
-		}
-		if (Flags.isEnum(flags)) {
-			return is50OrHigher && ((typeKinds & TypeKinds.ENUMS) != 0);
-		}
-		if (Flags.isInterface(flags)) {
-			return (typeKinds & TypeKinds.INTERFACES) != 0;
-		}
-		return (typeKinds & TypeKinds.CLASSES) != 0;
-	}
-
-
-	private boolean isVisible(TypeNameMatch curr, ICompilationUnit cu) {
-		int flags= curr.getModifiers();
-		if (Flags.isPrivate(flags)) {
-			return false;
-		}
-		if (Flags.isPublic(flags) || Flags.isProtected(flags)) {
-			return true;
-		}
-		return curr.getPackageName().equals(cu.getParent().getElementName());
-	}
-
+	@Override
 	public TemplateVariable getTemplateVariable(String name) {
-		TemplateVariable variable= fVariables.get(name);
-		if (variable != null && !variable.isResolved())
-			getContextType().resolve(variable, this);
-		return variable;
+		return this.fJavaContextCore.getTemplateVariable(name);
 	}
 
 	/**
@@ -753,6 +287,7 @@
 	 * @param slave the dependent variable
 	 * @since 3.3
 	 */
+	@Override
 	public void addDependency(MultiVariable master, MultiVariable slave) {
 		MultiVariableGuess guess= getMultiVariableGuess();
 		if (guess == null) {