Bug 574940: refactor Code completion

Change-Id: Ia6cd4e90df4388b4d8ed66172aa35484a519a53f
diff --git a/features/org.eclipse.ease.ui.feature/feature.xml b/features/org.eclipse.ease.ui.feature/feature.xml
index 3729105..8d00715 100644
--- a/features/org.eclipse.ease.ui.feature/feature.xml
+++ b/features/org.eclipse.ease.ui.feature/feature.xml
@@ -51,7 +51,6 @@
          id="org.eclipse.ease.ui.completions.java"
          download-size="0"
          install-size="0"
-         version="0.0.0"
-         fragment="true"/>
+         version="0.0.0"/>
 
 </feature>
diff --git a/plugins/org.eclipse.ease.lang.groovy/META-INF/MANIFEST.MF b/plugins/org.eclipse.ease.lang.groovy/META-INF/MANIFEST.MF
index daf15e6..0c1cb91 100644
--- a/plugins/org.eclipse.ease.lang.groovy/META-INF/MANIFEST.MF
+++ b/plugins/org.eclipse.ease.lang.groovy/META-INF/MANIFEST.MF
@@ -4,7 +4,8 @@
 Bundle-SymbolicName: org.eclipse.ease.lang.groovy;singleton:=true
 Bundle-Version: 0.9.0.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Require-Bundle: org.eclipse.ease
+Require-Bundle: org.eclipse.ease,
+ org.eclipse.ease.ui
 Bundle-Vendor: Eclipse.org
 Export-Package: org.eclipse.ease.lang.groovy
 Automatic-Module-Name: org.eclipse.ease.lang.groovy
diff --git a/plugins/org.eclipse.ease.lang.groovy/src/org/eclipse/ease/lang/groovy/GroovyCodeParser.java b/plugins/org.eclipse.ease.lang.groovy/src/org/eclipse/ease/lang/groovy/GroovyCodeParser.java
index e52947d..123a9a7 100644
--- a/plugins/org.eclipse.ease.lang.groovy/src/org/eclipse/ease/lang/groovy/GroovyCodeParser.java
+++ b/plugins/org.eclipse.ease.lang.groovy/src/org/eclipse/ease/lang/groovy/GroovyCodeParser.java
@@ -13,6 +13,9 @@
 package org.eclipse.ease.lang.groovy;
 
 import org.eclipse.ease.AbstractCodeParser;
+import org.eclipse.ease.ICompletionContext;
+import org.eclipse.ease.IScriptEngine;
+import org.eclipse.ease.ui.completion.BasicContext;
 
 public class GroovyCodeParser extends AbstractCodeParser {
 
@@ -39,4 +42,10 @@
 	protected String getBlockCommentEndToken() {
 		return BLOCK_COMMENT_END;
 	}
+
+	@Override
+	public ICompletionContext getContext(IScriptEngine scriptEngine, Object resource, String contents, int position, int selectionRange) {
+		return (scriptEngine != null) ? new BasicContext(scriptEngine, contents, position)
+				: new BasicContext(GroovyHelper.getScriptType(), resource, contents, position);
+	}
 }
\ No newline at end of file
diff --git a/plugins/org.eclipse.ease.lang.groovy/src/org/eclipse/ease/lang/groovy/GroovyHelper.java b/plugins/org.eclipse.ease.lang.groovy/src/org/eclipse/ease/lang/groovy/GroovyHelper.java
index 6ef1024..9bc9b0a 100644
--- a/plugins/org.eclipse.ease.lang.groovy/src/org/eclipse/ease/lang/groovy/GroovyHelper.java
+++ b/plugins/org.eclipse.ease.lang.groovy/src/org/eclipse/ease/lang/groovy/GroovyHelper.java
@@ -15,7 +15,24 @@
 
 import java.util.regex.Pattern;
 
-public class GroovyHelper {
+import org.eclipse.ease.service.IScriptService;
+import org.eclipse.ease.service.ScriptService;
+import org.eclipse.ease.service.ScriptType;
+
+public final class GroovyHelper {
+
+	/** Script type identifier for JavaScript. Must match with the script type 'name' from plugin.xml. */
+	public static final String SCRIPT_TYPE_GROOVY = "Groovy";
+
+	/**
+	 * Get the {@link ScriptType} for java script.
+	 *
+	 * @return script type definition
+	 */
+	public static ScriptType getScriptType() {
+		final IScriptService scriptService = ScriptService.getInstance();
+		return scriptService.getAvailableScriptTypes().get(SCRIPT_TYPE_GROOVY);
+	}
 
 	public static boolean isSaveName(final String identifier) {
 		return Pattern.matches("[a-zA-Z_$][a-zA-Z0-9_$]*", identifier);
diff --git a/plugins/org.eclipse.ease.lang.javascript.ui/src/org/eclipse/ease/lang/javascript/ui/completion/JavaScriptEditorCompletionComputer.java b/plugins/org.eclipse.ease.lang.javascript.ui/src/org/eclipse/ease/lang/javascript/ui/completion/JavaScriptEditorCompletionComputer.java
index 196c12b..6731ee2 100644
--- a/plugins/org.eclipse.ease.lang.javascript.ui/src/org/eclipse/ease/lang/javascript/ui/completion/JavaScriptEditorCompletionComputer.java
+++ b/plugins/org.eclipse.ease.lang.javascript.ui/src/org/eclipse/ease/lang/javascript/ui/completion/JavaScriptEditorCompletionComputer.java
@@ -13,6 +13,7 @@
 
 package org.eclipse.ease.lang.javascript.ui.completion;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
@@ -23,7 +24,7 @@
 import org.eclipse.ease.service.IScriptService;
 import org.eclipse.ease.service.ScriptType;
 import org.eclipse.ease.ui.completion.CodeCompletionAggregator;
-import org.eclipse.ease.ui.completion.ICompletionProvider;
+import org.eclipse.ease.ui.completion.provider.ICompletionProvider;
 import org.eclipse.jface.text.BadLocationException;
 import org.eclipse.jface.text.IDocument;
 import org.eclipse.jface.text.contentassist.ICompletionProposal;
@@ -43,10 +44,6 @@
  * JSDT JavaScript Editors. So all editors of this type share the same instance.
  */
 public class JavaScriptEditorCompletionComputer implements IJavaCompletionProposalComputer {
-	/**
-	 * {@link CodeCompletionAggregator} to handle the actual creation of proposals.
-	 */
-	private final CodeCompletionAggregator fCompletionAggregator = new CodeCompletionAggregator();
 
 	/**
 	 * Constructor sets up {@link CodeCompletionAggregator} and loads registered {@link ICompletionProvider}.
@@ -54,9 +51,6 @@
 	public JavaScriptEditorCompletionComputer() {
 		final IScriptService scriptService = PlatformUI.getWorkbench().getService(IScriptService.class);
 		final ScriptType scriptType = scriptService.getAvailableScriptTypes().get(JavaScriptHelper.SCRIPT_TYPE_JAVASCRIPT);
-
-		fCompletionAggregator.setScriptType(scriptType);
-		fCompletionAggregator.setCodeParser(scriptType.getCodeParser());
 	}
 
 	@Override
@@ -80,7 +74,10 @@
 
 					final int cursorPosition = context.getInvocationOffset();
 					final int selectionRange = context.getViewer().getSelectedRange().y;
-					return fCompletionAggregator.getCompletionProposals(resource, relevantText, cursorPosition, selectionRange, monitor);
+
+					final CodeCompletionAggregator aggregator = new CodeCompletionAggregator(resource, JavaScriptHelper.getScriptType());
+
+					return new ArrayList<>(aggregator.getProposals(relevantText, cursorPosition, monitor));
 
 				} catch (final BadLocationException e) {
 					Logger.error(PluginConstants.PLUGIN_ID, "Failed to calculate proposals for JavaScript editor", e);
diff --git a/plugins/org.eclipse.ease.lang.javascript/src/org/eclipse/ease/lang/javascript/JavaScriptCodeParser.java b/plugins/org.eclipse.ease.lang.javascript/src/org/eclipse/ease/lang/javascript/JavaScriptCodeParser.java
index eb2ba8d..b2e0c38 100644
--- a/plugins/org.eclipse.ease.lang.javascript/src/org/eclipse/ease/lang/javascript/JavaScriptCodeParser.java
+++ b/plugins/org.eclipse.ease.lang.javascript/src/org/eclipse/ease/lang/javascript/JavaScriptCodeParser.java
@@ -12,11 +12,11 @@
  *******************************************************************************/
 package org.eclipse.ease.lang.javascript;
 
+import org.eclipse.ease.AbstractCodeParser;
 import org.eclipse.ease.ICompletionContext;
 import org.eclipse.ease.IScriptEngine;
-import org.eclipse.ease.ui.completion.AbstractCompletionParser;
 
-public class JavaScriptCodeParser extends AbstractCompletionParser {
+public class JavaScriptCodeParser extends AbstractCodeParser {
 
 	private static final String LINE_COMMENT = "//";
 	private static final String BLOCK_COMMENT_START = "/*";
@@ -44,9 +44,7 @@
 
 	@Override
 	public ICompletionContext getContext(IScriptEngine scriptEngine, Object resource, String contents, int position, int selectionRange) {
-		final JavaScriptCompletionContext context = new JavaScriptCompletionContext(scriptEngine);
-		context.calculateContext(resource, contents, position, selectionRange);
-
-		return context;
+		return (scriptEngine != null) ? new JavaScriptCompletionContext(scriptEngine, contents, position)
+				: new JavaScriptCompletionContext(resource, contents, position);
 	}
 }
diff --git a/plugins/org.eclipse.ease.lang.javascript/src/org/eclipse/ease/lang/javascript/JavaScriptCompletionContext.java b/plugins/org.eclipse.ease.lang.javascript/src/org/eclipse/ease/lang/javascript/JavaScriptCompletionContext.java
index a0c8070..4dff27a 100644
--- a/plugins/org.eclipse.ease.lang.javascript/src/org/eclipse/ease/lang/javascript/JavaScriptCompletionContext.java
+++ b/plugins/org.eclipse.ease.lang.javascript/src/org/eclipse/ease/lang/javascript/JavaScriptCompletionContext.java
@@ -14,41 +14,69 @@
 package org.eclipse.ease.lang.javascript;
 
 import org.eclipse.ease.IScriptEngine;
-import org.eclipse.ease.ui.completion.CompletionContext;
+import org.eclipse.ease.ui.completion.BasicContext;
+import org.eclipse.ease.ui.completion.tokenizer.IVariablesResolver;
+import org.eclipse.ease.ui.completion.tokenizer.InputTokenizer;
 
-public class JavaScriptCompletionContext extends CompletionContext {
+public class JavaScriptCompletionContext extends BasicContext {
 
-	public JavaScriptCompletionContext(final IScriptEngine scriptEngine) {
-		super(scriptEngine, JavaScriptHelper.getScriptType());
+	public JavaScriptCompletionContext(IScriptEngine scriptEngine, String contents, int position) {
+		super(scriptEngine, contents, position);
+	}
+
+	public JavaScriptCompletionContext(Object resource, String contents, int position) {
+		super(JavaScriptHelper.getScriptType(), resource, contents, position);
 	}
 
 	@Override
-	protected boolean isLiteral(final char candidate) {
-		for (final char literal : "'\"".toCharArray()) {
-			if (candidate == literal)
-				return true;
+	protected InputTokenizer getInputTokenizer() {
+		if (getScriptEngine() != null)
+			return new JavaScriptInputTokenizer(v -> {
+				final Object variable = getScriptEngine().getVariable(v);
+				return (variable == null) ? null : variable.getClass();
+			});
+
+		return new JavaScriptInputTokenizer();
+	}
+
+	private final class JavaScriptInputTokenizer extends InputTokenizer {
+
+		private static final String PACKAGES_PREFIX = "Packages.";
+
+		private JavaScriptInputTokenizer() {
+			super();
 		}
 
-		return false;
-	}
+		private JavaScriptInputTokenizer(IVariablesResolver variablesResolver) {
+			super(variablesResolver);
+		}
 
-	@Override
-	protected String simplifyCode() {
-		final String code = super.simplifyCode();
-		if (code.startsWith("Packages."))
-			return code.substring("Packages.".length());
+		@Override
+		protected Class<?> getClass(String input) {
+			if (input.startsWith(PACKAGES_PREFIX))
+				return filterRhinoClasses(super.getClass(input.substring(PACKAGES_PREFIX.length())));
 
-		return code;
-	}
+			return filterRhinoClasses(super.getClass(input));
+		}
 
-	@Override
-	protected Class<? extends Object> getVariableClazz(final String name) {
-		final Class<? extends Object> clazz = super.getVariableClazz(name);
+		private Class<?> filterRhinoClasses(Class<?> clazz) {
+			if ((clazz != null) && (clazz.getName().startsWith("org.mozilla.javascript")))
+				return null;
 
-		// skip all rhino specific classes
-		if ((clazz != null) && (clazz.getName().startsWith("org.mozilla.javascript")))
-			return null;
+			return clazz;
+		}
 
-		return clazz;
+		@Override
+		protected Package getPackage(String input) {
+			if (input.startsWith(PACKAGES_PREFIX))
+				return super.getPackage(input.substring(PACKAGES_PREFIX.length()));
+
+			return super.getPackage(input);
+		}
+
+		@Override
+		protected boolean isLiteral(final char candidate) {
+			return ('"' == candidate) || ('\'' == candidate);
+		}
 	}
 }
diff --git a/plugins/org.eclipse.ease.lang.jvm.compiled/src/org/eclipse/ease/lang/jvm/compiled/JVMCompiledHeaderParser.java b/plugins/org.eclipse.ease.lang.jvm.compiled/src/org/eclipse/ease/lang/jvm/compiled/JVMCompiledHeaderParser.java
index 0812a6f..7c6feb9 100644
--- a/plugins/org.eclipse.ease.lang.jvm.compiled/src/org/eclipse/ease/lang/jvm/compiled/JVMCompiledHeaderParser.java
+++ b/plugins/org.eclipse.ease.lang.jvm.compiled/src/org/eclipse/ease/lang/jvm/compiled/JVMCompiledHeaderParser.java
@@ -13,6 +13,8 @@
 package org.eclipse.ease.lang.jvm.compiled;
 
 import org.eclipse.ease.AbstractCodeParser;
+import org.eclipse.ease.ICompletionContext;
+import org.eclipse.ease.IScriptEngine;
 
 public class JVMCompiledHeaderParser extends AbstractCodeParser {
 
@@ -39,4 +41,9 @@
 	protected boolean hasBlockComment() {
 		return true;
 	}
+
+	@Override
+	public ICompletionContext getContext(IScriptEngine scriptEngine, Object resource, String contents, int position, int selectionRange) {
+		return null;
+	}
 }
diff --git a/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/PythonCodeParser.java b/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/PythonCodeParser.java
index d735696..74c35c4 100644
--- a/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/PythonCodeParser.java
+++ b/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/PythonCodeParser.java
@@ -44,9 +44,7 @@
 
 	@Override
 	public ICompletionContext getContext(IScriptEngine scriptEngine, Object resource, String contents, int position, int selectionRange) {
-		final PythonCompletionContext context = new PythonCompletionContext(scriptEngine);
-		context.calculateContext(resource, contents, position, selectionRange);
-
-		return context;
+		return (scriptEngine != null) ? new PythonCompletionContext(scriptEngine, contents, position)
+				: new PythonCompletionContext(resource, contents, position);
 	}
 }
diff --git a/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/PythonCompletionContext.java b/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/PythonCompletionContext.java
index 0a7c399..79b45ce 100644
--- a/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/PythonCompletionContext.java
+++ b/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/PythonCompletionContext.java
@@ -14,30 +14,58 @@
 package org.eclipse.ease.lang.python;
 
 import org.eclipse.ease.IScriptEngine;
-import org.eclipse.ease.ui.completion.CompletionContext;
+import org.eclipse.ease.ui.completion.BasicContext;
+import org.eclipse.ease.ui.completion.tokenizer.IVariablesResolver;
+import org.eclipse.ease.ui.completion.tokenizer.InputTokenizer;
 
-public class PythonCompletionContext extends CompletionContext {
+public class PythonCompletionContext extends BasicContext {
 
-	public PythonCompletionContext(IScriptEngine scriptEngine) {
-		super(scriptEngine, PythonHelper.getScriptType());
+	public PythonCompletionContext(IScriptEngine scriptEngine, String contents, int position) {
+		super(scriptEngine, contents, position);
 	}
 
-	@Override
-	protected String simplifyCode() {
-		final String code = super.simplifyCode();
-
-		// XXX: API needs a review here, these simplifications are Py4J specific, not
-		// all Python code.
-		if (code.startsWith("jvm.gateway."))
-			return code.substring("jvm.gateway.".length());
-		if (code.startsWith("gateway."))
-			return code.substring("gateway.".length());
-
-		return code;
+	public PythonCompletionContext(Object resource, String contents, int position) {
+		super(PythonHelper.getScriptType(), resource, contents, position);
 	}
 
+	// @Override
+	// protected String simplifyCode() {
+	// final String code = super.simplifyCode();
+	//
+	// // XXX: API needs a review here, these simplifications are Py4J specific, not
+	// // all Python code.
+	// if (code.startsWith("jvm.gateway."))
+	// return code.substring("jvm.gateway.".length());
+	// if (code.startsWith("gateway."))
+	// return code.substring("gateway.".length());
+	//
+	// return code;
+	// }
+
 	@Override
-	protected boolean isLiteral(final char candidate) {
-		return "'\"".indexOf(candidate) != -1;
+	protected InputTokenizer getInputTokenizer() {
+		if (getScriptEngine() != null)
+			return new PythonInputTokenizer(v -> {
+				final Object variable = getScriptEngine().getVariable(v);
+				return variable == null ? null : variable.getClass();
+			});
+
+		return new PythonInputTokenizer();
+	}
+
+	private final class PythonInputTokenizer extends InputTokenizer {
+
+		private PythonInputTokenizer() {
+			super();
+		}
+
+		private PythonInputTokenizer(IVariablesResolver variablesResolver) {
+			super(variablesResolver);
+		}
+
+		@Override
+		protected boolean isLiteral(final char candidate) {
+			return ('"' == candidate) || ('\'' == candidate);
+		}
 	}
 }
diff --git a/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/ui/completion/PythonCompletionProviderWrapper.java b/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/ui/completion/PythonCompletionProviderWrapper.java
index 2ed64ca..9e18a95 100644
--- a/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/ui/completion/PythonCompletionProviderWrapper.java
+++ b/plugins/org.eclipse.ease.lang.python/src/org/eclipse/ease/lang/python/ui/completion/PythonCompletionProviderWrapper.java
@@ -10,6 +10,7 @@
  * Contributors:
  *     Martin Kloesch - initial API and implementation
  *******************************************************************************/
+
 package org.eclipse.ease.lang.python.ui.completion;
 
 import java.io.InputStream;
@@ -21,8 +22,8 @@
 import org.eclipse.ease.Logger;
 import org.eclipse.ease.lang.python.Activator;
 import org.eclipse.ease.lang.python.debugger.ResourceHelper;
-import org.eclipse.ease.ui.completion.ICompletionProvider;
 import org.eclipse.ease.ui.completion.ScriptCompletionProposal;
+import org.eclipse.ease.ui.completion.provider.ICompletionProvider;
 
 /**
  * {@link ICompletionProvider} dispatching actual completion calculation to Python.
@@ -43,13 +44,8 @@
 		fPythonProvider = provider;
 	}
 
-	/*
-	 * (non-Javadoc)
-	 *
-	 * @see org.eclipse.ease.ui.completion.ICompletionProvider#getProposals(org. eclipse.ease.ICompletionContext)
-	 */
 	@Override
-	public Collection<? extends ScriptCompletionProposal> getProposals(ICompletionContext context) {
+	public Collection<ScriptCompletionProposal> getProposals(ICompletionContext context) {
 		if (!isActive(context)) {
 			return new ArrayList<>();
 		}
@@ -78,11 +74,6 @@
 		return new ArrayList<>();
 	}
 
-	/*
-	 * (non-Javadoc)
-	 *
-	 * @see org.eclipse.ease.ui.completion.ICompletionProvider#isActive(org.eclipse. ease.ICompletionContext)
-	 */
 	@Override
 	public boolean isActive(ICompletionContext context) {
 		if (fPythonProvider == null) {
@@ -91,5 +82,4 @@
 			return fPythonProvider.isActive(context);
 		}
 	}
-
 }
diff --git a/plugins/org.eclipse.ease.lang.ruby/src/org/eclipse/ease/lang/ruby/RubyCodeParser.java b/plugins/org.eclipse.ease.lang.ruby/src/org/eclipse/ease/lang/ruby/RubyCodeParser.java
index 5a17c51..07044f7 100644
--- a/plugins/org.eclipse.ease.lang.ruby/src/org/eclipse/ease/lang/ruby/RubyCodeParser.java
+++ b/plugins/org.eclipse.ease.lang.ruby/src/org/eclipse/ease/lang/ruby/RubyCodeParser.java
@@ -13,6 +13,8 @@
 package org.eclipse.ease.lang.ruby;
 
 import org.eclipse.ease.AbstractCodeParser;
+import org.eclipse.ease.ICompletionContext;
+import org.eclipse.ease.IScriptEngine;
 
 public class RubyCodeParser extends AbstractCodeParser {
 
@@ -39,4 +41,9 @@
 	protected String getBlockCommentEndToken() {
 		return BLOCK_COMMENT_END;
 	}
+
+	@Override
+	public ICompletionContext getContext(IScriptEngine scriptEngine, Object resource, String contents, int position, int selectionRange) {
+		return null;
+	}
 }
\ No newline at end of file
diff --git a/plugins/org.eclipse.ease.ui.completions.java/META-INF/MANIFEST.MF b/plugins/org.eclipse.ease.ui.completions.java/META-INF/MANIFEST.MF
index 4f30ffc..763a6ac 100644
--- a/plugins/org.eclipse.ease.ui.completions.java/META-INF/MANIFEST.MF
+++ b/plugins/org.eclipse.ease.ui.completions.java/META-INF/MANIFEST.MF
@@ -4,12 +4,16 @@
 Bundle-SymbolicName: org.eclipse.ease.ui.completions.java;singleton:=true
 Bundle-Version: 0.9.0.qualifier
 Bundle-Vendor: Eclipse.org
-Fragment-Host: org.eclipse.ease.ui;bundle-version="0.9.0"
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Require-Bundle: org.eclipse.jdt.ui;bundle-version="[3.7.2,4.0.0)",
  org.eclipse.jdt.core;bundle-version="[3.7.3,4.0.0)",
- org.objectweb.asm;bundle-version="[3.3.1,10.0.0)"
-Export-Package: org.eclipse.ease.ui.completions.java.help.handlers;x-internal:=true,
- org.eclipse.ease.ui.completions.java.help.hovers.internal;x-internal:=true,
- org.eclipse.ease.ui.completions.java.provider;x-internal:=true
+ org.objectweb.asm;bundle-version="[3.3.1,10.0.0)",
+ org.eclipse.ease.ui,
+ org.eclipse.core.resources,
+ org.eclipse.core.runtime;bundle-version="3.22.0",
+ org.eclipse.jface.text;bundle-version="3.18.0",
+ org.eclipse.jface;bundle-version="3.22.200",
+ org.eclipse.ease
 Automatic-Module-Name: org.eclipse.ease.ui.completions.java
+Bundle-ActivationPolicy: lazy
+Bundle-ClassPath: .
diff --git a/plugins/org.eclipse.ease.ui.completions.java/build.properties b/plugins/org.eclipse.ease.ui.completions.java/build.properties
index 9bd91bb..b7ceb4c 100644
--- a/plugins/org.eclipse.ease.ui.completions.java/build.properties
+++ b/plugins/org.eclipse.ease.ui.completions.java/build.properties
@@ -2,6 +2,7 @@
 output.. = bin/
 bin.includes = META-INF/,\
                .,\
-               fragment.xml,\
+               plugin.xml,\
                icons/,\
-               about.html
+               about.html,\
+               resources/
\ No newline at end of file
diff --git a/plugins/org.eclipse.ease.ui.completions.java/fragment.xml b/plugins/org.eclipse.ease.ui.completions.java/plugin.xml
similarity index 96%
rename from plugins/org.eclipse.ease.ui.completions.java/fragment.xml
rename to plugins/org.eclipse.ease.ui.completions.java/plugin.xml
index b07ead4..f333a21 100644
--- a/plugins/org.eclipse.ease.ui.completions.java/fragment.xml
+++ b/plugins/org.eclipse.ease.ui.completions.java/plugin.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <?eclipse version="3.4"?>
-<fragment>
+<plugin>
    <extension
          point="org.eclipse.ease.ui.codeCompletionProvider">
       <codeCompletionProvider
@@ -14,4 +14,4 @@
       </codeCompletionProvider>
    </extension>
 
-</fragment>
+</plugin>
diff --git a/plugins/org.eclipse.ease.ui/resources/java10 classes.txt b/plugins/org.eclipse.ease.ui.completions.java/resources/java10 classes.txt
similarity index 100%
rename from plugins/org.eclipse.ease.ui/resources/java10 classes.txt
rename to plugins/org.eclipse.ease.ui.completions.java/resources/java10 classes.txt
diff --git a/plugins/org.eclipse.ease.ui/resources/java11 classes.txt b/plugins/org.eclipse.ease.ui.completions.java/resources/java11 classes.txt
similarity index 100%
rename from plugins/org.eclipse.ease.ui/resources/java11 classes.txt
rename to plugins/org.eclipse.ease.ui.completions.java/resources/java11 classes.txt
diff --git a/plugins/org.eclipse.ease.ui/resources/java12 classes.txt b/plugins/org.eclipse.ease.ui.completions.java/resources/java12 classes.txt
similarity index 100%
rename from plugins/org.eclipse.ease.ui/resources/java12 classes.txt
rename to plugins/org.eclipse.ease.ui.completions.java/resources/java12 classes.txt
diff --git a/plugins/org.eclipse.ease.ui/resources/java7 classes.txt b/plugins/org.eclipse.ease.ui.completions.java/resources/java7 classes.txt
similarity index 100%
rename from plugins/org.eclipse.ease.ui/resources/java7 classes.txt
rename to plugins/org.eclipse.ease.ui.completions.java/resources/java7 classes.txt
diff --git a/plugins/org.eclipse.ease.ui/resources/java8 classes.txt b/plugins/org.eclipse.ease.ui.completions.java/resources/java8 classes.txt
similarity index 100%
rename from plugins/org.eclipse.ease.ui/resources/java8 classes.txt
rename to plugins/org.eclipse.ease.ui.completions.java/resources/java8 classes.txt
diff --git a/plugins/org.eclipse.ease.ui/resources/java9 classes.txt b/plugins/org.eclipse.ease.ui.completions.java/resources/java9 classes.txt
similarity index 100%
rename from plugins/org.eclipse.ease.ui/resources/java9 classes.txt
rename to plugins/org.eclipse.ease.ui.completions.java/resources/java9 classes.txt
diff --git a/plugins/org.eclipse.ease.ui.completions.java/src/org/eclipse/ease/ui/completions/java/EaseUICompletionsJavaFragment.java b/plugins/org.eclipse.ease.ui.completions.java/src/org/eclipse/ease/ui/completions/java/EaseUICompletionsJavaFragment.java
deleted file mode 100644
index 67443ef..0000000
--- a/plugins/org.eclipse.ease.ui.completions.java/src/org/eclipse/ease/ui/completions/java/EaseUICompletionsJavaFragment.java
+++ /dev/null
@@ -1,18 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Kichwa Coders and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v2.0
- * which accompanies this distribution, and is available at
- * https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License_Identifier: EPL-2.0
- *
- * Contributors:
- *     Jonah Graham (Kichwa Coders) - initial API and implementation
- *******************************************************************************/
-package org.eclipse.ease.ui.completions.java;
-
-public class EaseUICompletionsJavaFragment {
-
-	public static final String FRAGMENT_ID = "org.eclipse.ease.ui.completions.java";
-}
diff --git a/plugins/org.eclipse.ease.ui.completions.java/src/org/eclipse/ease/ui/completions/java/provider/JavaClassCompletionProvider.java b/plugins/org.eclipse.ease.ui.completions.java/src/org/eclipse/ease/ui/completions/java/provider/JavaClassCompletionProvider.java
index e7b2632..0a7a253 100644
--- a/plugins/org.eclipse.ease.ui.completions.java/src/org/eclipse/ease/ui/completions/java/provider/JavaClassCompletionProvider.java
+++ b/plugins/org.eclipse.ease.ui.completions.java/src/org/eclipse/ease/ui/completions/java/provider/JavaClassCompletionProvider.java
@@ -12,48 +12,92 @@
  *******************************************************************************/
 package org.eclipse.ease.ui.completions.java.provider;
 
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.net.URL;
 import java.util.Collection;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
 import java.util.Map.Entry;
-import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
-import java.util.regex.Pattern;
 
-import org.eclipse.core.runtime.FileLocator;
 import org.eclipse.ease.ICompletionContext;
-import org.eclipse.ease.ICompletionContext.Type;
-import org.eclipse.ease.Logger;
-import org.eclipse.ease.ui.completion.AbstractCompletionProvider;
 import org.eclipse.ease.ui.completion.IHelpResolver;
 import org.eclipse.ease.ui.completion.IImageResolver;
 import org.eclipse.ease.ui.completion.ScriptCompletionProposal;
-import org.eclipse.ease.ui.completions.java.EaseUICompletionsJavaFragment;
+import org.eclipse.ease.ui.completion.provider.AbstractCompletionProvider;
+import org.eclipse.ease.ui.completion.tokenizer.InputTokenizer;
+import org.eclipse.ease.ui.completion.tokenizer.TokenList;
 import org.eclipse.ease.ui.completions.java.help.handlers.JavaClassHelpResolver;
 import org.eclipse.ease.ui.completions.java.provider.JavaMethodCompletionProvider.JDTImageResolver;
-import org.eclipse.ease.ui.tools.Timer;
 import org.eclipse.jdt.ui.ISharedImages;
 import org.eclipse.jface.resource.ImageDescriptor;
 import org.eclipse.jface.viewers.StyledString;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
 
 public class JavaClassCompletionProvider extends AbstractCompletionProvider {
 
-	private class JavaClassImageResolver extends DescriptorImageResolver {
+	@Override
+	public boolean isActive(final ICompletionContext context) {
+		return (super.isActive(context) && (isPackage(context))) || (isClass(context))
+		// also activate when no package is provided and the first letter is capitalized and we see at least 3 characters
+				|| ((context.getFilter().length() >= 3) && (Character.isUpperCase(context.getFilter().charAt(0))));
+	}
+
+	private boolean isClass(ICompletionContext context) {
+		return new TokenList(context.getTokens()).getFromLast(Class.class).size() == 1;
+	}
+
+	private boolean isPackage(ICompletionContext context) {
+		final TokenList tokens = new TokenList(context.getTokens()).getFromLast(Package.class);
+		if (!tokens.isEmpty()) {
+			tokens.remove(0);
+			tokens.removeIfMatches(0, ".");
+
+			return (tokens.isEmpty()) || InputTokenizer.isTextFilter(tokens.get(0));
+		}
+
+		return false;
+	}
+
+	@Override
+	protected void prepareProposals(final ICompletionContext context) {
+
+		final String filter = context.getFilter();
+
+		if (isPackage(context)) {
+			final Collection<String> candidates = JavaResources.getInstance().getClasses(getPackageName(context));
+
+			for (final String candidate : candidates) {
+				if (candidate.startsWith(filter)) {
+					final IHelpResolver helpResolver = new JavaClassHelpResolver(getPackageName(context), candidate);
+					final IImageResolver imageResolver = new JavaClassImageResolver(getPackageName(context), candidate);
+
+					addProposal(candidate, candidate, imageResolver, ScriptCompletionProposal.ORDER_CLASS, helpResolver);
+				}
+			}
+
+		} else {
+			// no package provided, look in all packages for matching class
+
+			for (final Entry<String, Collection<String>> packageEntry : JavaResources.getInstance().getClasses().entrySet()) {
+				for (final String candidate : packageEntry.getValue()) {
+					if (candidate.startsWith(filter)) {
+						final IHelpResolver helpResolver = new JavaClassHelpResolver(packageEntry.getKey(), candidate);
+						final IImageResolver imageResolver = new JavaClassImageResolver(packageEntry.getKey(), candidate);
+
+						final StyledString styledString = new StyledString(candidate);
+						styledString.append(" - " + packageEntry.getKey(), StyledString.QUALIFIER_STYLER);
+
+						addProposal(styledString, packageEntry.getKey() + "." + candidate, imageResolver, ScriptCompletionProposal.ORDER_CLASS, helpResolver);
+					}
+				}
+			}
+		}
+	}
+
+	private String getPackageName(ICompletionContext context) {
+		return ((Package) (new TokenList(context.getTokens()).getFromLast(Package.class).get(0))).getName();
+	}
+
+	private static final class JavaClassImageResolver extends DescriptorImageResolver {
 		private final String fPackageName;
 		private final String fClassName;
 
-		public JavaClassImageResolver(final String packageName, final String className) {
+		private JavaClassImageResolver(final String packageName, final String className) {
 			fPackageName = packageName;
 			fClassName = className;
 		}
@@ -75,232 +119,4 @@
 			return JDTImageResolver.getDescriptor(ISharedImages.IMG_OBJS_CLASS);
 		}
 	}
-
-	/** Maps Package name -> contained classes. */
-	private static Map<String, Collection<String>> CLASSES = null;
-
-	@Override
-	public boolean isActive(final ICompletionContext context) {
-		return super.isActive(context) && ((context.getType() == Type.PACKAGE)
-				// also activate when no package is provided and the first letter is capitalized and we see at least 3 characters
-				|| ((context.getType() == Type.NONE) && (context.getFilter().length() >= 3) && (Character.isUpperCase(context.getFilter().charAt(0)))));
-	}
-
-	@Override
-	protected void prepareProposals(final ICompletionContext context) {
-
-		if (getClasses().get(context.getPackage()) != null) {
-			// dedicated package provided, only query this package
-
-			for (final String className : getClasses().get(context.getPackage())) {
-				if (matchesFilter(className)) {
-
-					// add class name
-					final IHelpResolver helpResolver = new JavaClassHelpResolver(context.getPackage(), className);
-
-					// retrieve image
-					final IImageResolver imageResolver = new JavaClassImageResolver(context.getPackage(), className);
-
-					addProposal(className, className, imageResolver, ScriptCompletionProposal.ORDER_CLASS, helpResolver);
-				}
-			}
-
-		} else {
-			// no package provided, look in all packages for matching class
-			final String filter = context.getFilter();
-			final Pattern classPattern = Pattern.compile(filter + ".*");
-
-			for (final Entry<String, Collection<String>> packageEntry : getClasses().entrySet()) {
-				for (final String candidate : packageEntry.getValue()) {
-					if (classPattern.matcher(candidate).matches()) {
-						// add class proposal
-						// add class name
-						final IHelpResolver helpResolver = new JavaClassHelpResolver(packageEntry.getKey(), candidate);
-
-						// retrieve image
-						final IImageResolver imageResolver = new JavaClassImageResolver(packageEntry.getKey(), candidate);
-
-						final StyledString styledString = new StyledString(candidate);
-						styledString.append(" - " + packageEntry.getKey(), StyledString.QUALIFIER_STYLER);
-
-						addProposal(styledString, packageEntry.getKey() + "." + candidate, imageResolver, ScriptCompletionProposal.ORDER_CLASS, helpResolver);
-					}
-				}
-			}
-		}
-	}
-
-	private static Map<String, Collection<String>> getClasses() {
-		if (CLASSES == null) {
-			CLASSES = new HashMap<>();
-
-			Timer timer = new Timer();
-
-			// read java classes
-			try (BufferedReader reader = JavaPackagesCompletionProvider.getJavaClassDefinitions()) {
-				String fullQualifiedName;
-				while ((fullQualifiedName = reader.readLine()) != null) {
-					addClass(fullQualifiedName);
-				}
-
-				reader.close();
-
-			} catch (final IOException e) {
-				Logger.error(EaseUICompletionsJavaFragment.FRAGMENT_ID, "Cannot read class list for code completion", e);
-			}
-
-			Logger.trace(EaseUICompletionsJavaFragment.FRAGMENT_ID, TRACE_CODE_COMPLETION, "Load java classes took: " + timer.getMilliSeconds() + " ms");
-
-			// read eclipse classes
-			timer = new Timer();
-
-			final BundleContext context = FrameworkUtil.getBundle(JavaClassCompletionProvider.class).getBundleContext();
-			for (final Bundle bundle : context.getBundles()) {
-				final Collection<String> exportedPackages = JavaPackagesCompletionProvider.getExportedPackages(bundle);
-
-				// first look for class signatures in manifest, so we do not need to parse the whole bundle
-				boolean signedContent = false;
-				try {
-					final URL manifest = bundle.getEntry("/META-INF/MANIFEST.MF");
-					final InputStream inputStream = manifest.openConnection().getInputStream();
-					final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
-					String line;
-					while ((line = reader.readLine()) != null) {
-						if ((line.startsWith("Name:")) && (line.endsWith(".class")) && (!line.contains("$")) && (!line.contains("package-info"))) {
-							final String fullQualifiedName = line.substring(5, line.length() - 6).trim().replace('/', '.');
-							final String packageName = fullQualifiedName.contains(".") ? fullQualifiedName.substring(0, fullQualifiedName.lastIndexOf('.'))
-									: "";
-							if (exportedPackages.contains(packageName))
-								addClass(fullQualifiedName);
-
-							signedContent = true;
-						}
-					}
-				} catch (final IOException e) {
-					Logger.error(EaseUICompletionsJavaFragment.FRAGMENT_ID, "Could not parse manifest of bundle \"" + bundle.getBundleId() + "\"", e);
-				}
-
-				if (!signedContent) {
-					// we did not find a signed bundle, try to parse the bundle
-
-					try {
-						final File bundleFile = FileLocator.getBundleFile(bundle);
-
-						if (bundleFile.isDirectory()) {
-							// bundle stored as folder
-							for (final String packageName : exportedPackages) {
-								final String packagePath = bundleFile.getAbsolutePath() + File.separatorChar + packageName.replace('.', File.separatorChar);
-								final File packageFile = new File(packagePath);
-								if (packageFile.isDirectory()) {
-									for (final String candidate : packageFile.list()) {
-										if ((candidate.endsWith(".class")) && (!candidate.contains("$")))
-											addClass(packageName + "." + candidate.substring(0, candidate.length() - 6));
-									}
-								}
-							}
-
-						} else if (bundleFile.isFile()) {
-							// bundle stored as jar
-							final JarFile jarFile = new JarFile(bundleFile);
-							final Enumeration<JarEntry> entries = jarFile.entries();
-							while (entries.hasMoreElements()) {
-								final String candidate = entries.nextElement().getName();
-								if ((candidate.endsWith(".class")) && (!candidate.contains("$"))) {
-									final String fullQualifiedName = candidate.substring(0, candidate.length() - 6).replace('/', '.');
-									final String packageName = fullQualifiedName.contains(".")
-											? fullQualifiedName.substring(0, fullQualifiedName.lastIndexOf('.'))
-											: "";
-									if (exportedPackages.contains(packageName))
-										addClass(fullQualifiedName);
-								}
-							}
-
-							jarFile.close();
-
-						}
-					} catch (final IOException e) {
-						Logger.error(EaseUICompletionsJavaFragment.FRAGMENT_ID, "Cannot resolve location for bundle \"" + bundle.getBundleId() + "\"", e);
-					}
-				}
-			}
-			Logger.trace(EaseUICompletionsJavaFragment.FRAGMENT_ID, TRACE_CODE_COMPLETION, "Load eclipse classes took: " + timer.getMilliSeconds() + " ms");
-		}
-
-		return CLASSES;
-	}
-
-	/**
-	 * @param packageName
-	 * @param substring
-	 */
-	private static void addClass(final String fullQualifiedName) {
-
-		final String packageName = getPackage(fullQualifiedName);
-
-		if (!CLASSES.containsKey(packageName))
-			CLASSES.put(packageName, new HashSet<String>());
-
-		CLASSES.get(packageName).add(fullQualifiedName.substring(packageName.length() + 1));
-	}
-
-	/**
-	 * @param className
-	 * @return
-	 */
-	private static String getPackage(final String className) {
-		final int lastDot = className.lastIndexOf('.');
-		if (lastDot == -1)
-			return null;
-
-		final String candidate = className.substring(0, lastDot);
-		if (JavaPackagesCompletionProvider.containsPackage(candidate))
-			return candidate;
-
-		return getPackage(candidate);
-	}
-
-	/**
-	 * Helper method to extract class names from public javadoc. Not needed for productive use. May be helpful when new java version gets released.
-	 */
-	// public static void main(final String[] args) {
-	// // read java packages
-	// try {
-	// URL url = new URL("https://docs.oracle.com/javase/6/docs/api/allclasses-frame.html");
-	// System.out.print("fetching data ... ");
-	// InputStream inputStream = url.openConnection().getInputStream();
-	// String htmlContent = StringTools.toString(inputStream);
-	// inputStream.close();
-	// System.out.println("done");
-	//
-	// int start = htmlContent.indexOf("<body>");
-	// int end = htmlContent.indexOf("</body>");
-	//
-	// // preparing data
-	// htmlContent = htmlContent.substring(start, end + 7);
-	// htmlContent = htmlContent.replaceAll("&nbsp;", " ");
-	//
-	// System.out.print("parsing data ... ");
-	// XMLMemento root = XMLMemento.createReadRoot(new StringReader(htmlContent));
-	//
-	// File file = new File("/tmp/Java 6 classes.txt");
-	// if (!file.exists())
-	// file.createNewFile();
-	//
-	// FileOutputStream outputStream = new FileOutputStream(file);
-	// for (IMemento listNode : root.getChild("div").getChild("ul").getChildren("li")) {
-	// String className = listNode.getChild("a").getString("href").replace('/', '.').replace(".html", "");
-	// outputStream.write((className + "\n").getBytes());
-	// }
-	// outputStream.close();
-	//
-	// System.out.println("done");
-	//
-	// } catch (IOException e) {
-	// Logger.logError("Cannot read package list for code completion", e);
-	// } catch (WorkbenchException e) {
-	// // TODO handle this exception (but for now, at least know it happened)
-	// throw new RuntimeException(e);
-	//
-	// }
-	// }
 }
diff --git a/plugins/org.eclipse.ease.ui.completions.java/src/org/eclipse/ease/ui/completions/java/provider/JavaMethodCompletionProvider.java b/plugins/org.eclipse.ease.ui.completions.java/src/org/eclipse/ease/ui/completions/java/provider/JavaMethodCompletionProvider.java
index 06c4542..8ca0e89 100644
--- a/plugins/org.eclipse.ease.ui.completions.java/src/org/eclipse/ease/ui/completions/java/provider/JavaMethodCompletionProvider.java
+++ b/plugins/org.eclipse.ease.ui.completions.java/src/org/eclipse/ease/ui/completions/java/provider/JavaMethodCompletionProvider.java
@@ -10,6 +10,7 @@
  * Contributors:
  *     Christian Pontesegger - initial API and implementation
  *******************************************************************************/
+
 package org.eclipse.ease.ui.completions.java.provider;
 
 import java.lang.reflect.Field;
@@ -18,12 +19,13 @@
 import java.lang.reflect.Parameter;
 
 import org.eclipse.ease.ICompletionContext;
-import org.eclipse.ease.ICompletionContext.Type;
 import org.eclipse.ease.ui.Activator;
-import org.eclipse.ease.ui.completion.AbstractCompletionProvider;
 import org.eclipse.ease.ui.completion.IHelpResolver;
 import org.eclipse.ease.ui.completion.IImageResolver;
 import org.eclipse.ease.ui.completion.ScriptCompletionProposal;
+import org.eclipse.ease.ui.completion.provider.AbstractCompletionProvider;
+import org.eclipse.ease.ui.completion.tokenizer.InputTokenizer;
+import org.eclipse.ease.ui.completion.tokenizer.TokenList;
 import org.eclipse.ease.ui.completions.java.help.handlers.JavaFieldHelpResolver;
 import org.eclipse.ease.ui.completions.java.help.handlers.JavaMethodHelpResolver;
 import org.eclipse.jdt.ui.ISharedImages;
@@ -33,6 +35,105 @@
 
 public class JavaMethodCompletionProvider extends AbstractCompletionProvider {
 
+	@Override
+	public boolean isActive(final ICompletionContext context) {
+		return super.isActive(context) && isJavaClassContext(context);
+	}
+
+	@Override
+	protected void prepareProposals(final ICompletionContext context) {
+		final boolean isStatic = isStaticClassContext(context);
+
+		final Class<? extends Object> clazz = getJavaClass(context);
+		final String filter = context.getFilter();
+
+		for (final Method method : clazz.getMethods()) {
+			if ((isStatic) != (Modifier.isStatic(method.getModifiers())))
+				continue;
+
+			if (method.getName().startsWith(filter))
+				addMethodProposal(method);
+		}
+
+		for (final Field field : clazz.getFields()) {
+			if ((isStatic) != (Modifier.isStatic(field.getModifiers())))
+				continue;
+
+			if (field.getName().startsWith(filter))
+				addFieldProposal(field);
+		}
+	}
+
+	private void addFieldProposal(Field field) {
+		final IHelpResolver helpResolver = new JavaFieldHelpResolver(field);
+
+		final StyledString styledString = new StyledString(field.getName() + " : " + field.getType().getSimpleName());
+		styledString.append(" - " + field.getDeclaringClass().getSimpleName(), StyledString.QUALIFIER_STYLER);
+
+		final IImageResolver imageResolver = Modifier.isStatic(field.getModifiers())
+				? new DescriptorImageResolver(Activator.getImageDescriptor(Activator.PLUGIN_ID, "/icons/eobj16/static_field.png"))
+				: new JDTImageResolver(ISharedImages.IMG_FIELD_PUBLIC);
+
+		addProposal(styledString, field.getName(), imageResolver, ScriptCompletionProposal.ORDER_FIELD, helpResolver);
+	}
+
+	private void addMethodProposal(Method method) {
+		final IHelpResolver helpResolver = new JavaMethodHelpResolver(method);
+
+		final StyledString styledString = new StyledString(method.getName() + "(" + getMethodSignature(method) + ") : " + getMethodReturnType(method));
+		styledString.append(" - " + method.getDeclaringClass().getSimpleName(), StyledString.QUALIFIER_STYLER);
+
+		final IImageResolver imageResolver = Modifier.isStatic(method.getModifiers())
+				? new DescriptorImageResolver(Activator.getImageDescriptor(Activator.PLUGIN_ID, "/icons/eobj16/static_function.png"))
+				: new JDTImageResolver(ISharedImages.IMG_OBJS_PUBLIC);
+
+		if (method.getParameterTypes().length > 0)
+			addProposal(styledString, method.getName() + "(", imageResolver, ScriptCompletionProposal.ORDER_METHOD, helpResolver);
+		else
+			addProposal(styledString, method.getName() + "()", imageResolver, ScriptCompletionProposal.ORDER_METHOD, helpResolver);
+	}
+
+	private boolean isJavaClassContext(ICompletionContext context) {
+		final TokenList tokens = new TokenList(context.getTokens()).getFromLast(Class.class);
+		if (!tokens.isEmpty()) {
+			tokens.remove(0);
+			tokens.removeIfMatches(0, "()");
+			tokens.removeIfMatches(0, ".");
+
+			return (tokens.isEmpty()) || ((tokens.size() == 1) && (!InputTokenizer.isDelimiter(tokens.get(0))));
+		}
+
+		return false;
+	}
+
+	private boolean isStaticClassContext(ICompletionContext context) {
+		final TokenList tokens = new TokenList(context.getTokens()).getFromLast(Class.class);
+		return (tokens.size() == 1) || (!"()".equals(tokens.get(1)));
+	}
+
+	private Class<?> getJavaClass(ICompletionContext context) {
+		return (Class<?>) new TokenList(context.getTokens()).getFromLast(Class.class).get(0);
+	}
+
+	private String getMethodReturnType(final Method method) {
+		final Class<?> returnType = method.getReturnType();
+		return (returnType == null) ? "void" : returnType.getSimpleName();
+	}
+
+	private String getMethodSignature(final Method method) {
+		final StringBuilder result = new StringBuilder();
+
+		for (final Parameter parameter : method.getParameters()) {
+			if (result.length() > 0)
+				result.append(", ");
+
+			result.append(parameter.getType().getSimpleName());
+			result.append(' ').append(parameter.getName());
+		}
+
+		return result.toString();
+	}
+
 	public static class JDTImageResolver extends DescriptorImageResolver {
 
 		private final String fImageIdentifier;
@@ -52,73 +153,4 @@
 			return JavaUI.getSharedImages().getImageDescriptor(imageIdentifier);
 		}
 	}
-
-	@Override
-	public boolean isActive(final ICompletionContext context) {
-		return context.getReferredClazz() != null;
-	}
-
-	@Override
-	protected void prepareProposals(final ICompletionContext context) {
-		final Class<? extends Object> clazz = context.getReferredClazz();
-
-		for (final Method method : clazz.getMethods()) {
-			if ((context.getType() == Type.STATIC_CLASS) && (!Modifier.isStatic(method.getModifiers())))
-				continue;
-
-			if (matchesFilterIgnoreCase(method.getName())) {
-
-				final IHelpResolver helpResolver = new JavaMethodHelpResolver(method);
-
-				final StyledString styledString = new StyledString(method.getName() + "(" + getMethodSignature(method) + ") : " + getMethodReturnType(method));
-				styledString.append(" - " + method.getDeclaringClass().getSimpleName(), StyledString.QUALIFIER_STYLER);
-
-				final IImageResolver imageResolver = Modifier.isStatic(method.getModifiers())
-						? new DescriptorImageResolver(Activator.getImageDescriptor(Activator.PLUGIN_ID, "/icons/eobj16/static_function.png"))
-						: new JDTImageResolver(ISharedImages.IMG_OBJS_PUBLIC);
-
-				if (method.getParameterTypes().length > 0)
-					addProposal(styledString, method.getName() + "(", imageResolver, ScriptCompletionProposal.ORDER_METHOD, helpResolver);
-				else
-					addProposal(styledString, method.getName() + "()", imageResolver, ScriptCompletionProposal.ORDER_METHOD, helpResolver);
-			}
-		}
-
-		for (final Field field : clazz.getFields()) {
-			if ((context.getType() == Type.STATIC_CLASS) && (!Modifier.isStatic(field.getModifiers())))
-				continue;
-
-			if (matchesFilterIgnoreCase(field.getName())) {
-				final IHelpResolver helpResolver = new JavaFieldHelpResolver(field);
-
-				final StyledString styledString = new StyledString(field.getName() + " : " + field.getType().getSimpleName());
-				styledString.append(" - " + field.getDeclaringClass().getSimpleName(), StyledString.QUALIFIER_STYLER);
-
-				final IImageResolver imageResolver = Modifier.isStatic(field.getModifiers())
-						? new DescriptorImageResolver(Activator.getImageDescriptor(Activator.PLUGIN_ID, "/icons/eobj16/static_field.png"))
-						: new JDTImageResolver(ISharedImages.IMG_FIELD_PUBLIC);
-
-				addProposal(styledString, field.getName(), imageResolver, ScriptCompletionProposal.ORDER_FIELD, helpResolver);
-			}
-		}
-	}
-
-	public static String getMethodReturnType(final Method method) {
-		final Class<?> returnType = method.getReturnType();
-		return (returnType != null) ? returnType.getSimpleName() : "void";
-	}
-
-	public static String getMethodSignature(final Method method) {
-		final StringBuilder result = new StringBuilder();
-
-		for (final Parameter parameter : method.getParameters()) {
-			if (result.length() > 0)
-				result.append(", ");
-
-			result.append(parameter.getType().getSimpleName());
-			result.append(' ').append(parameter.getName());
-		}
-
-		return result.toString();
-	}
 }
diff --git a/plugins/org.eclipse.ease.ui.completions.java/src/org/eclipse/ease/ui/completions/java/provider/JavaPackagesCompletionProvider.java b/plugins/org.eclipse.ease.ui.completions.java/src/org/eclipse/ease/ui/completions/java/provider/JavaPackagesCompletionProvider.java
index a65cef2..0e0696c 100644
--- a/plugins/org.eclipse.ease.ui.completions.java/src/org/eclipse/ease/ui/completions/java/provider/JavaPackagesCompletionProvider.java
+++ b/plugins/org.eclipse.ease.ui.completions.java/src/org/eclipse/ease/ui/completions/java/provider/JavaPackagesCompletionProvider.java
@@ -10,183 +10,74 @@
  * Contributors:
  *     Christian Pontesegger - initial API and implementation
  *******************************************************************************/
+
 package org.eclipse.ease.ui.completions.java.provider;
 
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.net.URL;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
 import org.eclipse.ease.ICompletionContext;
-import org.eclipse.ease.ICompletionContext.Type;
-import org.eclipse.ease.Logger;
-import org.eclipse.ease.ui.Activator;
-import org.eclipse.ease.ui.completion.AbstractCompletionProvider;
-import org.eclipse.ease.ui.completion.IHelpResolver;
 import org.eclipse.ease.ui.completion.ScriptCompletionProposal;
-import org.eclipse.ease.ui.completions.java.EaseUICompletionsJavaFragment;
+import org.eclipse.ease.ui.completion.provider.AbstractCompletionProvider;
+import org.eclipse.ease.ui.completion.tokenizer.InputTokenizer;
+import org.eclipse.ease.ui.completion.tokenizer.TokenList;
 import org.eclipse.ease.ui.completions.java.help.handlers.JavaPackageHelpResolver;
 import org.eclipse.jdt.ui.ISharedImages;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.FrameworkUtil;
 
 public class JavaPackagesCompletionProvider extends AbstractCompletionProvider {
 
-	private static Map<String, Collection<String>> PACKAGES = null;
-
-	private static final Pattern JAVA_VERSION_MATCHER = Pattern.compile("1\\.(\\d)\\..*");
-
-	/**
-	 * Get the major version number of the java runtime.
-	 *
-	 * @return java major version, eg 6, 8, 12
-	 */
-	private static int getJavaMajorVersion() {
-		int result = 0;
-
-		final String version = System.getProperty("java.runtime.version");
-		final Matcher matcher = JAVA_VERSION_MATCHER.matcher(version);
-		if (matcher.matches())
-			result = Integer.parseInt(matcher.group(1));
-		else
-			result = Integer.parseInt(version.substring(0, version.indexOf('.')));
-
-		return Math.min(result, Activator.JAVA_CLASSES_MAX_VERSION);
-	}
-
 	@Override
 	public boolean isActive(final ICompletionContext context) {
-		return super.isActive(context) && ((context.getType() == Type.NONE) || (context.getType() == Type.PACKAGE));
+		return super.isActive(context) && !isCallChain(context);
+	}
+
+	private boolean isCallChain(ICompletionContext context) {
+		final TokenList tokens = new TokenList(context.getTokens()).getFromLast("()");
+
+		return (!tokens.isEmpty()) && tokens.getFromLast("(").isEmpty();
 	}
 
 	@Override
 	protected void prepareProposals(final ICompletionContext context) {
 
-		final String parentPackage = (context.getType() == Type.NONE) ? "" : context.getPackage();
+		final String packageFilter = getFilter(context);
 
-		if (getPackages().get(parentPackage) != null) {
-			for (final String packageName : getPackages().get(parentPackage)) {
-
-				if (matchesFilter(packageName)) {
-					final IHelpResolver helpResolver = new JavaPackageHelpResolver(parentPackage + "." + packageName);
-
-					if (parentPackage.isEmpty())
-						// add root package
-						addProposal(packageName, packageName + ".", new JavaMethodCompletionProvider.JDTImageResolver(ISharedImages.IMG_OBJS_PACKAGE),
-								ScriptCompletionProposal.ORDER_PACKAGE, helpResolver);
-					else
-						// add sub package
-						addProposal(parentPackage + "." + packageName, packageName + ".",
-								new JavaMethodCompletionProvider.JDTImageResolver(ISharedImages.IMG_OBJS_PACKAGE), ScriptCompletionProposal.ORDER_PACKAGE,
-								helpResolver);
-				}
+		for (final String candidate : JavaResources.getInstance().getPackages()) {
+			if (isValidCandidate(candidate, packageFilter)) {
+				addProposal(candidate, candidate.substring(packageFilter.length() - context.getFilter().length()) + ".",
+						new JavaMethodCompletionProvider.JDTImageResolver(ISharedImages.IMG_OBJS_PACKAGE), ScriptCompletionProposal.ORDER_PACKAGE,
+						new JavaPackageHelpResolver(candidate));
 			}
 		}
 	}
 
-	public static BufferedReader getJavaClassDefinitions() throws IOException {
-		final URL url = new URL("platform:/plugin/org.eclipse.ease.ui/resources/java" + getJavaMajorVersion() + " classes.txt");
-		return new BufferedReader(new InputStreamReader(url.openConnection().getInputStream()));
-	}
-
-	public static Map<String, Collection<String>> getPackages() {
-		if (PACKAGES == null) {
-			PACKAGES = new HashMap<>();
-
-			// read java packages
-			try (BufferedReader reader = getJavaClassDefinitions()) {
-				String className;
-				while ((className = reader.readLine()) != null) {
-					final String packageName = className.substring(0, className.lastIndexOf('.'));
-					registerPackage(packageName);
-				}
-
-				reader.close();
-
-			} catch (final IOException e) {
-				Logger.error(EaseUICompletionsJavaFragment.FRAGMENT_ID, "Cannot read package list for code completion", e);
-			}
-
-			// read eclipse packages
-			final BundleContext context = FrameworkUtil.getBundle(JavaPackagesCompletionProvider.class).getBundleContext();
-			for (final Bundle bundle : context.getBundles()) {
-
-				for (final String packageName : getExportedPackages(bundle))
-					registerPackage(packageName);
-			}
-		}
-
-		return PACKAGES;
-	}
-
-	/**
-	 * Get a list of all exported packages of a bundle.
-	 *
-	 * @param bundle
-	 *            bundle instance
-	 * @return collection of exported packages
-	 */
-	public static Collection<String> getExportedPackages(final Bundle bundle) {
-		final Collection<String> exportedPackages = new HashSet<>();
-
-		final String exportPackage = bundle.getHeaders().get("Export-Package");
-		if (exportPackage != null) {
-			final String[] packages = exportPackage.split(",");
-			for (final String packageEntry : packages) {
-				String candidate = packageEntry.trim().split(";")[0];
-				if (candidate.endsWith("\""))
-					candidate = candidate.substring(0, candidate.length() - 1);
-
-				if ((candidate.contains(".internal")) || (packageEntry.contains(";x-internal:=true")))
-					// ignore internal packages
-					continue;
-
-				if ((candidate.startsWith("Lib")) || (candidate.startsWith("about_files")) || (candidate.startsWith("META")))
-					// ignore some dedicated packages
-					continue;
-
-				exportedPackages.add(candidate);
-			}
-		}
-
-		return exportedPackages;
-	}
-
-	private static void registerPackage(final String packageName) {
-		final int lastIndex = packageName.lastIndexOf('.');
-		final String key = (lastIndex == -1) ? "" : packageName.substring(0, lastIndex);
-		final String value = (lastIndex == -1) ? packageName : packageName.substring(lastIndex + 1);
-
-		if (!PACKAGES.containsKey(key))
-			PACKAGES.put(key, new HashSet<String>());
-
-		PACKAGES.get(key).add(value);
-
-		if (!key.isEmpty())
-			registerPackage(key);
-	}
-
-	/**
-	 * Try to find a package in the registered package list
-	 *
-	 * @param candidate
-	 *            package name to look up
-	 * @return <code>true</code> when package is registered
-	 */
-	public static boolean containsPackage(final String candidate) {
-		final int lastDot = candidate.lastIndexOf('.');
-		final Collection<String> packageList = (lastDot > 0) ? getPackages().get(candidate.substring(0, lastDot)) : getPackages().get("");
-
-		if (packageList != null)
-			return packageList.contains(candidate.substring(lastDot + 1));
+	private boolean isValidCandidate(String candidate, String packageFilter) {
+		if (candidate.startsWith(packageFilter))
+			return !candidate.substring(packageFilter.length()).contains(".");
 
 		return false;
 	}
+
+	private String getFilter(final ICompletionContext context) {
+		final StringBuilder filter = new StringBuilder();
+
+		final List<Object> reversedTokens = new ArrayList<>(context.getTokens());
+		Collections.reverse(reversedTokens);
+
+		for (final Object token : reversedTokens) {
+			if (token instanceof Package) {
+				filter.insert(0, ((Package) token).getName());
+				break;
+
+			} else if ((InputTokenizer.isDelimiter(token)) && (!".".equals(token))) {
+				break;
+			} else if (token instanceof String) {
+				filter.insert(0, token);
+			} else
+				break;
+		}
+
+		return filter.toString();
+	}
 }
diff --git a/plugins/org.eclipse.ease.ui.completions.java/src/org/eclipse/ease/ui/completions/java/provider/JavaResources.java b/plugins/org.eclipse.ease.ui.completions.java/src/org/eclipse/ease/ui/completions/java/provider/JavaResources.java
new file mode 100644
index 0000000..91d9b24
--- /dev/null
+++ b/plugins/org.eclipse.ease.ui.completions.java/src/org/eclipse/ease/ui/completions/java/provider/JavaResources.java
@@ -0,0 +1,209 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License_Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.ui.completions.java.provider;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.ease.Logger;
+import org.eclipse.ease.ui.Activator;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+
+public final class JavaResources {
+
+	private static final JavaResources INSTANCE = new JavaResources();
+
+	private static final String PLUGIN_ID = "org.eclipse.ease.ui.completions.java";
+
+	private static final Pattern FQN_CLASSNAME_PATTERN = Pattern.compile("(\\p{Lower}+(?:\\.\\p{Lower}+)*)\\.(\\p{Upper}.*)");
+
+	private static final Collection<String> FILTERED_PACKAGES = Arrays.asList("java.awt", "java.applet", "groovy", "netscape", "kotlin");
+
+	public static JavaResources getInstance() {
+		return INSTANCE;
+	}
+
+	/** Maps packageName -> {classNames}, eg 'java.io' -> {File, FileBuffer, ...}. */
+	private Map<String, Collection<String>> fPackagesAndClasses = Collections.emptyMap();
+	private boolean fisLoadingTriggered = false;
+
+	private JavaResources() {
+	}
+
+	public Map<String, Collection<String>> getClasses() {
+		if (fPackagesAndClasses.isEmpty())
+			loadClasses();
+
+		return fPackagesAndClasses;
+	}
+
+	public Collection<String> getPackages() {
+		return getClasses().keySet();
+	}
+
+	public Collection<String> getClasses(String packageName) {
+		if (getClasses().containsKey(packageName))
+			return getClasses().get(packageName);
+
+		return Collections.emptySet();
+	}
+
+	private void loadClasses() {
+		if (!fisLoadingTriggered) {
+			fisLoadingTriggered = true;
+
+			final Job job = new Job("Load Java code completion") {
+				@Override
+				protected IStatus run(IProgressMonitor monitor) {
+					final Map<String, Collection<String>> loadingContent = new TreeMap<>();
+
+					readJavaPackagesAndClasses(loadingContent);
+					readEclipsePackages(loadingContent);
+
+					fPackagesAndClasses = loadingContent;
+
+					return Status.OK_STATUS;
+				}
+			};
+
+			job.setSystem(true);
+			job.schedule();
+		}
+	}
+
+	private void readEclipsePackages(Map<String, Collection<String>> target) {
+		final BundleContext context = FrameworkUtil.getBundle(getClass()).getBundleContext();
+
+		for (final Bundle bundle : context.getBundles()) {
+			for (final String packageName : getExportedPackages(bundle)) {
+				if (!isFiltered(packageName))
+					registerPackage(target, packageName);
+			}
+		}
+	}
+
+	private void readJavaPackagesAndClasses(Map<String, Collection<String>> target) {
+		try (BufferedReader reader = getJavaClassDefinitions()) {
+			String entry = reader.readLine();
+			while (entry != null) {
+				final Matcher matcher = FQN_CLASSNAME_PATTERN.matcher(entry);
+				if (matcher.matches())
+					registerClass(target, matcher.group(1), matcher.group(2));
+
+				entry = reader.readLine();
+			}
+
+		} catch (final IOException e) {
+			Logger.error(PLUGIN_ID, "Cannot read package list for code completion", e);
+		}
+	}
+
+	private void registerPackage(Map<String, Collection<String>> target, String packageName) {
+		if (!target.containsKey(packageName)) {
+			target.put(packageName, new TreeSet<String>());
+		}
+
+		final int delimiterPosition = packageName.lastIndexOf('.');
+		if (delimiterPosition > 0)
+			registerPackage(target, packageName.substring(0, delimiterPosition));
+	}
+
+	private void registerClass(Map<String, Collection<String>> target, final String packageName, String className) {
+		if (!isFiltered(packageName)) {
+			registerPackage(target, packageName);
+			target.get(packageName).add(className);
+		}
+	}
+
+	private boolean isFiltered(String packageName) {
+		for (final String filter : FILTERED_PACKAGES) {
+			if (packageName.startsWith(filter))
+				return true;
+		}
+
+		return false;
+	}
+
+	private BufferedReader getJavaClassDefinitions() throws IOException {
+		final URL url = new URL("platform:/plugin/" + PLUGIN_ID + "/resources/java" + getJavaMajorVersion() + " classes.txt");
+		return new BufferedReader(new InputStreamReader(url.openConnection().getInputStream()));
+	}
+
+	/**
+	 * Get the major version number of the java runtime.
+	 *
+	 * @return java major version, eg 6, 8, 12
+	 */
+	private int getJavaMajorVersion() {
+		int result = 1000; // set to a high value so that the min function below will pick the highest supported version
+
+		final Pattern versionPattern = Pattern.compile("(?:1\\.)?(\\d+).*");
+		final String version = System.getProperty("java.runtime.version");
+		final Matcher matcher = versionPattern.matcher(version);
+		if (matcher.matches())
+			result = Integer.parseInt(matcher.group(1));
+
+		return Math.min(result, Activator.JAVA_CLASSES_MAX_VERSION);
+	}
+
+	/**
+	 * Get a list of all exported packages of a bundle.
+	 *
+	 * @param bundle
+	 *            bundle instance
+	 * @return collection of exported packages
+	 */
+	private Collection<String> getExportedPackages(final Bundle bundle) {
+		final Collection<String> exportedPackages = new HashSet<>();
+
+		final String exportPackage = bundle.getHeaders().get("Export-Package");
+		if (exportPackage != null) {
+			final String[] packages = exportPackage.split(",");
+			for (final String packageEntry : packages) {
+				String candidate = packageEntry.trim().split(";")[0];
+				if (candidate.endsWith("\""))
+					candidate = candidate.substring(0, candidate.length() - 1);
+
+				if ((candidate.contains(".internal")) || (packageEntry.contains(";x-internal:=true")))
+					// ignore internal packages
+					continue;
+
+				if ((candidate.startsWith("Lib")) || (candidate.startsWith("about_files")) || (candidate.startsWith("META")))
+					// ignore some dedicated packages
+					continue;
+
+				exportedPackages.add(candidate);
+			}
+		}
+
+		return exportedPackages;
+	}
+}
diff --git a/plugins/org.eclipse.ease.ui/.classpath b/plugins/org.eclipse.ease.ui/.classpath
index 43b9862..c93a9d1 100644
--- a/plugins/org.eclipse.ease.ui/.classpath
+++ b/plugins/org.eclipse.ease.ui/.classpath
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
 	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
 	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
 	<classpathentry kind="output" path="target/classes"/>
 </classpath>
diff --git a/plugins/org.eclipse.ease.ui/META-INF/MANIFEST.MF b/plugins/org.eclipse.ease.ui/META-INF/MANIFEST.MF
index 119da71..c3d6fa9 100644
--- a/plugins/org.eclipse.ease.ui/META-INF/MANIFEST.MF
+++ b/plugins/org.eclipse.ease.ui/META-INF/MANIFEST.MF
@@ -6,7 +6,7 @@
 Bundle-ClassPath: .
 Bundle-Vendor: Eclipse.org
 Bundle-Localization: plugin
-Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Bundle-RequiredExecutionEnvironment: JavaSE-11
 Require-Bundle: org.eclipse.ui.console;bundle-version="[3.5.100,4.0.0)",
  org.eclipse.ui.ide;bundle-version="[3.7.0,4.0.0)",
  org.eclipse.core.expressions;bundle-version="[3.4.300,4.0.0)",
@@ -26,6 +26,7 @@
 Export-Package: org.eclipse.ease.ui,
  org.eclipse.ease.ui.completion,
  org.eclipse.ease.ui.completion.provider,
+ org.eclipse.ease.ui.completion.tokenizer,
  org.eclipse.ease.ui.console,
  org.eclipse.ease.ui.console.actions;x-internal:=true,
  org.eclipse.ease.ui.debugging.model,
diff --git a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/AbstractCompletionParser.java b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/AbstractCompletionParser.java
deleted file mode 100644
index 29cd109..0000000
--- a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/AbstractCompletionParser.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Christian Pontesegger and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v2.0
- * which accompanies this distribution, and is available at
- * https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License_Identifier: EPL-2.0
- *
- * Contributors:
- *     Christian Pontesegger - initial API and implementation
- *******************************************************************************/
-
-package org.eclipse.ease.ui.completion;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.eclipse.ease.AbstractCodeParser;
-import org.eclipse.jface.text.Position;
-
-public abstract class AbstractCompletionParser extends AbstractCodeParser {
-
-	public static List<Position> findInvocations(final String call, final String code) {
-		List<Position> result = new ArrayList<Position>();
-
-		String methodName = (call.contains("(")) ? call.substring(0, call.indexOf('(')) : call;
-
-		// group 1: full call
-		// group 2: parameters
-		Pattern methodPattern = Pattern.compile("^.*(" + methodName + "\\s*\\((.*)\\))\\s*;?\\s*$", Pattern.MULTILINE);
-		Matcher matcher = methodPattern.matcher(code);
-
-		while (matcher.find()) {
-			// pattern found
-			result.add(new Position(matcher.start(1), matcher.end(1) - matcher.start(1)));
-		}
-
-		return result;
-	}
-
-	public static String[] getParameters(final String call) {
-		int start = call.indexOf('(');
-		int end = call.lastIndexOf(')');
-		if ((start > 0) && (end > start))
-			return call.substring(start + 1, end).split(",");
-
-		return new String[0];
-	}
-}
diff --git a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/AbstractCompletionProvider.java b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/AbstractCompletionProvider.java
deleted file mode 100644
index b06ca41..0000000
--- a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/AbstractCompletionProvider.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Christian Pontesegger and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v2.0
- * which accompanies this distribution, and is available at
- * https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License_Identifier: EPL-2.0
- *
- * Contributors:
- *     Christian Pontesegger - initial API and implementation
- *******************************************************************************/
-
-package org.eclipse.ease.ui.completion;
-
-import java.util.ArrayList;
-import java.util.Collection;
-
-import org.eclipse.ease.ICompletionContext;
-import org.eclipse.ease.ICompletionContext.Type;
-import org.eclipse.jface.resource.ImageDescriptor;
-import org.eclipse.jface.viewers.StyledString;
-import org.eclipse.swt.graphics.Image;
-
-public abstract class AbstractCompletionProvider implements ICompletionProvider {
-
-	public static class DescriptorImageResolver implements IImageResolver {
-		private final ImageDescriptor fDescriptor;
-
-		public DescriptorImageResolver() {
-			fDescriptor = null;
-		}
-
-		public DescriptorImageResolver(ImageDescriptor descriptor) {
-			fDescriptor = descriptor;
-		}
-
-		@Override
-		public Image getImage() {
-			return (getDescriptor() != null) ? getDescriptor().createImage() : null;
-		}
-
-		protected ImageDescriptor getDescriptor() {
-			return fDescriptor;
-		}
-	}
-
-	private Collection<ScriptCompletionProposal> fProposals = null;
-	private ICompletionContext fContext;
-
-	@Override
-	public boolean isActive(final ICompletionContext context) {
-		return context.getType() != Type.UNKNOWN;
-	}
-
-	@Override
-	public Collection<? extends ScriptCompletionProposal> getProposals(final ICompletionContext context) {
-		fContext = context;
-		fProposals = new ArrayList<>();
-
-		prepareProposals(context);
-
-		final Collection<ScriptCompletionProposal> result = fProposals;
-		fProposals = null;
-		fContext = null;
-		return result;
-	}
-
-	/**
-	 * Get the current context. Only valid during proposal evaluation. Clients may retrieve the content when {@link #prepareProposals(ICompletionContext)} is
-	 * called.
-	 *
-	 * @return the current context or <code>null</code> when proposals are not evaluated
-	 */
-	public ICompletionContext getContext() {
-		return fContext;
-	}
-
-	protected void addProposal(final ScriptCompletionProposal proposal) {
-		fProposals.add(proposal);
-	}
-
-	protected void addProposal(final StyledString displayString, final String replacementString, final IImageResolver imageResolver, final int priority,
-			final IHelpResolver helpResolver) {
-
-		fProposals.add(new ScriptCompletionProposal(fContext, displayString, replacementString, imageResolver, priority, helpResolver));
-	}
-
-	protected void addProposal(final String displayString, final String replacementString, final IImageResolver imageResolver, final int priority,
-			final IHelpResolver helpResolver) {
-		
-		fProposals.add(new ScriptCompletionProposal(fContext, displayString, replacementString, imageResolver, priority, helpResolver));
-	}
-
-	protected boolean matchesFilter(final String proposal) {
-		return matches(fContext.getFilter(), proposal);
-	}
-
-	protected boolean matchesFilterIgnoreCase(final String proposal) {
-		return matchesIgnoreCase(fContext.getFilter(), proposal);
-	}
-
-	protected static boolean matches(final String filter, final String proposal) {
-		return (filter != null) ? proposal.startsWith(filter) : true;
-	}
-
-	protected static boolean matchesIgnoreCase(final String filter, final String proposal) {
-		return (filter != null) ? proposal.toLowerCase().startsWith(filter.toLowerCase()) : true;
-	}
-
-	protected abstract void prepareProposals(ICompletionContext context);
-}
diff --git a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/BasicContext.java b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/BasicContext.java
new file mode 100644
index 0000000..63a1f47
--- /dev/null
+++ b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/BasicContext.java
@@ -0,0 +1,170 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License_Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.ui.completion;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.ease.ICompletionContext;
+import org.eclipse.ease.IScriptEngine;
+import org.eclipse.ease.modules.EnvironmentModule;
+import org.eclipse.ease.modules.ModuleDefinition;
+import org.eclipse.ease.modules.ModuleHelper;
+import org.eclipse.ease.service.ScriptType;
+import org.eclipse.ease.ui.completion.tokenizer.InputTokenizer;
+import org.eclipse.ease.ui.completion.tokenizer.TokenList;
+
+public class BasicContext implements ICompletionContext {
+
+	private final String fContent;
+	private final int fCursorPosition;
+	private final IScriptEngine fScriptEngine;
+	private final ScriptType fScriptType;
+	private final Object fResource;
+
+	public BasicContext(IScriptEngine scriptEngine, String content, int cursorPosition) {
+		fScriptEngine = scriptEngine;
+		fScriptType = scriptEngine.getDescription().getSupportedScriptTypes().get(0);
+		fResource = null;
+		fContent = content;
+		fCursorPosition = cursorPosition;
+	}
+
+	public BasicContext(ScriptType scriptType, Object resource, String content, int cursorPosition) {
+		fScriptEngine = null;
+		fScriptType = scriptType;
+		fResource = resource;
+		fContent = content;
+		fCursorPosition = cursorPosition;
+	}
+
+	@Override
+	public List<Object> getTokens() {
+		return getInputTokenizer().getTokens(getRelevantText());
+	}
+
+	protected InputTokenizer getInputTokenizer() {
+		if (getScriptEngine() != null)
+			return new InputTokenizer(v -> {
+				if (getScriptEngine().hasVariable(v)) {
+					final Object variable = getScriptEngine().getVariable(v);
+					return variable == null ? null : variable.getClass();
+				}
+
+				return null;
+			});
+
+		return new InputTokenizer();
+	}
+
+	@Override
+	public String getText() {
+		return fContent;
+	}
+
+	private String getRelevantText() {
+		return getText().substring(0, getReplaceOffset());
+	}
+
+	@Override
+	public int getReplaceOffset() {
+		return fCursorPosition;
+	}
+
+	@Override
+	public int getReplaceLength() {
+		return 0;
+	}
+
+	@Override
+	public IScriptEngine getScriptEngine() {
+		return fScriptEngine;
+	}
+
+	@Override
+	public List<ModuleDefinition> getLoadedModules() {
+		final List<ModuleDefinition> modules = new ArrayList<>();
+
+		if (getScriptEngine() != null) {
+			modules.addAll(ModuleHelper.getLoadedModules(getScriptEngine()));
+
+		} else {
+			modules.addAll(getLoadedModules(getRelevantText()));
+
+			if (modules.stream().filter(m -> EnvironmentModule.MODULE_NAME.equals(m.getName())).count() == 0)
+				modules.add(ModuleHelper.resolveModuleName(EnvironmentModule.MODULE_NAME));
+		}
+
+		return modules;
+	}
+
+	private static final Pattern LOAD_MODULE_PATTERN = Pattern.compile(EnvironmentModule.LOAD_MODULE_METHOD + "\\([\\\"\\'](.*?)[\\\"\\']");
+
+	private Collection<? extends ModuleDefinition> getLoadedModules(String content) {
+		final List<ModuleDefinition> modules = new ArrayList<>();
+
+		final Matcher matcher = LOAD_MODULE_PATTERN.matcher(content);
+		while (matcher.find()) {
+			final String moduleId = matcher.group(1);
+			final ModuleDefinition moduleDefinition = ModuleHelper.resolveModuleName(moduleId);
+			if (moduleDefinition != null) {
+				if (modules.stream().filter(m -> moduleId.equals(m.getName())).count() == 0)
+					modules.add(moduleDefinition);
+			}
+		}
+
+		return modules;
+	}
+
+	private Map<Object, String> getIncludes() {
+		return Collections.emptyMap();
+	}
+
+	@Override
+	public String getFilterToken() {
+		final Object lastToken = new TokenList(getTokens()).getLastToken();
+		return (isFilter(lastToken)) ? lastToken.toString() : "";
+	}
+
+	@Override
+	public String getFilter() {
+		final String filter = getFilterToken();
+
+		return isStringLiteral(filter) ? filter.substring(1) : filter;
+	}
+
+	private boolean isFilter(final Object lastToken) {
+		return (lastToken instanceof String) && (!InputTokenizer.isDelimiter(lastToken));
+	}
+
+	@Override
+	public boolean isStringLiteral(String input) {
+		return input.startsWith("\"");
+	}
+
+	@Override
+	public ScriptType getScriptType() {
+		return fScriptType;
+	}
+
+	@Override
+	public Object getResource() {
+		return fResource;
+	}
+}
diff --git a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/CodeCompletionAggregator.java b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/CodeCompletionAggregator.java
index ce40ac4..7f67a76 100644
--- a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/CodeCompletionAggregator.java
+++ b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/CodeCompletionAggregator.java
@@ -13,234 +13,115 @@
 
 package org.eclipse.ease.ui.completion;
 
+import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IConfigurationElement;
 import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.Platform;
-import org.eclipse.core.runtime.Status;
-import org.eclipse.core.runtime.jobs.Job;
-import org.eclipse.ease.ICodeParser;
+import org.eclipse.core.runtime.NullProgressMonitor;
 import org.eclipse.ease.ICompletionContext;
 import org.eclipse.ease.IScriptEngine;
 import org.eclipse.ease.Logger;
 import org.eclipse.ease.service.ScriptType;
+import org.eclipse.ease.tools.PlatformExtension;
 import org.eclipse.ease.ui.Activator;
+import org.eclipse.ease.ui.completion.provider.ICompletionProvider;
 import org.eclipse.jface.fieldassist.IContentProposal;
 import org.eclipse.jface.fieldassist.IContentProposalProvider;
-import org.eclipse.jface.text.contentassist.ICompletionProposal;
 
-/**
- * Dispatcher class create code completion proposals.
- *
- * First checks all registered {@link ICompletionProvider} objects to get the {@link ICompletionContext} for the desired line.
- *
- * Then uses all registered {@link ICompletionProvider} objects to calculate the {@link IContentProposal} array for {@link #getProposals(String, int)}.
- *
- * TODO: Refactor to use multi-threading.
- *
- * @author Martin Kloesch
- *
- */
 public class CodeCompletionAggregator implements IContentProposalProvider {
 
-	/**
-	 * String constant for codeCompletionProvider extension point.
-	 */
-	public static final String COMPLETION_PROCESSOR = "org.eclipse.ease.ui.codeCompletionProvider";
+	private static final String SCRIPT_COMPLETION_EXTENSION_POINT = "org.eclipse.ease.ui.codeCompletionProvider";
 
-	/**
-	 * String constant for script type attribute of codeCompletionProvider extension.
-	 */
-	public static final String ATTRIBUTE_SCRIPT_TYPE = "scriptType";
+	private final IScriptEngine fScriptEngine;
+	private ScriptType fScriptType;
+	private final Object fResource;
 
-	/**
-	 * String constant for class attribute of codeCompletionProvider extension.
-	 */
-	public static final String ATTRIBUTE_CLASS = "class";
+	private final List<ICompletionProvider> fStaticCompletionProviders = new ArrayList<>();
 
-	/** Timeout for completion processor. */
-	private static final long COMPLETION_TIMEOUT = 500;
+	public CodeCompletionAggregator(IScriptEngine scriptEngine) {
+		if (scriptEngine == null)
+			throw new IllegalArgumentException("scriptEngine cannot be null");
 
-	/**
-	 * Retrieve all {@link ICompletionProvider}s matching a given script type.
-	 *
-	 * @param scriptType
-	 *            script type filter for code completion proposal providers. May be <code>null</code> to get all providers.
-	 *
-	 * @return list of all matching {@link ICompletionProvider}s.
-	 */
-	private static Collection<ICompletionProvider> getProviders(final String scriptType) {
-		final Collection<ICompletionProvider> providers = new HashSet<>();
-
-		final IConfigurationElement[] elements = Platform.getExtensionRegistry().getConfigurationElementsFor(COMPLETION_PROCESSOR);
-		for (final IConfigurationElement element : elements) {
-
-			if (scriptType != null) {
-				final String registeredType = element.getAttribute(ATTRIBUTE_SCRIPT_TYPE);
-				if ((registeredType != null) && (!registeredType.isEmpty()) && (!scriptType.equals(registeredType)))
-					// ignore as script type does not match
-					continue;
-			}
-
-			try {
-				final Object candidate = element.createExecutableExtension(ATTRIBUTE_CLASS);
-				if (candidate instanceof ICompletionProvider)
-					// register provider
-					providers.add((ICompletionProvider) candidate);
-
-			} catch (final CoreException e) {
-				Logger.error(Activator.PLUGIN_ID, "Invalid completion provider detected in " + element.getContributor().getName(), e);
-			}
-		}
-
-		return providers;
-	}
-
-	/**
-	 * Static code analyzer to split given line of code to base {@link ICompletionContext} for {@link ICompletionProvider#refineContext(ICompletionContext)}.
-	 */
-	private ICodeParser fCodeParser;
-
-	/** All registered {@link ICompletionProvider}s. */
-	private Collection<ICompletionProvider> fCompletionProviders = Collections.emptySet();
-
-	/** Manually added {@link ICompletionProvider}s. */
-	private final Collection<ICompletionProvider> fAddedCompletionProviders = new HashSet<>();
-
-	/** Registered script engine. */
-	private IScriptEngine fScriptEngine;
-
-	/**
-	 * Setter method for ICompletionAnalyzer.
-	 *
-	 * @param codeParser
-	 *            {@link ICodeParser} for completion calculation.
-	 */
-	public void setCodeParser(final ICodeParser codeParser) {
-		fCodeParser = codeParser;
-	}
-
-	public char[] getActivationChars() {
-		return new char[] { '.' };
-	}
-
-	/**
-	 * Sets the given script engine for all registered completion providers.
-	 *
-	 * @param scriptEngine
-	 *            {@link IScriptEngine} to be set.
-	 */
-	public void setScriptEngine(final IScriptEngine scriptEngine) {
 		fScriptEngine = scriptEngine;
-
-		if (fScriptEngine != null) {
-			final List<ScriptType> supportedScriptTypes = fScriptEngine.getDescription().getSupportedScriptTypes();
-			if (supportedScriptTypes.size() > 0) {
-				setScriptType(supportedScriptTypes.get(0));
-			}
-		}
+		setScriptType(fScriptEngine.getDescription().getSupportedScriptTypes().stream().findAny().orElse(null));
+		fResource = null;
 	}
 
-	public void setScriptType(final ScriptType scriptType) {
-		setCodeParser(scriptType.getCodeParser());
-		fCompletionProviders = getProviders(scriptType.getName());
-		fCompletionProviders.addAll(fAddedCompletionProviders);
+	public CodeCompletionAggregator(Object resource, ScriptType scriptType) {
+		fScriptEngine = null;
+		setScriptType(scriptType);
+		fResource = resource;
 	}
 
-	public void addCompletionProvider(ICompletionProvider completionProvider) {
-		fAddedCompletionProviders.add(completionProvider);
+	private void setScriptType(ScriptType scriptType) {
+		if (scriptType == null)
+			throw new IllegalArgumentException("scriptType cannot be detected");
+
+		fScriptType = scriptType;
 	}
 
-	/**
-	 * Calculate relevant completion proposals.
-	 *
-	 * @param resource
-	 *            resource that contains relevantText. May be <code>null</code>
-	 * @param relevantText
-	 *            text that is relevant for completion calculation
-	 * @param insertOffset
-	 *            cursor position within relevatText
-	 * @param selectionRange
-	 * @param monitor
-	 *            job monitor for calculation termination
-	 * @return
-	 */
-	public List<ICompletionProposal> getCompletionProposals(final Object resource, final String relevantText, final int insertOffset, final int selectionRange,
-			final IProgressMonitor monitor) {
+	@Override
+	public IContentProposal[] getProposals(String contents, int position) {
+		return getProposals(contents, position, new NullProgressMonitor()).toArray(new IContentProposal[0]);
+	}
 
-		final LinkedList<ICompletionProposal> proposals = new LinkedList<>();
-		final Job completionProcessorJob = new Job("Calculate EASE code completions") {
+	public List<ScriptCompletionProposal> getProposals(String content, int cursorPosition, IProgressMonitor monitor) {
+		final List<ScriptCompletionProposal> proposals = new LinkedList<>();
 
-			@Override
-			protected IStatus run(IProgressMonitor monitor) {
-				try {
-					if (monitor.isCanceled())
-						return Status.CANCEL_STATUS;
+		final ICompletionContext context = createContext(content, cursorPosition);
 
-					final ICompletionContext context = createContext(resource, relevantText, insertOffset, selectionRange);
-
-					if (context != null) {
-						for (final ICompletionProvider provider : fCompletionProviders) {
-							try {
-								if (monitor.isCanceled())
-									return Status.CANCEL_STATUS;
-
-								if (provider.isActive(context))
-									proposals.addAll(provider.getProposals(context));
-
-							} catch (final Throwable ex) {
-								Logger.error(Activator.PLUGIN_ID, "Could not get proposals from ICompletionProvider <" + provider.getClass().getName() + ">",
-										ex);
-							}
-						}
-					}
-
-				} finally {
-					synchronized (this) {
-						notifyAll();
-					}
-				}
-
-				return Status.OK_STATUS;
-			}
-		};
-
-		completionProcessorJob.schedule();
-
-		synchronized (completionProcessorJob) {
+		for (final ICompletionProvider provider : getProposalProviders()) {
 			try {
-				completionProcessorJob.wait(COMPLETION_TIMEOUT);
-			} catch (final InterruptedException e) {
+				if (provider.isActive(context))
+					proposals.addAll(provider.getProposals(context));
+
+			} catch (final Throwable e) {
+				Logger.error(Activator.PLUGIN_ID, "Code completion provider failed", e);
 			}
 		}
 
 		return proposals;
 	}
 
-	private ICompletionContext createContext(final Object resource, final String relevantText, final int insertOffset, final int selectionRange) {
-		if (fCodeParser != null)
-			return fCodeParser.getContext(fScriptEngine, resource, relevantText, insertOffset, selectionRange);
+	private ICompletionContext createContext(String content, int cursorPosition) {
+		if (fScriptEngine == null)
+			return new BasicContext(fScriptType, null, content, cursorPosition);
 
-		return null;
+		return new BasicContext(fScriptEngine, content, cursorPosition);
 	}
 
-	@Override
-	public IContentProposal[] getProposals(final String contents, final int position) {
-		final List<ICompletionProposal> proposals = getCompletionProposals(null, contents, position, 0, null);
-		Collections.sort(proposals, (o1, o2) -> {
-			if ((o1 instanceof ScriptCompletionProposal) && (o2 instanceof ScriptCompletionProposal))
-				return ((ScriptCompletionProposal) o1).compareTo((ScriptCompletionProposal) o2);
+	private List<ICompletionProvider> getProposalProviders() {
+		final List<ICompletionProvider> providers = new ArrayList<>();
+		providers.addAll(getExtensionProposalProviders());
+		providers.addAll(getLocalProposalProviders());
+		return providers;
+	}
 
-			return o1.getDisplayString().compareTo(o2.getDisplayString());
-		});
+	private List<ICompletionProvider> getLocalProposalProviders() {
+		return fStaticCompletionProviders;
+	}
 
-		return proposals.toArray(new IContentProposal[proposals.size()]);
+	private List<ICompletionProvider> getExtensionProposalProviders() {
+		final Collection<PlatformExtension> extensions = PlatformExtension.createForName(SCRIPT_COMPLETION_EXTENSION_POINT, "codeCompletionProvider");
+
+		return extensions.stream().filter(e -> matchesScriptType(e.getAttribute("scriptType"))).map(e -> {
+			try {
+				return e.createInstance("class", ICompletionProvider.class);
+			} catch (CoreException | ClassCastException ex) {
+				return null;
+			}
+		}).filter(p -> p != null).collect(Collectors.toList());
+	}
+
+	private boolean matchesScriptType(String extensionScriptType) {
+		return (extensionScriptType == null) || extensionScriptType.isEmpty() || extensionScriptType.equals(fScriptType.getName());
+	}
+
+	public void addCompletionProvider(ICompletionProvider completionProvider) {
+		fStaticCompletionProviders.add(completionProvider);
 	}
 }
diff --git a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/CompletionContext.java b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/CompletionContext.java
deleted file mode 100644
index d56ecbc..0000000
--- a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/CompletionContext.java
+++ /dev/null
@@ -1,723 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Martin Kloesch and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v2.0
- * which accompanies this distribution, and is available at
- * https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License_Identifier: EPL-2.0
- *
- * Contributors:
- *     Martin Kloesch - initial API and implementation
- *     Christian Pontesegger - rewrite of implementation
- *******************************************************************************/
-
-package org.eclipse.ease.ui.completion;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.eclipse.ease.ICompletionContext;
-import org.eclipse.ease.IScriptEngine;
-import org.eclipse.ease.classloader.EaseClassLoader;
-import org.eclipse.ease.modules.EnvironmentModule;
-import org.eclipse.ease.modules.ModuleDefinition;
-import org.eclipse.ease.modules.ModuleHelper;
-import org.eclipse.ease.service.ScriptType;
-import org.eclipse.ease.tools.ResourceTools;
-import org.eclipse.jface.text.Position;
-
-/**
- * The context evaluates and stores information on the code fragment at a given cursor position.
- */
-public abstract class CompletionContext implements ICompletionContext {
-
-	public static class Bracket {
-
-		private int fStart = -1;
-		private int fEnd = -1;
-
-		public Bracket(final int start, final int end) {
-			fStart = start;
-			fEnd = end;
-		}
-	}
-
-	private static final Pattern JAVA_PACKAGE_PATTERN = Pattern.compile("([A-Za-z]+\\.?)+");
-
-	private final IScriptEngine fScriptEngine;
-	private Object fResource;
-	private ScriptType fScriptType;
-	private String fOriginalCode = "";
-
-	private final Map<Object, String> fIncludes = new HashMap<>();
-	private Collection<ModuleDefinition> fLoadedModules = null;
-	private Class<? extends Object> fReferredClazz;
-	private String fFilter = "";
-	private Type fType = Type.UNKNOWN;
-	private String fPackage;
-	private String fCaller = "";
-	private int fParameterOffset = -1;
-
-	private int fOffset;
-	private int fSelectionRange;
-
-	/** Global classloader to resolve unknown java classes (lazily loaded). */
-	private EaseClassLoader fGlobalClassLoader = null;
-
-	/**
-	 * Context constructor. A context is bound to a given script engine or script type.
-	 *
-	 * @param scriptEngine
-	 *            script engine to evaluate
-	 * @param scriptType
-	 *            script type to evaluate
-	 */
-	public CompletionContext(final IScriptEngine scriptEngine, final ScriptType scriptType) {
-		fScriptEngine = scriptEngine;
-		fScriptType = scriptType;
-
-		if ((fScriptType == null) && (fScriptEngine != null))
-			fScriptType = fScriptEngine.getDescription().getSupportedScriptTypes().get(0);
-	}
-
-	@Override
-	public Type getType() {
-		return fType;
-	}
-
-	@Override
-	public Class<? extends Object> getReferredClazz() {
-		return fReferredClazz;
-	}
-
-	/**
-	 * Calculate a context over a given code fragment.
-	 *
-	 * @param resource
-	 *            base resource (eg. edited file)
-	 * @param code
-	 *            code fragment to evaluate
-	 * @param offset
-	 *            the offset within the provided document (usually code.length())
-	 * @param selectionRange
-	 *            amount of selected characters
-	 */
-	public void calculateContext(final Object resource, String code, final int offset, final int selectionRange) {
-		fOffset = offset;
-		fSelectionRange = selectionRange;
-		fIncludes.clear();
-		fLoadedModules = null;
-
-		fResource = resource;
-		fOriginalCode = code;
-		fReferredClazz = null;
-		fFilter = "";
-
-		// process include() calls
-		addInclude(getOriginalCode());
-
-		// remove irrelevant parts
-		code = simplifyCode();
-
-		parseCode(code);
-	}
-
-	/**
-	 * Try to evaluate the calling method or class.
-	 *
-	 * @param code
-	 *            code fragment to parse
-	 */
-	protected void parseCode(final String code) {
-
-		// sometimes simplifyCode() already detects the type correctly, no need for further parsing
-		if (getType() == Type.UNKNOWN) {
-			final int dotDelimiter = code.lastIndexOf('.');
-			if (dotDelimiter == -1) {
-				fReferredClazz = null;
-				fType = code.endsWith(")") ? Type.UNKNOWN : Type.NONE;
-				fFilter = code;
-
-			} else {
-				fFilter = code.substring(dotDelimiter + 1);
-				fReferredClazz = getClazz(code.substring(0, dotDelimiter).trim());
-
-				if (fReferredClazz == null) {
-					// maybe we have a package
-					if (JAVA_PACKAGE_PATTERN.matcher(code.subSequence(0, dotDelimiter)).matches()) {
-						fType = Type.PACKAGE;
-						fPackage = code.subSequence(0, dotDelimiter).toString();
-					}
-				}
-			}
-		}
-	}
-
-	/**
-	 * Try to remove unnecessary information from code fragment to simplify parsing.
-	 *
-	 * @return simplified code fragment
-	 */
-	protected String simplifyCode() {
-		// only operate on last line
-		final int lineFeedPosition = getOriginalCode().lastIndexOf('\n');
-		String code = (lineFeedPosition > 0) ? getOriginalCode().substring(lineFeedPosition) : getOriginalCode();
-		code = code.trim();
-
-		// remove all literals with dummies for simpler parsing: "some 'literal'" -> ""
-		code = replaceStringLiterals(code);
-		if (fType == Type.STRING_LITERAL) {
-			// we are within a string literal, cannot simplify further
-
-			// try to detect calling method
-			if (!code.isEmpty()) {
-				final int openingBracket = findMatchingBracket(code + ")", code.length());
-				if (openingBracket != -1) {
-					// caller found
-					fCaller = code.substring(0, openingBracket);
-
-					String callerParameters = code.substring(openingBracket + 1);
-					callerParameters = removeMethodCalls(callerParameters);
-					fParameterOffset = callerParameters.split(",").length - 1;
-				}
-			}
-
-			return code;
-		}
-
-		// if we find an opening bracket with no closing bracket, we can forget about everything left from it
-		final Collection<Bracket> brackets = matchBrackets(code, '(', ')');
-
-		int truncatePosition = -1;
-		for (final Bracket bracket : brackets) {
-			if ((bracket.fStart >= 0) && (bracket.fEnd == -1)) {
-				// found an open bracket
-				truncatePosition = Math.max(truncatePosition, bracket.fStart + 1);
-			}
-		}
-
-		// try to truncate parameters
-		for (int pos = code.length() - 1; pos >= 0; pos--) {
-			final char c = code.charAt(pos);
-			if ((c == ' ') || (c == '\t') || (c == ',') || (c == '!') || (c == '=') || (c == '<') || (c == '>') || (c == '+') || (c == '-') || (c == '*')
-					|| (c == '/') || (c == '%') || (c == '&') || (c == '|') || (c == '^')) {
-
-				// we have a separation character (operator, comma)
-				if (getBracket(brackets, pos) == null) {
-					// outside of a closed bracket, therefore we can truncate here
-					truncatePosition = Math.max(truncatePosition, pos + 1);
-
-					// parsing further to the left is pointless as truncatePosition cannot get bigger anymore
-					break;
-				}
-			}
-		}
-
-		if (truncatePosition != -1)
-			code = code.substring(truncatePosition);
-
-		return code;
-	}
-
-	private static int countOccurrence(final String string, final char character) {
-		int count = 0;
-		for (final char c : string.toCharArray()) {
-			if (c == character)
-				count++;
-		}
-
-		return count;
-	}
-
-	/**
-	 * Remove all brackets from method calls along with their content. Eg. transforms "some(call() + 3, test()), another(4)" to "some, another".
-	 *
-	 * @param code
-	 *            string to parse
-	 * @return transformed string
-	 */
-	private static String removeMethodCalls(String code) {
-		int closingBracket = code.lastIndexOf(')');
-		while (closingBracket != -1) {
-			final int openingBracket = findMatchingBracket(code, closingBracket);
-			if (openingBracket != -1)
-				code = code.substring(0, openingBracket) + code.substring(closingBracket + 1);
-			else
-				// error, no opening bracket, giving up
-				return code;
-
-			// find next location
-			closingBracket = code.lastIndexOf(')');
-		}
-
-		return code;
-	}
-
-	/**
-	 * Try to evaluate the class returned from a function or an object.
-	 *
-	 * @param code
-	 *            code to evaluate
-	 * @return class or <code>null</code>
-	 */
-	private Class<? extends Object> getClazz(String code) {
-		code = code.trim();
-		String parameters = null;
-		if (code.endsWith(")")) {
-			final int bracketOpenPosition = findMatchingBracket(code, code.length() - 1);
-
-			// extract parameters in case we have multiple candidates
-			parameters = code.substring(bracketOpenPosition + 1, code.length() - 1);
-			code = code.substring(0, bracketOpenPosition);
-		}
-
-		// lets try: invoke class
-		try {
-			final Class<?> clazz = CompletionContext.class.getClassLoader().loadClass(code);
-			fType = (parameters != null) ? Type.CLASS_INSTANCE : Type.STATIC_CLASS;
-			return clazz;
-
-		} catch (final ClassNotFoundException e) {
-			// did not work, we need to dig deeper
-		}
-
-		final int dotDelimiter = code.lastIndexOf('.');
-		if (dotDelimiter == -1) {
-
-			if (parameters != null) {
-				// maybe a function call
-				final Method method = getMethodDefinition(code);
-				if (method != null) {
-					fType = Type.CLASS_INSTANCE;
-					return method.getReturnType();
-				}
-
-				// giving up
-				return null;
-
-			} else {
-				// maybe a field
-				final Field field = getFieldDefinition(code);
-				if (field != null) {
-					fType = Type.CLASS_INSTANCE;
-					return field.getType();
-				}
-
-				// must be a script variable
-				Class<? extends Object> clazz = getVariableClazz(code);
-				if (clazz != null) {
-					fType = Type.CLASS_INSTANCE;
-					return clazz;
-				}
-
-				// maybe a variable and we find a definition somewhere in the previous code
-				clazz = parseVariableType(code);
-				if (clazz != null) {
-					fType = Type.CLASS_INSTANCE;
-					return clazz;
-				}
-
-				// giving up
-				return null;
-			}
-		}
-
-		final String keyWord = code.substring(dotDelimiter + 1);
-		code = code.substring(0, dotDelimiter).trim();
-
-		if (!code.isEmpty()) {
-			final Class<? extends Object> clazz = getClazz(code);
-			if (clazz != null) {
-				if (parameters != null) {
-					// searching for a method
-					for (final Method method : clazz.getMethods()) {
-						if (method.getName().matches(keyWord)) {
-							fType = Type.CLASS_INSTANCE;
-							return method.getReturnType();
-						}
-					}
-
-				} else {
-					// searching for a field
-					for (final Field field : clazz.getFields()) {
-						if (field.getName().matches(keyWord)) {
-							fType = Type.CLASS_INSTANCE;
-							return field.getType();
-						}
-					}
-				}
-			}
-		}
-
-		return null;
-	}
-
-	/**
-	 * Parse source code for a variable type definition. Type definitions are comments right before a variable definition in the form: // @type java.lang.String
-	 *
-	 * @param name
-	 *            variable name to look up
-	 * @return variable class type
-	 */
-	protected Class<? extends Object> parseVariableType(final String name) {
-
-		final List<String> sources = new ArrayList<>();
-		sources.add(getOriginalCode());
-		sources.addAll(getIncludedResources().values());
-
-		final Pattern pattern = Pattern.compile("@type\\s([a-zA-Z0-9_\\.]+)\\s*$\\s*.*?" + Pattern.quote(name) + "\\s*=", Pattern.MULTILINE);
-
-		for (final String source : sources) {
-			final Matcher matcher = pattern.matcher(source);
-			if (matcher.find()) {
-				try {
-					return getGlobalClassLoader().loadClass(matcher.group(1));
-				} catch (final ClassNotFoundException e) {
-					// did not work, invalid definition, giving up
-					return null;
-				}
-			}
-		}
-
-		return null;
-	}
-
-	/**
-	 * Return a classloader that can access all files avaliable in the RCP application.
-	 *
-	 * @return global classloader
-	 */
-	private EaseClassLoader getGlobalClassLoader() {
-		if (fGlobalClassLoader == null)
-			fGlobalClassLoader = new EaseClassLoader();
-
-		return fGlobalClassLoader;
-	}
-
-	/**
-	 * Retrieve a method definition from loaded modules.
-	 *
-	 * @param code
-	 *            method call to look up
-	 * @return method definition or <code>null</code>
-	 */
-	private Method getMethodDefinition(final String code) {
-		for (final ModuleDefinition definition : getLoadedModules()) {
-			for (final Method method : definition.getMethods()) {
-				if (method.getName().equals(code))
-					return method;
-			}
-		}
-
-		return null;
-	}
-
-	/**
-	 * Retrieve a field definition from loaded modules.
-	 *
-	 * @param code
-	 *            field to look up
-	 * @return field definition or <code>null</code>
-	 */
-	private Field getFieldDefinition(final String code) {
-		for (final ModuleDefinition definition : getLoadedModules()) {
-			for (final Field field : definition.getFields()) {
-				if (field.getName().equals(code))
-					return field;
-			}
-		}
-
-		return null;
-	}
-
-	/**
-	 * Retrieve a variable from a running script engine.
-	 *
-	 * @param name
-	 *            variable to look up
-	 * @return variable class or <code>null</code>
-	 */
-	protected Class<? extends Object> getVariableClazz(final String name) {
-		if (getScriptEngine() != null) {
-			final Object variable = getScriptEngine().getVariable(name);
-			if (variable != null)
-				return variable.getClass();
-		}
-
-		return null;
-	}
-
-	/**
-	 * Find the corresponding opening bracket to a closing bracket. Therefore we search the string to the left.
-	 *
-	 * @param code
-	 *            code fragment to parse
-	 * @param offset
-	 *            offset position of the closing bracket
-	 * @return offset of the corresponding opening bracket or -1
-	 */
-	private static int findMatchingBracket(final String string, int offset) {
-		int openBrackets = 0;
-		do {
-			if (string.charAt(offset) == ')')
-				openBrackets++;
-			else if (string.charAt(offset) == '(')
-				openBrackets--;
-
-			offset--;
-
-		} while ((openBrackets > 0) && (offset >= 0));
-
-		return (openBrackets > 0) ? -1 : offset + 1;
-	}
-
-	/**
-	 * Remove all string literal content and keep empty literals.
-	 *
-	 * @param code
-	 *            code fragment to parse
-	 * @return code fragment with empty string literals
-	 */
-	public String replaceStringLiterals(final String code) {
-		final StringBuilder simplifiedString = new StringBuilder();
-		final StringBuilder literalContent = new StringBuilder();
-
-		Character currentLiteral = null;
-		for (int index = 0; index < code.length(); index++) {
-			if (isLiteral(code.charAt(index))) {
-				if ((index == 0) || (code.charAt(index - 1) != '\\')) {
-					// not escaped
-
-					if (currentLiteral == null) {
-						// start new literal
-						currentLiteral = code.charAt(index);
-
-					} else if (currentLiteral == code.charAt(index)) {
-						// close literal
-						literalContent.delete(0, literalContent.length());
-
-						simplifiedString.append(currentLiteral);
-						simplifiedString.append(currentLiteral);
-
-						currentLiteral = null;
-					}
-
-					continue;
-				}
-			}
-
-			// process character
-			if (currentLiteral == null)
-				simplifiedString.append(code.charAt(index));
-			else
-				literalContent.append(code.charAt(index));
-		}
-
-		if (currentLiteral != null) {
-			fFilter = literalContent.toString();
-			fType = Type.STRING_LITERAL;
-		}
-
-		return simplifiedString.toString();
-	}
-
-	/**
-	 * See if a character matches a string literal token.
-	 *
-	 * @param candidate
-	 *            character to test
-	 * @return <code>true</code> when character is a string literal token
-	 */
-	protected abstract boolean isLiteral(final char candidate);
-
-	private void addLoadedModules(final String code) {
-		final List<Position> modulePositions = AbstractCompletionParser.findInvocations("loadModule(java.lang.String, boolean)", code);
-
-		for (final Position position : modulePositions) {
-			final String call = code.substring(position.getOffset(), position.getOffset() + position.getLength());
-			final String[] parameters = AbstractCompletionParser.getParameters(call);
-			if (parameters.length > 0) {
-				final String candidate = parameters[0].trim();
-
-				if (candidate.charAt(0) == candidate.charAt(candidate.length() - 1)) {
-					// TODO add string literal characters lookup method
-					if ((candidate.charAt(0) == '"') || (candidate.charAt(0) == '\'')) {
-						// found loadModule, try to resolve
-						final ModuleDefinition moduleDefinition = ModuleHelper.resolveModuleName(candidate.substring(1, candidate.length() - 1));
-						if (moduleDefinition != null)
-							fLoadedModules.add(moduleDefinition);
-					}
-				}
-			}
-		}
-	}
-
-	/**
-	 * @param originalCode
-	 */
-	private void addInclude(final String code) {
-		final List<Position> includePositions = AbstractCompletionParser.findInvocations("include(java.lang.String)", code);
-
-		for (final Position position : includePositions) {
-			try {
-				final String call = code.substring(position.getOffset(), position.getOffset() + position.getLength());
-				final String[] parameters = AbstractCompletionParser.getParameters(call);
-				if (parameters.length > 0) {
-					final String candidate = parameters[0].trim();
-
-					if (candidate.charAt(0) == candidate.charAt(candidate.length() - 1)) {
-						// TODO add string literal characters lookup method
-						if ((candidate.charAt(0) == '"') || (candidate.charAt(0) == '\'')) {
-							// found resource, try to resolve
-							final Object includeResource = ResourceTools.resolve(candidate.substring(1, candidate.length() - 1), getResource());
-							if (includeResource != null) {
-								if (!fIncludes.containsKey(includeResource)) {
-									// store object & content, as we need to parse this content multiple times
-									fIncludes.put(includeResource, ResourceTools.toString(includeResource));
-
-									// recursively process include files
-									addInclude(fIncludes.get(includeResource));
-								}
-							}
-						}
-					}
-				}
-			} catch (final Exception e) {
-				// ignore invalid include locations
-			}
-		}
-	}
-
-	@Override
-	public String getOriginalCode() {
-		return fOriginalCode;
-	}
-
-	@Override
-	public String getProcessedCode() {
-		return getOriginalCode();
-	}
-
-	@Override
-	public Object getResource() {
-		return fResource;
-	}
-
-	@Override
-	public IScriptEngine getScriptEngine() {
-		return fScriptEngine;
-	}
-
-	@Override
-	public ScriptType getScriptType() {
-		return fScriptType;
-	}
-
-	@Override
-	public Collection<ModuleDefinition> getLoadedModules() {
-		if (fLoadedModules == null) {
-			// lazy loading of modules
-			fLoadedModules = new HashSet<>();
-
-			// add default environment module
-			fLoadedModules.add(ModuleHelper.resolveModuleName(EnvironmentModule.MODULE_NAME));
-
-			// process loadModule() calls
-			addLoadedModules(getOriginalCode());
-
-			for (final String includeContent : fIncludes.values()) {
-				// recursively process include files for loadModule() calls
-				if (includeContent != null)
-					addLoadedModules(includeContent);
-			}
-
-			// add loaded modules from script engine
-			if (getScriptEngine() != null) {
-				for (final ModuleDefinition definition : ModuleHelper.getLoadedModules(getScriptEngine()))
-					fLoadedModules.add(definition);
-			}
-		}
-
-		return fLoadedModules;
-	}
-
-	@Override
-	public Map<Object, String> getIncludedResources() {
-		return fIncludes;
-	}
-
-	@Override
-	public String getFilter() {
-		return fFilter;
-	}
-
-	@Override
-	public int getOffset() {
-		return fOffset;
-	}
-
-	@Override
-	public int getSelectionRange() {
-		return fSelectionRange;
-	}
-
-	@Override
-	public String getPackage() {
-		return fPackage;
-	}
-
-	@Override
-	public String getCaller() {
-		return fCaller;
-	}
-
-	@Override
-	public int getParameterOffset() {
-		return fParameterOffset;
-	}
-
-	private static Collection<Bracket> matchBrackets(final String code, final char openChar, final char closeChar) {
-		final List<Bracket> brackets = new ArrayList<>();
-
-		for (int pos = 0; pos < code.length(); pos++) {
-			final char c = code.charAt(pos);
-			if (c == openChar) {
-				// push new Bracket
-				brackets.add(0, new Bracket(pos, -1));
-
-			} else if (c == closeChar) {
-				boolean found = false;
-				for (final Bracket bracket : brackets) {
-					if (bracket.fEnd == -1) {
-						bracket.fEnd = pos;
-						found = true;
-						break;
-					}
-				}
-
-				if (!found)
-					brackets.add(0, new Bracket(-1, pos));
-			}
-		}
-
-		return brackets;
-	}
-
-	private static Bracket getBracket(final Collection<Bracket> brackets, final int pos) {
-		for (final Bracket bracket : brackets) {
-			if ((bracket.fStart != -1) && (bracket.fStart <= pos) && (bracket.fEnd != -1) && (bracket.fEnd > pos))
-				return bracket;
-		}
-
-		return null;
-	}
-}
\ No newline at end of file
diff --git a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/IImageResolver.java b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/IImageResolver.java
index 472d010..fb5bce2 100644
--- a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/IImageResolver.java
+++ b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/IImageResolver.java
@@ -19,7 +19,7 @@
 
 	/**
 	 * Returns an image instance. Only called when the actual image gets displayed.
-	 * 
+	 *
 	 * @return image to be displayed in proposal
 	 */
 	Image getImage();
diff --git a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/ScriptCompletionProposal.java b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/ScriptCompletionProposal.java
index 0bb22d4..e3f4073 100644
--- a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/ScriptCompletionProposal.java
+++ b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/ScriptCompletionProposal.java
@@ -68,6 +68,7 @@
 	public ScriptCompletionProposal(final ICompletionContext context, final StyledString styledString, final String replacementString,
 			final IImageResolver imageResolver, final int sortOrder, final IHelpResolver helpResolver) {
 		this(context, styledString.getString(), replacementString, imageResolver, sortOrder, helpResolver);
+
 		fStyledString = styledString;
 	}
 
@@ -101,11 +102,7 @@
 	@Override
 	public void apply(final IDocument document) {
 		try {
-			if (fContext.getFilter() != null)
-				document.replace(fContext.getOffset() - fContext.getFilter().length(), fContext.getFilter().length(), fReplacementString);
-
-			else
-				document.replace(fContext.getOffset(), 0, fReplacementString);
+			document.replace(fContext.getReplaceOffset(), fContext.getReplaceLength(), fReplacementString);
 
 		} catch (final BadLocationException e) {
 			Logger.error(Activator.PLUGIN_ID, "Could not insert completion proposal into document", e);
@@ -114,11 +111,7 @@
 
 	@Override
 	public Point getSelection(final IDocument document) {
-		if (fContext.getFilter() != null)
-			return new Point((fContext.getOffset() - fContext.getFilter().length()) + fReplacementString.length(), 0);
-
-		else
-			return new Point(fContext.getOffset() + fReplacementString.length(), 0);
+		return new Point(fContext.getReplaceOffset() + fReplacementString.length(), 0);
 	}
 
 	@Override
@@ -159,33 +152,26 @@
 	// ------------------------------------------------------------------
 	@Override
 	public String getContent() {
-		final String original = fContext.getOriginalCode();
-		return original.substring(0, original.length() - fContext.getFilter().length()) + fReplacementString;
+		final String prefix = fContext.getText().substring(0, fContext.getReplaceOffset() - fContext.getFilter().length());
+		final String suffix = fContext.getText().substring(fContext.getReplaceOffset());
+
+		return prefix + fReplacementString + suffix;
 	}
 
 	@Override
 	public int getCursorPosition() {
-		return getContent().length();
+		final String prefix = fContext.getText().substring(0, fContext.getReplaceOffset() - fContext.getFilter().length());
+
+		return (prefix + fReplacementString).length();
 	}
 
 	@Override
 	public String getLabel() {
-		return getDisplayString() + "x";
+		return getDisplayString();
 	}
 
 	@Override
 	public String getDescription() {
 		return getAdditionalProposalInfo();
 	}
-
-	// ------------------------------------------------------------------
-	// Custom methods for script completion in EASE
-	// ------------------------------------------------------------------
-	public String getReplacementString() {
-		return fReplacementString;
-	}
-
-	public int getCursorStartPosition() {
-		return fContext.getOriginalCode().length() - fContext.getFilter().length();
-	}
 }
diff --git a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/provider/AbstractCompletionProvider.java b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/provider/AbstractCompletionProvider.java
new file mode 100644
index 0000000..a22a825
--- /dev/null
+++ b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/provider/AbstractCompletionProvider.java
@@ -0,0 +1,187 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License_Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.ui.completion.provider;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.ease.ICompletionContext;
+import org.eclipse.ease.modules.ModuleDefinition;
+import org.eclipse.ease.service.IScriptService;
+import org.eclipse.ease.ui.completion.IHelpResolver;
+import org.eclipse.ease.ui.completion.IImageResolver;
+import org.eclipse.ease.ui.completion.ScriptCompletionProposal;
+import org.eclipse.ease.ui.completion.tokenizer.InputTokenizer;
+import org.eclipse.ease.ui.completion.tokenizer.TokenList;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.viewers.StyledString;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.ui.PlatformUI;
+
+public abstract class AbstractCompletionProvider implements ICompletionProvider {
+
+	public static class DescriptorImageResolver implements IImageResolver {
+		private final ImageDescriptor fDescriptor;
+
+		public DescriptorImageResolver() {
+			fDescriptor = null;
+		}
+
+		public DescriptorImageResolver(ImageDescriptor descriptor) {
+			fDescriptor = descriptor;
+		}
+
+		@Override
+		public Image getImage() {
+			return (getDescriptor() != null) ? getDescriptor().createImage() : null;
+		}
+
+		protected ImageDescriptor getDescriptor() {
+			return fDescriptor;
+		}
+	}
+
+	public static class WorkbenchDescriptorImageResolver extends DescriptorImageResolver {
+		private final String fIdentifier;
+
+		public WorkbenchDescriptorImageResolver(String identifier) {
+			super();
+
+			fIdentifier = identifier;
+		}
+
+		@Override
+		protected ImageDescriptor getDescriptor() {
+			return PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(fIdentifier);
+		}
+	}
+
+	protected static ModuleDefinition getModuleDefinition(final String identifier) {
+		final IScriptService scriptService = PlatformUI.getWorkbench().getService(IScriptService.class);
+		return scriptService.getModuleDefinition(identifier);
+	}
+
+	private Collection<ScriptCompletionProposal> fProposals = null;
+	private ICompletionContext fContext;
+
+	@Override
+	public boolean isActive(final ICompletionContext context) {
+		return true;
+	}
+
+	@Override
+	public Collection<ScriptCompletionProposal> getProposals(final ICompletionContext context) {
+		fContext = context;
+		fProposals = new ArrayList<>();
+
+		prepareProposals(context);
+
+		final Collection<ScriptCompletionProposal> result = fProposals;
+		fProposals = null;
+		fContext = null;
+		return result;
+	}
+
+	/**
+	 * Get the current context. Only valid during proposal evaluation. Clients may retrieve the content when {@link #prepareProposals(ICompletionContext)} is
+	 * called.
+	 *
+	 * @return the current context or <code>null</code> when proposals are not evaluated
+	 */
+	public ICompletionContext getContext() {
+		return fContext;
+	}
+
+	protected void addProposal(final ScriptCompletionProposal proposal) {
+		fProposals.add(proposal);
+	}
+
+	protected void addProposal(final StyledString displayString, final String replacementString, final IImageResolver imageResolver, final int priority,
+			final IHelpResolver helpResolver) {
+
+		fProposals.add(new ScriptCompletionProposal(fContext, displayString, replacementString, imageResolver, priority, helpResolver));
+	}
+
+	protected void addProposal(final String displayString, final String replacementString, final IImageResolver imageResolver, final int priority,
+			final IHelpResolver helpResolver) {
+
+		fProposals.add(new ScriptCompletionProposal(fContext, displayString, replacementString, imageResolver, priority, helpResolver));
+	}
+
+	protected boolean matchesFilter(final String proposal) {
+		return matches(fContext.getFilter(), proposal);
+	}
+
+	protected boolean matchesFilterIgnoreCase(final String proposal) {
+		return matchesIgnoreCase(fContext.getFilter(), proposal);
+	}
+
+	protected boolean isMethodParameter(ICompletionContext context, Method calledMethod, int parameterIndex) {
+		return isMethodParameter(context, calledMethod.getName(), parameterIndex);
+	}
+
+	protected boolean isMethodParameter(ICompletionContext context, String calledMethod, int parameterIndex) {
+		return isMethodCall(context) && matchesMethodName(context, calledMethod) && isParameterIndex(context, parameterIndex);
+	}
+
+	protected boolean isStringParameter(ICompletionContext context) {
+		return isMethodCall(context) && context.isStringLiteral(context.getFilterToken());
+	}
+
+	private boolean matchesMethodName(ICompletionContext context, String name) {
+		final List<Object> tokens = context.getTokens();
+		final TokenList candidates = new TokenList(tokens).getFromLast("(");
+
+		if (!candidates.isEmpty())
+			return name.equals(tokens.get(tokens.size() - 1 - candidates.size()));
+
+		return false;
+	}
+
+	private boolean isMethodCall(ICompletionContext context) {
+		final TokenList candidates = new TokenList(context.getTokens()).getFromLast("(");
+
+		if (!candidates.isEmpty()) {
+			candidates.removeIfMatches(0, "(");
+			candidates.removeAny(",");
+
+			return candidates.isEmpty() || ((candidates.size() == 1) && (InputTokenizer.isTextFilter(candidates.get(0))));
+		}
+
+		return false;
+	}
+
+	private boolean isParameterIndex(ICompletionContext context, int parameterIndex) {
+		final TokenList candidates = new TokenList(context.getTokens()).getFromLast("(");
+
+		if (!candidates.isEmpty()) {
+			candidates.removeIfMatches(0, "(");
+			return parameterIndex == candidates.stream().filter(t -> ",".equals(t)).count();
+		}
+
+		return false;
+	}
+
+	protected static boolean matches(final String filter, final String proposal) {
+		return (filter != null) ? proposal.startsWith(filter) : true;
+	}
+
+	protected static boolean matchesIgnoreCase(final String filter, final String proposal) {
+		return (filter != null) ? proposal.toLowerCase().startsWith(filter.toLowerCase()) : true;
+	}
+
+	protected abstract void prepareProposals(ICompletionContext context);
+}
diff --git a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/provider/AbstractFileLocationCompletionProvider.java b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/provider/AbstractFileLocationCompletionProvider.java
index 920a6a2..6115dc1 100644
--- a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/provider/AbstractFileLocationCompletionProvider.java
+++ b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/provider/AbstractFileLocationCompletionProvider.java
@@ -10,6 +10,7 @@
  * Contributors:
  *     Christian Pontesegger - initial API and implementation
  *******************************************************************************/
+
 package org.eclipse.ease.ui.completion.provider;
 
 import java.io.File;
@@ -20,71 +21,26 @@
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IResource;
 import org.eclipse.core.resources.IWorkspaceRoot;
-import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.ease.ICompletionContext;
-import org.eclipse.ease.ICompletionContext.Type;
 import org.eclipse.ease.tools.ResourceTools;
-import org.eclipse.ease.ui.completion.AbstractCompletionProvider;
 import org.eclipse.ease.ui.completion.IImageResolver;
 import org.eclipse.ease.ui.completion.ScriptCompletionProposal;
-import org.eclipse.jface.resource.ImageDescriptor;
-import org.eclipse.jface.viewers.ILabelProvider;
 import org.eclipse.swt.graphics.Image;
-import org.eclipse.swt.widgets.Display;
 import org.eclipse.ui.ISharedImages;
 import org.eclipse.ui.PlatformUI;
 import org.eclipse.ui.model.IWorkbenchAdapter;
-import org.eclipse.ui.model.WorkbenchLabelProvider;
 
 public abstract class AbstractFileLocationCompletionProvider extends AbstractCompletionProvider {
 
-	private static class ResourceImageResolver implements IImageResolver {
-
-		private final Object fFile;
-
-		public ResourceImageResolver(Object file) {
-			fFile = file;
-		}
-
-		@Override
-		public Image getImage() {
-			if (fFile instanceof IResource) {
-				final IWorkbenchAdapter adapter = Platform.getAdapterManager().getAdapter(fFile, IWorkbenchAdapter.class);
-				return (adapter != null) ? adapter.getImageDescriptor(fFile).createImage() : null;
-
-			} else if (fFile instanceof File) {
-
-				if (isRootFile((File) fFile))
-					return PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_OBJ_FOLDER).createImage();
-
-				if (((File) fFile).isFile())
-					return PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_OBJ_FILE).createImage();
-
-				if (((File) fFile).isDirectory())
-					return PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_OBJ_FOLDER).createImage();
-
-			} else if (fFile instanceof ImageDescriptor)
-				return ((ImageDescriptor) fFile).createImage();
-
-			return null;
-		}
-	}
-
 	private static final int ORDER_URI_SCHEME = ScriptCompletionProposal.ORDER_DEFAULT;
 	private static final int ORDER_PROJECT = ScriptCompletionProposal.ORDER_DEFAULT + 1;
 	private static final int ORDER_FOLDER = ScriptCompletionProposal.ORDER_DEFAULT + 2;
 	private static final int ORDER_FILE = ScriptCompletionProposal.ORDER_DEFAULT + 3;
 
-	private final ILabelProvider fLabelProvider = new WorkbenchLabelProvider();
-
-	public AbstractFileLocationCompletionProvider() {
-		Display.getDefault().syncExec(() -> fLabelProvider.getImage(ResourcesPlugin.getWorkspace().getRoot()));
-	}
-
 	@Override
 	public boolean isActive(final ICompletionContext context) {
-		return (context.getType() == Type.STRING_LITERAL);
+		return super.isActive(context) && isStringParameter(context);
 	}
 
 	@Override
@@ -95,7 +51,7 @@
 		if ((matches(context.getFilter(), "workspace:/")) && (showCandidate("workspace://")))
 			addProposal("workspace://", "workspace://", null, ORDER_URI_SCHEME, null);
 
-		if ((matches(context.getFilter(), "project:/")) && (getContext().getResource() instanceof IResource) && (showCandidate("project://")))
+		if ((matches(context.getFilter(), "project:/")) && (showCandidate("project://")) && (getContext().getResource() instanceof IResource))
 			addProposal("project://", "project://", null, ORDER_URI_SCHEME, null);
 
 		if ((matches(context.getFilter(), "file://")) && (showCandidate("file:///")))
@@ -143,14 +99,10 @@
 			final Object parentFolder = resolver.getParentFolder();
 
 			if ((parentFolder instanceof IResource) && !(parentFolder instanceof IWorkspaceRoot)) {
-				addProposal("..", resolver.getParentString() + "../",
-						new ResourceImageResolver(PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_OBJ_FOLDER)), ORDER_FOLDER,
-						null);
+				addProposal("..", resolver.getParentString() + "../", new WorkbenchDescriptorImageResolver(ISharedImages.IMG_OBJ_FOLDER), ORDER_FOLDER, null);
 
 			} else if ((parentFolder instanceof File) && !(isRootFile((File) parentFolder)) && !(ResourceTools.VIRTUAL_WINDOWS_ROOT.equals(parentFolder))) {
-				addProposal("..", resolver.getParentString() + "/../",
-						new ResourceImageResolver(PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_OBJ_FOLDER)), ORDER_FOLDER,
-						null);
+				addProposal("..", resolver.getParentString() + "/../", new WorkbenchDescriptorImageResolver(ISharedImages.IMG_OBJ_FOLDER), ORDER_FOLDER, null);
 			}
 		}
 	}
@@ -210,4 +162,35 @@
 	protected static boolean isFolder(final Object candidate) {
 		return ((candidate instanceof File) && (((File) candidate).isDirectory())) || (candidate instanceof IContainer);
 	}
-}
\ No newline at end of file
+
+	private static final class ResourceImageResolver implements IImageResolver {
+
+		private final Object fFile;
+
+		private ResourceImageResolver(Object file) {
+			fFile = file;
+		}
+
+		@Override
+		public Image getImage() {
+			if (fFile instanceof IResource) {
+				final IWorkbenchAdapter adapter = Platform.getAdapterManager().getAdapter(fFile, IWorkbenchAdapter.class);
+				return (adapter == null) ? null : adapter.getImageDescriptor(fFile).createImage();
+
+			} else if (fFile instanceof File) {
+
+				if (isRootFile((File) fFile))
+					return PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_OBJ_FOLDER).createImage();
+
+				if (((File) fFile).isFile())
+					return PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_OBJ_FILE).createImage();
+
+				if (((File) fFile).isDirectory())
+					return PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_OBJ_FOLDER).createImage();
+
+			}
+
+			return null;
+		}
+	}
+}
diff --git a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/provider/AbstractPathCompletionProvider.java b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/provider/AbstractPathCompletionProvider.java
new file mode 100644
index 0000000..743aeb8
--- /dev/null
+++ b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/provider/AbstractPathCompletionProvider.java
@@ -0,0 +1,74 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License_Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.ui.completion.provider;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.ease.ICompletionContext;
+
+public abstract class AbstractPathCompletionProvider extends AbstractCompletionProvider {
+
+	protected <T> Collection<T> filter(Collection<T> elements, ICompletionContext context) {
+		final Collection<T> filteredElements = new ArrayList<>();
+
+		final String filter = getFilter(context);
+		for (final T element : elements) {
+			final IPath path = convertToPath(element).makeAbsolute();
+			if (path.toString().startsWith(filter)) {
+				if ((new Path(filter).segmentCount() + (filter.endsWith("/") ? 1 : 0)) == path.segmentCount())
+					filteredElements.add(element);
+			}
+		}
+
+		return filteredElements;
+	}
+
+	private String getFilter(ICompletionContext context) {
+		return (context.getFilter().startsWith("/")) ? context.getFilter() : "/" + context.getFilter();
+	}
+
+	protected Collection<IPath> getPathsFromElements(Collection<?> elements) {
+		final Collection<IPath> paths = new LinkedHashSet<>();
+
+		final List<IPath> pathsFromElements = elements.stream().map(e -> convertToPath(e)).filter(p -> p.segmentCount() > 0)
+				.map(p -> p.removeLastSegments(1).makeAbsolute()).collect(Collectors.toList());
+		for (final IPath path : pathsFromElements)
+			addPath(path, paths);
+
+		return paths;
+	}
+
+	private void addPath(IPath path, Collection<IPath> paths) {
+		if (!Path.ROOT.equals(path)) {
+			addPath(path.removeLastSegments(1), paths);
+			paths.add(path);
+		}
+	}
+
+	private IPath convertToPath(Object element) {
+		if (element instanceof IPath)
+			return (IPath) element;
+
+		return toPath(element);
+	}
+
+	protected abstract IPath toPath(Object element);
+
+}
diff --git a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/provider/EnvironmentLocationCompletionProvider.java b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/provider/EnvironmentLocationCompletionProvider.java
index 0b888ad..6aa7d70 100644
--- a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/provider/EnvironmentLocationCompletionProvider.java
+++ b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/provider/EnvironmentLocationCompletionProvider.java
@@ -14,22 +14,23 @@
 package org.eclipse.ease.ui.completion.provider;
 
 import org.eclipse.ease.ICompletionContext;
+import org.eclipse.ease.modules.EnvironmentModule;
 
 public class EnvironmentLocationCompletionProvider extends AbstractFileLocationCompletionProvider {
 
 	@Override
 	public boolean isActive(final ICompletionContext context) {
-		return super.isActive(context) && ((context.getCaller().endsWith("include")) || (context.getCaller().endsWith("loadJar")))
-				&& (context.getParameterOffset() == 0);
+		return super.isActive(context)
+				&& (isMethodParameter(context, EnvironmentModule.INCLUDE_METHOD, 0) || isMethodParameter(context, EnvironmentModule.LOAD_JAR_METHOD, 0));
 	}
 
 	@Override
 	protected boolean showCandidate(final Object candidate) {
 		if (isFile(candidate)) {
-			if (getContext().getCaller().endsWith("include"))
+			if (isMethodParameter(getContext(), EnvironmentModule.INCLUDE_METHOD, 0))
 				return hasFileExtension(candidate, getContext().getScriptType().getDefaultExtension());
 
-			else if (getContext().getCaller().endsWith("loadJar"))
+			else if (isMethodParameter(getContext(), EnvironmentModule.LOAD_JAR_METHOD, 0))
 				return hasFileExtension(candidate, "jar");
 		}
 
diff --git a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/ICompletionProvider.java b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/provider/ICompletionProvider.java
similarity index 60%
rename from plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/ICompletionProvider.java
rename to plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/provider/ICompletionProvider.java
index addcb75..8509c70 100644
--- a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/ICompletionProvider.java
+++ b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/provider/ICompletionProvider.java
@@ -12,35 +12,30 @@
  *     Christian Pontesegger - rewrite of this interface
  *******************************************************************************/
 
-package org.eclipse.ease.ui.completion;
+package org.eclipse.ease.ui.completion.provider;
 
 import java.util.Collection;
 
-import org.eclipse.core.runtime.Platform;
 import org.eclipse.ease.ICompletionContext;
-import org.eclipse.ease.ui.Activator;
+import org.eclipse.ease.ui.completion.ScriptCompletionProposal;
 
 public interface ICompletionProvider {
 
-	/** Trace enablement for code completion. */
-	boolean TRACE_CODE_COMPLETION = Activator.getDefault().isDebugging()
-			&& "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.ease.ui/debug/codeCompletion"));
-
 	/**
-	 * Calculate all matching proposals for given {@link ICompletionContext}.
+	 * Calculate all matching proposals.
 	 *
 	 * @param context
-	 *            {@link ICompletionContext} with necessary information to calculate proposals.
+	 *            with necessary information to calculate proposals.
 	 * @return Collection of matching proposals.
 	 */
-	Collection<? extends ScriptCompletionProposal> getProposals(ICompletionContext context);
+	Collection<ScriptCompletionProposal> getProposals(ICompletionContext context);
 
 	/**
 	 * Query indicating that this providers completion proposals should be taken into account.
 	 *
 	 * @param context
-	 *            {@link ICompletionContext} with necessary information to calculate proposals.
+	 *            with necessary information to calculate proposals.
 	 * @return <code>true</code> when active
 	 */
 	boolean isActive(ICompletionContext context);
-}
\ No newline at end of file
+}
diff --git a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/provider/LoadModuleCompletionProvider.java b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/provider/LoadModuleCompletionProvider.java
index caf8e34..22a532e 100644
--- a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/provider/LoadModuleCompletionProvider.java
+++ b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/provider/LoadModuleCompletionProvider.java
@@ -13,78 +13,52 @@
 package org.eclipse.ease.ui.completion.provider;
 
 import java.util.Collection;
-import java.util.HashSet;
 
 import org.eclipse.core.runtime.IPath;
-import org.eclipse.core.runtime.Path;
 import org.eclipse.ease.ICompletionContext;
-import org.eclipse.ease.ICompletionContext.Type;
+import org.eclipse.ease.modules.EnvironmentModule;
 import org.eclipse.ease.modules.ModuleDefinition;
 import org.eclipse.ease.service.IScriptService;
+import org.eclipse.ease.service.ScriptService;
 import org.eclipse.ease.ui.Activator;
-import org.eclipse.ease.ui.completion.AbstractCompletionProvider;
 import org.eclipse.jface.viewers.StyledString;
 import org.eclipse.ui.ISharedImages;
-import org.eclipse.ui.PlatformUI;
 
-public class LoadModuleCompletionProvider extends AbstractCompletionProvider {
+public class LoadModuleCompletionProvider extends AbstractPathCompletionProvider {
 
 	@Override
 	public boolean isActive(final ICompletionContext context) {
-		return (context.getType() == Type.STRING_LITERAL) && (context.getCaller().endsWith("loadModule"));
+		return (super.isActive(context)) && isMethodParameter(context, EnvironmentModule.LOAD_MODULE_METHOD, 0);
 	}
 
 	@Override
 	protected void prepareProposals(final ICompletionContext context) {
 
-		// create a path to search for
-		final IPath filterPath = new Path(context.getFilter());
-		final IPath searchPath;
-
-		if (filterPath.segmentCount() > 1)
-			searchPath = filterPath.makeAbsolute().removeLastSegments(1);
-
-		else if (filterPath.hasTrailingSeparator())
-			searchPath = filterPath.makeAbsolute();
-
-		else
-			searchPath = new Path("/");
-
-		final Collection<String> pathProposals = new HashSet<>();
-
-		final IScriptService scriptService = PlatformUI.getWorkbench().getService(IScriptService.class);
+		final IScriptService scriptService = ScriptService.getInstance();
 		final Collection<ModuleDefinition> availableModules = scriptService.getAvailableModules();
 
-		for (final ModuleDefinition definition : availableModules) {
-			final IPath modulePath = definition.getPath();
-			if (searchPath.isPrefixOf(modulePath)) {
-				// this is a valid candidate
+		final Collection<IPath> paths = filter(getPathsFromElements(availableModules), context);
 
-				if ((searchPath.segmentCount() + 1) == modulePath.segmentCount()) {
+		paths.stream().forEach(p -> {
+			addProposal(p.toString(), p.toString() + "/", new WorkbenchDescriptorImageResolver(ISharedImages.IMG_OBJ_FOLDER), 10, null);
+		});
 
-					if (matchesFilterIgnoreCase(definition.getName())) {
+		final Collection<ModuleDefinition> modules = filter(availableModules, context);
+		modules.stream().forEach(m -> {
+			final StyledString displayString = new StyledString(m.getPath().lastSegment());
+			if (!m.isVisible())
+				displayString.append(" (hidden)", StyledString.DECORATIONS_STYLER);
 
-						// add module proposal
-						final StyledString displayString = new StyledString(modulePath.lastSegment());
-						if (!definition.isVisible())
-							displayString.append(" (hidden)", StyledString.DECORATIONS_STYLER);
+			addProposal(displayString, m.getName(), new DescriptorImageResolver(Activator.getImageDescriptor(Activator.PLUGIN_ID, "/icons/eobj16/module.png")),
+					0, null);
+		});
+	}
 
-						addProposal(displayString, definition.getName(),
-								new DescriptorImageResolver(Activator.getImageDescriptor(Activator.PLUGIN_ID, "/icons/eobj16/module.png")), 0, null);
-					}
+	@Override
+	protected IPath toPath(Object element) {
+		if (element instanceof ModuleDefinition)
+			return ((ModuleDefinition) element).getPath();
 
-				} else {
-					// add path proposal; collect them first to avoid duplicates
-					pathProposals.add(modulePath.removeLastSegments(1).toString());
-				}
-			}
-		}
-
-		// add path proposals
-		for (final String pathProposal : pathProposals) {
-			if (matchesFilterIgnoreCase(pathProposal))
-				addProposal(pathProposal, pathProposal + "/",
-						new DescriptorImageResolver(PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_OBJ_FOLDER)), 10, null);
-		}
+		throw new IllegalArgumentException("element is not of type ModuleDefinition");
 	}
 }
\ No newline at end of file
diff --git a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/provider/LoadedModuleCompletionProvider.java b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/provider/LoadedModuleCompletionProvider.java
index 8ea1d6c..15cc847 100644
--- a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/provider/LoadedModuleCompletionProvider.java
+++ b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/provider/LoadedModuleCompletionProvider.java
@@ -10,17 +10,14 @@
  * Contributors:
  *     Christian Pontesegger - initial API and implementation
  *******************************************************************************/
+
 package org.eclipse.ease.ui.completion.provider;
 
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-
 import org.eclipse.ease.ICompletionContext;
-import org.eclipse.ease.ICompletionContext.Type;
 import org.eclipse.ease.modules.ModuleDefinition;
 import org.eclipse.ease.ui.Activator;
-import org.eclipse.ease.ui.completion.AbstractCompletionProvider;
 import org.eclipse.ease.ui.completion.ScriptCompletionProposal;
+import org.eclipse.ease.ui.completion.tokenizer.TokenList;
 import org.eclipse.ease.ui.modules.ui.ModulesTools;
 import org.eclipse.jface.viewers.StyledString;
 
@@ -28,42 +25,60 @@
 
 	@Override
 	public boolean isActive(final ICompletionContext context) {
-		return context.getType() == Type.NONE;
+		return super.isActive(context) && isModuleContext(context);
+	}
+
+	private boolean isModuleContext(ICompletionContext context) {
+		TokenList tokenList = new TokenList(context.getTokens());
+
+		final TokenList methodCall = tokenList.getFromLast("(");
+		if (!methodCall.isEmpty()) {
+			methodCall.remove(0);
+			while (methodCall.removeIfMatches(0, ",")) {
+				// repeat
+			}
+
+			tokenList = methodCall;
+		}
+
+		if (tokenList.isEmpty())
+			return true;
+
+		if (tokenList.size() == 1) {
+			return (tokenList.get(0) instanceof String) && (!context.isStringLiteral(tokenList.get(0).toString()));
+		}
+
+		return false;
 	}
 
 	@Override
 	protected void prepareProposals(final ICompletionContext context) {
 		for (final ModuleDefinition definition : context.getLoadedModules()) {
 			// field proposals
-			for (final Field field : definition.getFields()) {
-				if (matchesFilterIgnoreCase(field.getName())) {
-					final StyledString styledString = new StyledString(field.getName());
-					styledString.append(" : " + field.getType().getSimpleName(), StyledString.DECORATIONS_STYLER);
-					styledString.append(" - " + definition.getName(), StyledString.QUALIFIER_STYLER);
+			definition.getFields().stream().filter(f -> f.getName().startsWith(context.getFilter())).forEach(field -> {
+				final StyledString styledString = new StyledString(field.getName());
+				styledString.append(" : " + field.getType().getSimpleName(), StyledString.DECORATIONS_STYLER);
+				styledString.append(" - " + definition.getName(), StyledString.QUALIFIER_STYLER);
 
-					addProposal(styledString, field.getName(),
-							new DescriptorImageResolver(Activator.getLocalImageDescriptor("/icons/eobj16/field_public_obj.png")),
-							ScriptCompletionProposal.ORDER_FIELD, null);
-				}
-			}
+				addProposal(styledString, field.getName(), new DescriptorImageResolver(Activator.getLocalImageDescriptor("/icons/eobj16/field_public_obj.png")),
+						ScriptCompletionProposal.ORDER_FIELD, null);
+			});
 
 			// method proposals
-			for (final Method method : definition.getMethods()) {
-				if (matchesFilterIgnoreCase(method.getName())) {
-					final StyledString styledString = ModulesTools.getSignature(method, true);
-					styledString.append(" - " + definition.getName(), StyledString.QUALIFIER_STYLER);
+			definition.getMethods().stream().filter(m -> m.getName().startsWith(context.getFilter())).forEach(method -> {
+				final StyledString styledString = ModulesTools.getSignature(method, true);
+				styledString.append(" - " + definition.getName(), StyledString.QUALIFIER_STYLER);
 
-					if ((method.getParameterTypes().length - ModulesTools.getOptionalParameterCount(method)) > 0) {
-						addProposal(styledString, method.getName() + "(",
-								new DescriptorImageResolver(Activator.getLocalImageDescriptor("/icons/eobj16/field_public_obj.png")),
-								ScriptCompletionProposal.ORDER_METHOD, null);
-					} else {
-						addProposal(styledString, method.getName() + "()",
-								new DescriptorImageResolver(Activator.getLocalImageDescriptor("/icons/eobj16/field_public_obj.png")),
-								ScriptCompletionProposal.ORDER_METHOD, null);
-					}
+				if ((method.getParameterTypes().length - ModulesTools.getOptionalParameterCount(method)) > 0) {
+					addProposal(styledString, method.getName() + "(",
+							new DescriptorImageResolver(Activator.getLocalImageDescriptor("/icons/eobj16/field_public_obj.png")),
+							ScriptCompletionProposal.ORDER_METHOD, null);
+				} else {
+					addProposal(styledString, method.getName() + "()",
+							new DescriptorImageResolver(Activator.getLocalImageDescriptor("/icons/eobj16/field_public_obj.png")),
+							ScriptCompletionProposal.ORDER_METHOD, null);
 				}
-			}
+			});
 		}
 	}
 }
diff --git a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/provider/VariablesCompletionProvider.java b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/provider/VariablesCompletionProvider.java
index 82dbf30..fc41436 100644
--- a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/provider/VariablesCompletionProvider.java
+++ b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/provider/VariablesCompletionProvider.java
@@ -12,14 +12,17 @@
  *******************************************************************************/
 package org.eclipse.ease.ui.completion.provider;
 
+import java.util.Collection;
+import java.util.List;
 import java.util.Map.Entry;
+import java.util.stream.Collectors;
 
 import org.eclipse.ease.ICompletionContext;
-import org.eclipse.ease.ICompletionContext.Type;
 import org.eclipse.ease.modules.IEnvironment;
 import org.eclipse.ease.ui.Activator;
-import org.eclipse.ease.ui.completion.AbstractCompletionProvider;
 import org.eclipse.ease.ui.completion.ScriptCompletionProposal;
+import org.eclipse.ease.ui.completion.tokenizer.InputTokenizer;
+import org.eclipse.ease.ui.completion.tokenizer.TokenList;
 import org.eclipse.jface.viewers.StyledString;
 
 /**
@@ -29,25 +32,40 @@
 
 	@Override
 	public boolean isActive(final ICompletionContext context) {
-		return super.isActive(context) && (context.getScriptEngine() != null) && (context.getType() == Type.NONE);
+		return super.isActive(context) && (context.getScriptEngine() != null) && ((context.getTokens().size() <= 1) || (isParameter(context)));
+	}
+
+	private boolean isParameter(ICompletionContext context) {
+		final TokenList candidates = new TokenList(context.getTokens()).getFromLast("(");
+
+		if (!candidates.isEmpty()) {
+			candidates.removeIfMatches(0, "(");
+			candidates.removeAny(",");
+
+			return candidates.isEmpty() || ((candidates.size() == 1) && (InputTokenizer.isTextFilter(candidates.get(0))));
+		}
+
+		return false;
 	}
 
 	@Override
 	protected void prepareProposals(final ICompletionContext context) {
-		for (final Entry<String, Object> variable : context.getScriptEngine().getVariables().entrySet()) {
-			// ignore mapped modules
-			if (!variable.getKey().startsWith(IEnvironment.MODULE_PREFIX)) {
-				if (matchesFilterIgnoreCase(variable.getKey())) {
-					final String type = (variable.getValue() != null) ? variable.getValue().getClass().getSimpleName() : "null";
-					final StyledString styledString = new StyledString(variable.getKey());
-					styledString.append(" : " + type, StyledString.DECORATIONS_STYLER);
-					styledString.append(" - " + "Variable", StyledString.QUALIFIER_STYLER);
+		final Collection<Entry<String, Object>> elements = context.getScriptEngine().getVariables().entrySet();
 
-					addProposal(styledString, variable.getKey(),
-							new DescriptorImageResolver(Activator.getLocalImageDescriptor("/icons/eobj16/debug_local_variable.png")),
-							ScriptCompletionProposal.ORDER_FIELD, null);
-				}
-			}
+		final List<Entry<String, Object>> filteredEntries = elements.stream().filter(e -> !e.getKey().startsWith(IEnvironment.MODULE_PREFIX))
+				.filter(e -> matchesFilterIgnoreCase(e.getKey())).collect(Collectors.toList());
+
+		for (final Entry<String, Object> entry : filteredEntries) {
+			final StyledString styledString = new StyledString(entry.getKey());
+			styledString.append(String.format(" : %s", getClassName(entry.getValue())), StyledString.DECORATIONS_STYLER);
+			styledString.append(" - Variable", StyledString.QUALIFIER_STYLER);
+
+			addProposal(styledString, entry.getKey(), new DescriptorImageResolver(Activator.getLocalImageDescriptor("/icons/eobj16/debug_local_variable.png")),
+					ScriptCompletionProposal.ORDER_FIELD, null);
 		}
 	}
+
+	private String getClassName(Object entry) {
+		return (entry == null) ? "null" : entry.getClass().getSimpleName();
+	}
 }
diff --git a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/tokenizer/Bracket.java b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/tokenizer/Bracket.java
new file mode 100644
index 0000000..9f06f98
--- /dev/null
+++ b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/tokenizer/Bracket.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License_Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.ui.completion.tokenizer;
+
+import java.util.Objects;
+
+public class Bracket {
+	private final int fStart;
+	private int fEnd;
+
+	public Bracket(final int start, final int end) {
+		fStart = start;
+		fEnd = end;
+	}
+
+	public int getStart() {
+		return fStart;
+	}
+
+	public int getEnd() {
+		return fEnd;
+	}
+
+	public void setEnd(int end) {
+		fEnd = end;
+	}
+
+	@Override
+	public int hashCode() {
+		return Objects.hash(fEnd, fStart);
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		final Bracket other = (Bracket) obj;
+		return (fEnd == other.fEnd) && (fStart == other.fStart);
+	}
+
+	@Override
+	public String toString() {
+		return String.format("start: %d, end: %d", getStart(), getEnd());
+	}
+}
diff --git a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/tokenizer/BracketMatcher.java b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/tokenizer/BracketMatcher.java
new file mode 100644
index 0000000..1a58edd
--- /dev/null
+++ b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/tokenizer/BracketMatcher.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License_Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.ui.completion.tokenizer;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class BracketMatcher {
+
+	private static final char OPEN = '(';
+	private static final char CLOSE = ')';
+
+	private final List<Bracket> fBrackets;
+
+	public BracketMatcher(String input) {
+		fBrackets = new ArrayList<>();
+
+		for (int pos = 0; pos < input.length(); pos++) {
+			final char c = input.charAt(pos);
+			if (c == OPEN) {
+				fBrackets.add(0, new Bracket(pos, -1));
+
+			} else if (c == CLOSE) {
+				for (final Bracket bracket : fBrackets) {
+					if (bracket.getEnd() == -1) {
+						bracket.setEnd(pos);
+						break;
+					}
+				}
+			}
+		}
+	}
+
+	public List<Bracket> getBrackets() {
+		return fBrackets;
+	}
+
+	public boolean hasOpenBrackets() {
+		return getBrackets().stream().anyMatch(b -> b.getEnd() == -1);
+	}
+
+	public List<Bracket> getOpenBrackets() {
+		return getBrackets().stream().filter(b -> b.getEnd() == -1).collect(Collectors.toList());
+	}
+}
diff --git a/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/modules/ModulesToolsTest.java b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/tokenizer/IVariablesResolver.java
similarity index 78%
copy from tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/modules/ModulesToolsTest.java
copy to plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/tokenizer/IVariablesResolver.java
index d20563c..2aa0315 100644
--- a/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/modules/ModulesToolsTest.java
+++ b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/tokenizer/IVariablesResolver.java
@@ -11,8 +11,10 @@
  *     Christian Pontesegger - initial API and implementation
  *******************************************************************************/
 
-package org.eclipse.ease.ui.modules;
+package org.eclipse.ease.ui.completion.tokenizer;
 
-public class ModulesToolsTest {
+@FunctionalInterface
+public interface IVariablesResolver {
 
+	Class<?> resolveClass(String variableName);
 }
diff --git a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/tokenizer/InputTokenizer.java b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/tokenizer/InputTokenizer.java
new file mode 100644
index 0000000..2687793
--- /dev/null
+++ b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/tokenizer/InputTokenizer.java
@@ -0,0 +1,311 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License_Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.ui.completion.tokenizer;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class InputTokenizer {
+
+	private static final Pattern PACKAGE_PATTERN = Pattern.compile("(java|com|org)\\.([\\p{Lower}\\d]+\\.?)*");
+	private static final Pattern CLASS_PATTERN = Pattern.compile("(java|com|org)\\.([\\p{Lower}\\d]+\\.?)\\.\\p{Upper}(\\w)*");
+	private static final Pattern VARIABLES_PATTERN = Pattern.compile("\\p{Alpha}\\w*");
+
+	private static final char[] DELIMITERS = { '.', '(', ',' };
+
+	public static boolean isDelimiter(Object element) {
+		for (final char c : DELIMITERS) {
+			if (new String(new char[] { c }).equals(element))
+				return true;
+		}
+
+		return "()".equals(element) || ")".equals(element);
+	}
+
+	public static boolean isTextFilter(Object element) {
+		return (element instanceof String) && (!isDelimiter(element));
+	}
+
+	private final IVariablesResolver fVariablesResolver;
+
+	public InputTokenizer() {
+		this(v -> null);
+	}
+
+	public InputTokenizer(IVariablesResolver variablesResolver) {
+		fVariablesResolver = variablesResolver;
+	}
+
+	public List<Object> getTokens(String input) {
+		return getTokensFromSimplifiedInput(getSimplifiedInput(input));
+	}
+
+	private List<Object> getTokensFromSimplifiedInput(String simpleInput) {
+		final List<Object> simpleToken = getSimpleToken(simpleInput);
+		if (simpleToken != null)
+			return simpleToken;
+
+		final int delimiterPosition = findLastDelimiter(simpleInput);
+		if (delimiterPosition > 0)
+			return divideAndConquerTokens(simpleInput, delimiterPosition);
+		else
+			return Arrays.asList(simpleInput);
+	}
+
+	private List<Object> divideAndConquerTokens(String simpleInput, final int delimiterPosition) {
+		final List<Object> tokens = new ArrayList<>();
+		final String beforeDelimiter = simpleInput.substring(0, delimiterPosition);
+		final String delimiterAndRest = simpleInput.substring(delimiterPosition).trim();
+
+		tokens.addAll(getTokensFromSimplifiedInput(beforeDelimiter));
+
+		final Class<?> lastClass = getTrailingClassToken(tokens);
+		final Method method = (lastClass != null) ? detectMethod(lastClass, delimiterAndRest.substring(1)) : null;
+
+		if (method != null)
+			tokens.add(method);
+
+		else if ("()".equals(delimiterAndRest))
+			tokens.add(delimiterAndRest);
+
+		else if (delimiterAndRest.startsWith("(") && (delimiterAndRest.length() > 1)) {
+			tokens.add("(");
+			tokens.add(delimiterAndRest.substring(1).trim());
+
+		} else if (delimiterAndRest.startsWith(".") && (delimiterAndRest.length() > 1)) {
+			tokens.add(".");
+			tokens.add(delimiterAndRest.substring(1).trim());
+
+		} else if (delimiterAndRest.startsWith(",") && (delimiterAndRest.length() > 1)) {
+			tokens.add(",");
+			tokens.add(delimiterAndRest.substring(1).trim());
+
+		} else
+			tokens.add(delimiterAndRest);
+
+		return tokens;
+	}
+
+	private Method detectMethod(Class<?> clazz, String methodName) {
+		return Arrays.asList(clazz.getMethods()).stream().filter(m -> methodName.equals(m.getName())).findFirst().orElse(null);
+	}
+
+	private Class<?> getTrailingClassToken(List<Object> tokens) {
+		Object checkToken = null;
+		if ((tokens.size() >= 2) && ("()".equals(tokens.get(tokens.size() - 1)))) {
+			checkToken = tokens.get(tokens.size() - 2);
+
+			if (checkToken instanceof Method)
+				return ((Method) checkToken).getReturnType();
+
+		} else if (!tokens.isEmpty()) {
+			checkToken = tokens.get(tokens.size() - 1);
+		}
+
+		if (checkToken instanceof Class<?>)
+			return (Class<?>) checkToken;
+
+		return null;
+	}
+
+	private int findLastDelimiter(String input) {
+		int position = -1;
+		for (final char delimiter : DELIMITERS)
+			position = Math.max(position, input.lastIndexOf(delimiter));
+
+		return position;
+	}
+
+	private List<Object> getSimpleToken(String input) {
+		if (input.isEmpty())
+			return Collections.emptyList();
+
+		final Matcher variablesMatcher = VARIABLES_PATTERN.matcher(input);
+		if (variablesMatcher.matches()) {
+			final Class<?> candidate = fVariablesResolver.resolveClass(input);
+			if ((candidate != null) && (!isBlacklisted(candidate)))
+				return Arrays.asList(candidate, "()");
+		}
+
+		final Package packageInstance = getPackage(input);
+		if (packageInstance != null)
+			return Arrays.asList(packageInstance);
+
+		final Class<?> clazz = getClass(input);
+		if (clazz != null)
+			return Arrays.asList(clazz);
+
+		return null;
+	}
+
+	private boolean isBlacklisted(Class<?> candidate) {
+		return candidate.getName().startsWith("org.mozilla.javascript");
+	}
+
+	protected Package getPackage(String input) {
+		final Matcher packageMatcher = PACKAGE_PATTERN.matcher(input);
+		if (packageMatcher.matches()) {
+			return Package.getPackage(input);
+		}
+
+		return null;
+	}
+
+	protected Class<?> getClass(String input) {
+		final Matcher classMatcher = CLASS_PATTERN.matcher(input);
+		if (classMatcher.matches()) {
+			try {
+				return getClass().getClassLoader().loadClass(input);
+			} catch (final ClassNotFoundException e) {
+				// class does not exist
+			}
+		}
+
+		return null;
+	}
+
+	private String getSimplifiedInput(String input) {
+		String simplifiedInput = input.trim();
+		simplifiedInput = simplifyLiterals(simplifiedInput);
+		final String trailingLiteral = getTrailingLiteral(simplifiedInput);
+		simplifiedInput = simplifiedInput.substring(0, simplifiedInput.length() - trailingLiteral.length());
+
+		simplifiedInput = simplifyBrackets(simplifiedInput);
+		simplifiedInput = simplifyParameters(simplifiedInput);
+		simplifiedInput = clipIrrelevantStuff(simplifiedInput);
+
+		return simplifiedInput + trailingLiteral;
+	}
+
+	private String getTrailingLiteral(String input) {
+		final int literalStart = input.indexOf('"');
+
+		return (literalStart >= 0) ? input.substring(literalStart) : "";
+	}
+
+	/**
+	 * Remove unused tokens. Removes stuff left of an assignment or left of whitespace.
+	 *
+	 * @param input
+	 *            text to simplify
+	 * @return simplified string, 'foo = new File' ... 'File'
+	 */
+	private String clipIrrelevantStuff(String input) {
+		String simplifiedInput = input;
+
+		final int locationOfEquals = simplifiedInput.lastIndexOf('=');
+		if (locationOfEquals >= 0)
+			simplifiedInput = simplifiedInput.substring(locationOfEquals + 1).trim();
+
+		final int locationOfSpace = simplifiedInput.lastIndexOf(' ');
+		if (locationOfSpace >= 0)
+			simplifiedInput = simplifiedInput.substring(locationOfSpace + 1).trim();
+
+		final int locationOfTab = simplifiedInput.lastIndexOf('\t');
+		if (locationOfTab >= 0)
+			simplifiedInput = simplifiedInput.substring(locationOfTab + 1).trim();
+
+		return simplifiedInput;
+	}
+
+	/**
+	 * Remove parameters in an open bracket when they are not relevant.
+	 *
+	 * @param input
+	 *            text to simplify
+	 * @return simplified string, 'foo(42, bar(), another' ... 'foo(,,another'
+	 */
+	private String simplifyParameters(String input) {
+		final BracketMatcher bracketMatcher = new BracketMatcher(input);
+		if (bracketMatcher.hasOpenBrackets()) {
+			final Bracket openBracket = bracketMatcher.getOpenBrackets().get(0);
+			final int lastCommaPosition = input.lastIndexOf(',');
+
+			if (lastCommaPosition > openBracket.getStart()) {
+				final StringBuilder simplifiedText = new StringBuilder(input.substring(0, openBracket.getStart() + 1));
+				simplifiedText.append(getCommas(input.substring(openBracket.getStart())));
+				simplifiedText.append(input.substring(lastCommaPosition + 1).trim());
+				return simplifiedText.toString();
+			}
+		}
+
+		return input;
+	}
+
+	private String getCommas(String input) {
+		final int amountOfNeededCommas = (int) input.chars().filter(c -> c == ',').count();
+		return ",".repeat(amountOfNeededCommas);
+	}
+
+	/**
+	 * Remove contents within brackets.
+	 *
+	 * @param input
+	 *            text to simplify
+	 * @return simplified string, 'foo(42, 12, bar())' ... 'foo()'
+	 */
+	private String simplifyBrackets(String input) {
+		final StringBuilder simplifiedText = new StringBuilder(input);
+
+		while (true) {
+			final BracketMatcher bracketMatcher = new BracketMatcher(simplifiedText.toString());
+			final Optional<Bracket> bracket = bracketMatcher.getBrackets().stream().filter(b -> b.getStart() < (b.getEnd() - 1)).findFirst();
+			if (bracket.isPresent()) {
+				simplifiedText.delete(bracket.get().getStart() + 1, bracket.get().getEnd());
+			} else
+				break;
+		}
+
+		return simplifiedText.toString();
+	}
+
+	/**
+	 * Remove content within String literals.
+	 *
+	 * @param input
+	 *            text to simplify
+	 * @return simplified string
+	 */
+	private String simplifyLiterals(String input) {
+		final StringBuilder simplified = new StringBuilder(input);
+
+		int startIndex;
+		int endIndex = 0;
+		do {
+			startIndex = simplified.indexOf("\"");
+			if (startIndex >= 0) {
+				endIndex = simplified.indexOf("\"", startIndex + 1);
+
+				while ((endIndex > startIndex) && (simplified.charAt(endIndex - 1) == '\\')) {
+					endIndex = simplified.indexOf("\"", endIndex + 1);
+				}
+
+				if (endIndex > startIndex)
+					simplified.delete(startIndex, endIndex + 1);
+			}
+		} while ((startIndex >= 0) && (endIndex > startIndex));
+
+		return simplified.toString();
+	}
+
+	protected boolean isLiteral(final char candidate) {
+		return ('"' == candidate);
+	}
+}
diff --git a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/tokenizer/TokenList.java b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/tokenizer/TokenList.java
new file mode 100644
index 0000000..df8d128
--- /dev/null
+++ b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/completion/tokenizer/TokenList.java
@@ -0,0 +1,78 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License_Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.ui.completion.tokenizer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class TokenList extends ArrayList<Object> {
+
+	private static final long serialVersionUID = -6713581488474706366L;
+
+	public TokenList(List<Object> baseList) {
+		super(baseList);
+	}
+
+	public Object getLastToken() {
+		return getFromTail(0);
+	}
+
+	public Object getFromTail(int index) {
+		if (size() > index)
+			return get(size() - index - 1);
+
+		return null;
+	}
+
+	public TokenList getFromLast(Class<?> clazz) {
+		int startIndex = size();
+		for (int index = 0; index < size(); index++) {
+			if (clazz.isAssignableFrom(get(index).getClass()))
+				startIndex = index;
+		}
+
+		return new TokenList(subList(startIndex, size()));
+	}
+
+	public TokenList getFromLast(String needle) {
+		int startIndex = size();
+		for (int index = 0; index < size(); index++) {
+			if (needle.equals(get(index)))
+				startIndex = index;
+		}
+
+		return new TokenList(subList(startIndex, size()));
+	}
+
+	public <T> T getLast(Class<T> clazz) {
+		final TokenList remainingTokens = getFromLast(clazz);
+		return remainingTokens.isEmpty() ? null : (T) getFromLast(clazz).get(0);
+	}
+
+	public boolean removeIfMatches(int index, String expected) {
+		if ((size() > index) && (expected.equals(get(index)))) {
+			remove(index);
+			return true;
+		}
+
+		return false;
+	}
+
+	public void removeAny(String needle) {
+		for (final Object element : new ArrayList<>(this)) {
+			if (needle.equals(element))
+				remove(element);
+		}
+	}
+}
diff --git a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/modules/ui/ModulesTools.java b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/modules/ui/ModulesTools.java
index a23a4d8..8bbe3f4 100644
--- a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/modules/ui/ModulesTools.java
+++ b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/modules/ui/ModulesTools.java
@@ -27,7 +27,6 @@
 import org.eclipse.jface.resource.JFaceResources;
 import org.eclipse.jface.viewers.StyledString;
 import org.eclipse.jface.viewers.StyledString.Styler;
-import org.eclipse.swt.graphics.Font;
 import org.eclipse.swt.graphics.TextStyle;
 import org.eclipse.ui.PlatformUI;
 
@@ -65,12 +64,10 @@
 		}
 	}
 
-	private static Styler OPTIONAL_PARAMETER_STYLE = new Styler() {
-		private final Font italic = JFaceResources.getFontRegistry().getItalic(JFaceResources.DEFAULT_FONT);
-
+	private static final Styler OPTIONAL_PARAMETER_STYLE = new Styler() {
 		@Override
 		public void applyStyles(TextStyle textStyle) {
-			textStyle.font = italic;
+			textStyle.font = JFaceResources.getFontRegistry().getItalic(JFaceResources.DEFAULT_FONT);
 			textStyle.foreground = JFaceResources.getColorRegistry().get("QUALIFIER_COLOR");
 		}
 	};
diff --git a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/view/ScriptShell.java b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/view/ScriptShell.java
index 6d03225..a699db7 100644
--- a/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/view/ScriptShell.java
+++ b/plugins/org.eclipse.ease.ui/src/org/eclipse/ease/ui/view/ScriptShell.java
@@ -14,10 +14,11 @@
 package org.eclipse.ease.ui.view;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import org.eclipse.core.commands.Command;
 import org.eclipse.core.commands.State;
@@ -40,12 +41,12 @@
 import org.eclipse.ease.service.ScriptService;
 import org.eclipse.ease.service.ScriptType;
 import org.eclipse.ease.ui.Activator;
-import org.eclipse.ease.ui.completion.AbstractCompletionProvider.DescriptorImageResolver;
 import org.eclipse.ease.ui.completion.CodeCompletionAggregator;
 import org.eclipse.ease.ui.completion.CompletionLabelProvider;
-import org.eclipse.ease.ui.completion.ICompletionProvider;
 import org.eclipse.ease.ui.completion.IImageResolver;
 import org.eclipse.ease.ui.completion.ScriptCompletionProposal;
+import org.eclipse.ease.ui.completion.provider.AbstractCompletionProvider.DescriptorImageResolver;
+import org.eclipse.ease.ui.completion.provider.ICompletionProvider;
 import org.eclipse.ease.ui.console.ScriptConsole;
 import org.eclipse.ease.ui.dnd.ShellDropTarget;
 import org.eclipse.ease.ui.handler.ToggleDropinsSection;
@@ -139,7 +140,7 @@
 
 	private AutoFocus fAutoFocusListener = null;
 
-	private final CodeCompletionAggregator fCompletionDispatcher = new CodeCompletionAggregator();
+	private CodeCompletionAggregator fCompletionDispatcher = null;
 
 	private Collection<IShellDropin> fDropins = Collections.emptySet();
 
@@ -239,8 +240,6 @@
 		}
 		fHistory = fInputCombo.getItems().clone();
 
-		addAutoCompletion();
-
 		// clear reference as we are done with initialization
 		fInitMemento = null;
 
@@ -319,26 +318,15 @@
 			}
 
 			@Override
-			public Collection<? extends ScriptCompletionProposal> getProposals(ICompletionContext context) {
-				final Collection<ScriptCompletionProposal> proposals = new HashSet<>();
-
-				for (final String history : fHistory) {
-					if (history.startsWith(context.getOriginalCode())) {
-						proposals.add(new ScriptCompletionProposal(context, history, history, fImageResolver, ScriptCompletionProposal.ORDER_HISTORY, null) {
-							@Override
-							public String getContent() {
-								return getReplacementString();
-							}
-						});
-					}
-				}
-
-				return proposals;
+			public Collection<ScriptCompletionProposal> getProposals(ICompletionContext context) {
+				return Arrays.asList(fHistory).stream().filter(t -> t.startsWith(context.getText()))
+						.map(t -> new ScriptCompletionProposal(context, t, t, fImageResolver, ScriptCompletionProposal.ORDER_HISTORY, null))
+						.collect(Collectors.toList());
 			}
 		});
 
 		final ContentProposalModifier contentAssistAdapter = new ContentProposalModifier(fInputCombo, new ComboContentAdapter(), fCompletionDispatcher,
-				KeyStroke.getInstance(SWT.CTRL, ' '), fCompletionDispatcher.getActivationChars());
+				KeyStroke.getInstance(SWT.CTRL, ' '), new char[] { '.' });
 
 		contentAssistAdapter.setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE);
 		contentAssistAdapter.setLabelProvider(new CompletionLabelProvider());
@@ -539,7 +527,8 @@
 				dropin.setScriptEngine(fScriptEngine);
 
 			// set script engine
-			fCompletionDispatcher.setScriptEngine(fScriptEngine);
+			fCompletionDispatcher = new CodeCompletionAggregator(fScriptEngine);
+			addAutoCompletion();
 		}
 	}
 
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/AbstractCodeParser.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/AbstractCodeParser.java
index 6a65852..fbaa273 100644
--- a/plugins/org.eclipse.ease/src/org/eclipse/ease/AbstractCodeParser.java
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/AbstractCodeParser.java
@@ -142,12 +142,6 @@
 	}
 
 	@Override
-	public ICompletionContext getContext(final IScriptEngine scriptEngine, final Object resource, final String contents, final int position,
-			final int selectionRange) {
-		return null;
-	}
-
-	@Override
 	public SignatureInfo getSignatureInfo(final InputStream stream) throws ScriptSignatureException {
 		final BufferedReader bReader = new BufferedReader(new InputStreamReader(stream));
 		try {
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/ICompletionContext.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/ICompletionContext.java
index 3e12710..f512fcd 100644
--- a/plugins/org.eclipse.ease/src/org/eclipse/ease/ICompletionContext.java
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/ICompletionContext.java
@@ -13,89 +13,47 @@
 
 package org.eclipse.ease;
 
-import java.util.Collection;
-import java.util.Map;
+import java.util.List;
 
 import org.eclipse.ease.modules.ModuleDefinition;
 import org.eclipse.ease.service.ScriptType;
 
-/**
- * Interface for completion context. This context helps ICompletionProvider to simplify completion proposal calculation. Stores information about given input,
- * filter for part of interest, and Source stack for part of interest.
- *
- * @author Martin Kloesch
- */
 public interface ICompletionContext {
 
-	enum Type {
-		UNKNOWN, NONE, STATIC_CLASS, CLASS_INSTANCE, PACKAGE, STRING_LITERAL
-	};
+	List<Object> getTokens();
 
-	String getOriginalCode();
+	String getText();
 
-	String getProcessedCode();
+	int getReplaceOffset();
 
-	String getFilter();
-
-	Class<? extends Object> getReferredClazz();
+	int getReplaceLength();
 
 	/**
-	 * Get the base resource of the context. Typically holds a reference to the file open in an editor
-	 *
-	 * @return base resource or <code>null</code>
-	 */
-	Object getResource();
-
-	/**
-	 * Get the running script engine. Only works for live engines like a shell.
+	 * Get active script engine.
 	 *
 	 * @return script engine or <code>null</code>
 	 */
 	IScriptEngine getScriptEngine();
 
-	ScriptType getScriptType();
-
 	/**
-	 * Get a list of loaded modules.
+	 * Get all loaded modules.
 	 *
 	 * @return loaded modules
 	 */
-	Collection<ModuleDefinition> getLoadedModules();
+	List<ModuleDefinition> getLoadedModules();
 
 	/**
-	 * Get a list of included resource. Returns a map of resource objects -&gt; resource content.
+	 * Get a text filter to be applied for the current input. This is the prefix of the expected completion proposals.
 	 *
-	 * @return map of included resources
+	 * @return filter text or empty string
 	 */
-	Map<Object, String> getIncludedResources();
+	String getFilter();
 
-	public Type getType();
+	String getFilterToken();
 
-	int getOffset();
+	boolean isStringLiteral(String input);
 
-	int getSelectionRange();
+	ScriptType getScriptType();
 
-	/**
-	 * Returns the package for PACKAGE types.
-	 *
-	 * @return package name
-	 */
-	String getPackage();
-
-	/**
-	 * Get the caller method for string literals. On STRING_LITERAL types this value denotes the calling method. The whole context of the caller is passed as a
-	 * value. Eg. "new java.lang.String". May not return <code>null</code>.
-	 *
-	 * @return calling method or empty string
-	 */
-	String getCaller();
-
-	/**
-	 * Get the index of the parameter for string literals. On STRING_LITERAL types this value indicates which parameter we are looking at: 0 for the first, 1
-	 * for the second, ...
-	 *
-	 * @return parameter offset for string literals
-	 */
-	int getParameterOffset();
-
+	Object getResource();
 }
\ No newline at end of file
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/modules/EnvironmentModule.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/modules/EnvironmentModule.java
index 2ae5a33..d0013d8 100644
--- a/plugins/org.eclipse.ease/src/org/eclipse/ease/modules/EnvironmentModule.java
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/modules/EnvironmentModule.java
@@ -57,6 +57,15 @@
 
 	public static final String MODULE_NAME = "/System/Environment";
 
+	/** Used by code completion. Keep in sync with method name in this class. */
+	public static final String INCLUDE_METHOD = "include";
+
+	/** Used by code completion. Keep in sync with method name in this class. */
+	public static final String LOAD_JAR_METHOD = "loadJar";
+
+	/** Used by code completion. Keep in sync with method name in this class. */
+	public static final String LOAD_MODULE_METHOD = "loadModule";
+
 	private static final Pattern VALID_TOPICS_PATTERN = Pattern.compile("[\\w ]+(?:\\(\\))?");
 
 	public static void bootstrap() throws ExecutionException {
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/modules/ModuleDefinition.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/modules/ModuleDefinition.java
index 26eb337..925fe99 100644
--- a/plugins/org.eclipse.ease/src/org/eclipse/ease/modules/ModuleDefinition.java
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/modules/ModuleDefinition.java
@@ -185,7 +185,7 @@
 	}
 
 	/**
-	 * Sets visibility status of module in preferences
+	 * Sets visibility status of module in preferences.
 	 *
 	 * @param visible
 	 *            <code>true</code> to make visible
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/service/ScriptService.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/service/ScriptService.java
index 29301ee..d2a6419 100644
--- a/plugins/org.eclipse.ease/src/org/eclipse/ease/service/ScriptService.java
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/service/ScriptService.java
@@ -315,7 +315,8 @@
 	@Override
 	public ModuleDefinition getModuleDefinition(final String moduleId) {
 		for (final ModuleDefinition definition : getAvailableModules()) {
-			if (definition.getId().equals(moduleId))
+
+			if ((definition.getId().equals(moduleId)) || (definition.getPath().toString().equals(moduleId)))
 				return definition;
 		}
 
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/tools/PlatformExtension.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/tools/PlatformExtension.java
new file mode 100644
index 0000000..691c8cb
--- /dev/null
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/tools/PlatformExtension.java
@@ -0,0 +1,60 @@
+/*******************************************************************************

+ * Copyright (c) 2021 Christian Pontesegger and others.

+ * All rights reserved. This program and the accompanying materials

+ * are made available under the terms of the Eclipse Public License v2.0

+ * which accompanies this distribution, and is available at

+ * https://www.eclipse.org/legal/epl-2.0/

+ *

+ * SPDX-License_Identifier: EPL-2.0

+ *

+ * Contributors:

+ *     Christian Pontesegger - initial API and implementation

+ *******************************************************************************/

+

+package org.eclipse.ease.tools;

+

+import java.util.Arrays;

+import java.util.Collection;

+import java.util.Objects;

+import java.util.stream.Collectors;

+

+import org.eclipse.core.runtime.CoreException;

+import org.eclipse.core.runtime.IConfigurationElement;

+import org.eclipse.core.runtime.Platform;

+

+public class PlatformExtension {

+

+	public static Collection<PlatformExtension> createFor(String extensionPoint) {

+		final IConfigurationElement[] config = Platform.getExtensionRegistry().getConfigurationElementsFor(extensionPoint);

+

+		return Arrays.asList(config).stream().map(e -> new PlatformExtension(e)).collect(Collectors.toSet());

+	}

+

+	public static Collection<PlatformExtension> createForName(String extensionPoint, String name) {

+		final Collection<PlatformExtension> extensions = createFor(extensionPoint);

+

+		return extensions.stream().filter(e -> Objects.equals(name, e.getConfigurationElement().getName())).collect(Collectors.toSet());

+	}

+

+	private final IConfigurationElement fConfigurationElement;

+

+	public PlatformExtension(IConfigurationElement configurationElement) {

+		fConfigurationElement = configurationElement;

+	}

+

+	public <T> T createInstance(String name, Class<T> clazz) throws CoreException {

+		final Object instance = getConfigurationElement().createExecutableExtension(name);

+		if (clazz.isAssignableFrom(instance.getClass()))

+			return (T) instance;

+

+		throw new ClassCastException(String.format("Could not cast class '%s' to '%s'", instance.getClass().getName(), clazz.getName()));

+	}

+

+	public IConfigurationElement getConfigurationElement() {

+		return fConfigurationElement;

+	}

+

+	public String getAttribute(String name) {

+		return getConfigurationElement().getAttribute(name);

+	}

+}

diff --git a/releng/org.eclipse.ease.releng.coverage/pom.xml b/releng/org.eclipse.ease.releng.coverage/pom.xml
index 08ba9cf..81fc87b 100644
--- a/releng/org.eclipse.ease.releng.coverage/pom.xml
+++ b/releng/org.eclipse.ease.releng.coverage/pom.xml
@@ -226,6 +226,12 @@
 			<version>0.9.0-SNAPSHOT</version>

 			<scope>test</scope>

 		</dependency>

+		<dependency>
+			<groupId>org.eclipse.ease</groupId>

+			<artifactId>org.eclipse.ease.ui.completions.java.test</artifactId>

+			<version>0.9.0-SNAPSHOT</version>

+			<scope>test</scope>
+		</dependency>
 		<!-- test insertion point -->

 	</dependencies>

 </project>

diff --git a/releng/org.eclipse.ease.releng/pmd/pmd_rules_unittest.xml b/releng/org.eclipse.ease.releng/pmd/pmd_rules_unittest.xml
index 8a63d7e..c3e829a 100644
--- a/releng/org.eclipse.ease.releng/pmd/pmd_rules_unittest.xml
+++ b/releng/org.eclipse.ease.releng/pmd/pmd_rules_unittest.xml
@@ -24,6 +24,9 @@
 

 		<!-- More than 1 assert allowed in tests -->

 		<exclude name="JUnitTestContainsTooManyAsserts" />

+

+		<!-- JUnit 5 tests should be package private -->

+		<exclude name="JUnit5TestShouldBePackagePrivate" />

 	</rule>

 

 	<rule ref="category/java/codestyle.xml">

diff --git a/releng/org.eclipse.ease.releng/pom.xml b/releng/org.eclipse.ease.releng/pom.xml
index baa299e..f69b4ce 100644
--- a/releng/org.eclipse.ease.releng/pom.xml
+++ b/releng/org.eclipse.ease.releng/pom.xml
@@ -198,6 +198,7 @@
 		<module>../../tests/org.eclipse.ease.ui.scripts.test</module>
 		<module>../../tests/org.eclipse.ease.ui.test</module>
 
+		<module>../../tests/org.eclipse.ease.ui.completions.java.test</module>
 		<!-- test insertion point -->
 		
 	</modules>
diff --git a/releng/org.eclipse.ease.releng/scripts/Setup.js b/releng/org.eclipse.ease.releng/scripts/Setup.js
index 2a44db7..499e73d 100644
--- a/releng/org.eclipse.ease.releng/scripts/Setup.js
+++ b/releng/org.eclipse.ease.releng/scripts/Setup.js
@@ -83,7 +83,7 @@
 	this.internalCreatePlugin(pluginId, pluginName, "workspace://${releng.project}/templates/plugin-test");

 	

 	// add to master pom

-	this.replaceInFile("workspace://${releng.project}/pom.xml", "<!-- test insertion point -->", "<module>../../tests/${bundle.id}</module>\n\t\t\t\t<!-- test insertion point -->");

+	this.replaceInFile("workspace://${releng.project}/pom.xml", "<!-- test insertion point -->", "<module>../../tests/${bundle.id}</module>\n\t\t<!-- test insertion point -->");

 	this.replaceInFile("workspace://${releng.project}.coverage/pom.xml", "<!-- test insertion point -->", "<dependency>\n\t\t\t<groupId>${project.root.id}</groupId>\n\t\t\t<artifactId>${bundle.id}</artifactId>\n\t\t\t<version>${bundle.version}-SNAPSHOT</version>\n\t\t\t<scope>test</scope>\n\t\t</dependency>\n\t\t<!-- test insertion point -->");

 }

 

@@ -146,14 +146,14 @@
 Setup.prototype.replaceInFile = function(file, search, replacement) {

 	var resolvedFile = this.replaceText(file)

 	var content = readFile(resolvedFile);

-	var handle = writeFile(resolvedFile, content.replace(search, replacement));

+	var handle = writeFile(resolvedFile, content.replace(search, this.replaceText(replacement)));

 	closeFile(handle);

 }

 

 Setup.prototype.appendToFile = function(file, text) {

 	var resolvedFile = this.replaceText(file)

 	var content = readFile(resolvedFile);

-	var handle = writeFile(resolvedFile, content + text);

+	var handle = writeFile(resolvedFile, content + this.replaceText(text));

 	closeFile(handle);

 }

 

diff --git a/releng/org.eclipse.ease.releng/spotbugs/spotbugs_filter_unittest.xml b/releng/org.eclipse.ease.releng/spotbugs/spotbugs_filter_unittest.xml
index b230cbb..8f19bb7 100644
--- a/releng/org.eclipse.ease.releng/spotbugs/spotbugs_filter_unittest.xml
+++ b/releng/org.eclipse.ease.releng/spotbugs/spotbugs_filter_unittest.xml
@@ -19,4 +19,9 @@
     <Match>

         <Bug pattern="UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR"/>

     </Match>

+    

+    <!-- Ignore default encoding when converting byte[] / String -->

+    <Match>

+        <Bug pattern="DM_DEFAULT_ENCODING"/>

+    </Match>

 </FindBugsFilter>
\ No newline at end of file
diff --git a/releng/org.eclipse.ease.releng/templates/plugin-test/META-INF/MANIFEST.MF b/releng/org.eclipse.ease.releng/templates/plugin-test/META-INF/MANIFEST.MF
index ec16d27..dd9564b 100644
--- a/releng/org.eclipse.ease.releng/templates/plugin-test/META-INF/MANIFEST.MF
+++ b/releng/org.eclipse.ease.releng/templates/plugin-test/META-INF/MANIFEST.MF
@@ -6,6 +6,7 @@
 Bundle-Version: ${bundle.version}.qualifier

 Bundle-Vendor: ${bundle.vendor}

 Bundle-RequiredExecutionEnvironment: ${java.version}

-Fragment-Host: ${bundle.host.id};bundle-version="${bundle.version}"

+Fragment-Host: ${bundle.host.id}

 Require-Bundle: org.junit.jupiter.api,

- org.mockito

+ org.mockito,

+ org.junit.jupiter.params

diff --git a/releng/org.eclipse.ease.releng/templates/plugin-test/pom.xml b/releng/org.eclipse.ease.releng/templates/plugin-test/pom.xml
index c5fa761..df8f6ff 100644
--- a/releng/org.eclipse.ease.releng/templates/plugin-test/pom.xml
+++ b/releng/org.eclipse.ease.releng/templates/plugin-test/pom.xml
@@ -8,7 +8,7 @@
 	

 	<parent>

 		<groupId>${project.root.id}</groupId>

-		<artifactId>${project.root.id}.unittests</artifactId>

+		<artifactId>${project.root.id}.core.tests</artifactId>

 		<version>${bundle.version}-SNAPSHOT</version>

 		<relativePath>..</relativePath>

 	</parent>

diff --git a/tests/org.eclipse.ease.lang.javascript.test/src/org/eclipse/ease/lang/javascript/JavaScriptCompletionContextTest.java b/tests/org.eclipse.ease.lang.javascript.test/src/org/eclipse/ease/lang/javascript/JavaScriptCompletionContextTest.java
deleted file mode 100644
index d2cedf9..0000000
--- a/tests/org.eclipse.ease.lang.javascript.test/src/org/eclipse/ease/lang/javascript/JavaScriptCompletionContextTest.java
+++ /dev/null
@@ -1,113 +0,0 @@
-package org.eclipse.ease.lang.javascript;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-
-import org.eclipse.ease.IScriptEngine;
-import org.eclipse.ease.service.EngineDescription;
-import org.eclipse.ease.service.IScriptService;
-import org.eclipse.ease.service.ScriptService;
-import org.eclipse.ease.ui.completion.CompletionContext;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-public class JavaScriptCompletionContextTest {
-
-	private static JavaScriptCompletionContext fContext;
-
-	@BeforeEach
-	public void setup() {
-		fContext = new JavaScriptCompletionContext(null);
-	}
-
-	@Test
-	public void verifyUnknownTypes() {
-		fContext.calculateContext(null, "getScriptEngine()", 0, 0);
-		assertEquals(CompletionContext.Type.UNKNOWN, fContext.getType());
-	}
-
-	@Test
-	public void verifyNoneTypes() {
-		fContext.calculateContext(null, "", 0, 0);
-		assertEquals(CompletionContext.Type.NONE, fContext.getType());
-
-		fContext.calculateContext(null, "get", 0, 0);
-		assertEquals(CompletionContext.Type.NONE, fContext.getType());
-	}
-
-	@Test
-	public void verifyStaticClassTypes() {
-		fContext.calculateContext(null, "java.lang.String.", 0, 0);
-		assertEquals(CompletionContext.Type.STATIC_CLASS, fContext.getType());
-
-		fContext.calculateContext(null, "Packages.java.lang.String.", 0, 0);
-		assertEquals(CompletionContext.Type.STATIC_CLASS, fContext.getType());
-	}
-
-	@Test
-	public void verifyStringLiteralTypes() {
-		fContext.calculateContext(null, "'Hello", 0, 0);
-		assertEquals(CompletionContext.Type.STRING_LITERAL, fContext.getType());
-		assertEquals("Hello", fContext.getFilter());
-		assertEquals("", fContext.getCaller());
-
-		fContext.calculateContext(null, "print('Hello", 0, 0);
-		assertEquals(CompletionContext.Type.STRING_LITERAL, fContext.getType());
-		assertEquals("Hello", fContext.getFilter());
-		assertEquals("print", fContext.getCaller());
-
-		fContext.calculateContext(null, "new java.lang.String('", 0, 0);
-		assertEquals(CompletionContext.Type.STRING_LITERAL, fContext.getType());
-		assertEquals("", fContext.getFilter());
-		assertEquals("new java.lang.String", fContext.getCaller());
-	}
-
-	@Test
-	public void verifyClassInstanceTypes() {
-		fContext.calculateContext(null, "getScriptEngine().", 0, 0);
-		assertEquals(CompletionContext.Type.CLASS_INSTANCE, fContext.getType());
-
-		fContext.calculateContext(null, "getScriptEngine().getInputStream().", 0, 0);
-		assertEquals(CompletionContext.Type.CLASS_INSTANCE, fContext.getType());
-	}
-
-	@Test
-	public void verifyPackageTypes() {
-		fContext.calculateContext(null, "java.lang.String", 0, 0);
-		assertEquals(CompletionContext.Type.PACKAGE, fContext.getType());
-
-		fContext.calculateContext(null, "Packages.java.lang.String", 0, 0);
-		assertEquals(CompletionContext.Type.PACKAGE, fContext.getType());
-	}
-
-	@Test
-	public void verifyTypesForScriptEngine() {
-		// we need to retrieve the service singleton as the workspace is not available in headless tests
-		final IScriptService scriptService = ScriptService.getService();
-		final EngineDescription engineDescription = scriptService.getEngine(JavaScriptHelper.SCRIPT_TYPE_JAVASCRIPT);
-		assertNotNull(engineDescription, "No JavaScript engine available");
-
-		final IScriptEngine engine = engineDescription.createEngine();
-		engine.setVariable("test", "Hello world");
-
-		fContext = new JavaScriptCompletionContext(engine);
-
-		fContext.calculateContext(null, "test.", 0, 0);
-		assertEquals(CompletionContext.Type.CLASS_INSTANCE, fContext.getType());
-
-		fContext.calculateContext(null, "test.getByt", 0, 0);
-		assertEquals(CompletionContext.Type.CLASS_INSTANCE, fContext.getType());
-
-		fContext.calculateContext(null, "test.toString().", 0, 0);
-		assertEquals(CompletionContext.Type.CLASS_INSTANCE, fContext.getType());
-	}
-
-	@Test
-	public void definedVariableTypes() {
-		fContext.calculateContext(null, "		// @type java.lang.String\n" + "		var a = \"foo\";\n" + "		a.", 0, 0);
-		assertEquals(CompletionContext.Type.CLASS_INSTANCE, fContext.getType());
-
-		fContext.calculateContext(null, "		// @type java.lang.String\n" + "		var a = \"foo\";\n" + "		a.toString().", 0, 0);
-		assertEquals(CompletionContext.Type.CLASS_INSTANCE, fContext.getType());
-	}
-}
diff --git a/tests/org.eclipse.ease.test/.classpath b/tests/org.eclipse.ease.test/.classpath
index f193818..a621c21 100644
--- a/tests/org.eclipse.ease.test/.classpath
+++ b/tests/org.eclipse.ease.test/.classpath
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
 	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
 	<classpathentry kind="src" path="src">
 		<attributes>
diff --git a/tests/org.eclipse.ease.test/.settings/org.eclipse.jdt.core.prefs b/tests/org.eclipse.ease.test/.settings/org.eclipse.jdt.core.prefs
index 64e8545..7004b85 100644
--- a/tests/org.eclipse.ease.test/.settings/org.eclipse.jdt.core.prefs
+++ b/tests/org.eclipse.ease.test/.settings/org.eclipse.jdt.core.prefs
@@ -11,17 +11,21 @@
 org.eclipse.jdt.core.codeComplete.staticFinalFieldSuffixes=
 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
 org.eclipse.jdt.core.compiler.codegen.methodParameters=generate
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=11
 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.compliance=11
 org.eclipse.jdt.core.compiler.debug.lineNumber=generate
 org.eclipse.jdt.core.compiler.debug.localVariable=generate
 org.eclipse.jdt.core.compiler.debug.sourceFile=generate
 org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
 org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.source=1.8
+org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
+org.eclipse.jdt.core.compiler.release=enabled
+org.eclipse.jdt.core.compiler.source=11
 org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647
 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
+org.eclipse.jdt.core.formatter.alignment_for_additive_operator=16
 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
@@ -29,19 +33,22 @@
 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
 org.eclipse.jdt.core.formatter.alignment_for_assignment=0
-org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator=16
 org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
 org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
 org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
 org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
 org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header=0
+org.eclipse.jdt.core.formatter.alignment_for_logical_operator=16
 org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
 org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
+org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator=16
 org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references=0
 org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
 org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
 org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
 org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_string_concatenation=16
 org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
 org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
 org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
@@ -130,11 +137,12 @@
 org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
 org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
 org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_after_additive_operator=insert
 org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
 org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
 org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
 org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator=insert
 org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
 org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
 org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
@@ -165,6 +173,8 @@
 org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
 org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
 org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert
+org.eclipse.jdt.core.formatter.insert_space_after_logical_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator=insert
 org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
 org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
 org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
@@ -189,13 +199,17 @@
 org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
 org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
 org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_relational_operator=insert
 org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
 org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert
+org.eclipse.jdt.core.formatter.insert_space_after_shift_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation=insert
 org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_additive_operator=insert
 org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
 org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
 org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_bitwise_operator=insert
 org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
 org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
 org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
@@ -243,6 +257,8 @@
 org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
 org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
 org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert
+org.eclipse.jdt.core.formatter.insert_space_before_logical_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator=insert
 org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
 org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
 org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
@@ -279,9 +295,12 @@
 org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
 org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
 org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_relational_operator=insert
 org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
 org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
 org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_shift_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation=insert
 org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
 org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
 org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
@@ -317,9 +336,13 @@
 org.eclipse.jdt.core.formatter.tabulation.size=4
 org.eclipse.jdt.core.formatter.use_on_off_tags=true
 org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
+org.eclipse.jdt.core.formatter.wrap_before_additive_operator=true
 org.eclipse.jdt.core.formatter.wrap_before_assignment_operator=false
-org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator=true
 org.eclipse.jdt.core.formatter.wrap_before_conditional_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_logical_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator=true
 org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
+org.eclipse.jdt.core.formatter.wrap_before_string_concatenation=true
 org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
 org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter
diff --git a/tests/org.eclipse.ease.test/META-INF/MANIFEST.MF b/tests/org.eclipse.ease.test/META-INF/MANIFEST.MF
index 7c262d9..6d1316b 100644
--- a/tests/org.eclipse.ease.test/META-INF/MANIFEST.MF
+++ b/tests/org.eclipse.ease.test/META-INF/MANIFEST.MF
@@ -4,7 +4,7 @@
 Bundle-SymbolicName: org.eclipse.ease.test;singleton:=true
 Bundle-Version: 0.9.0.qualifier
 Fragment-Host: org.eclipse.ease
-Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Bundle-RequiredExecutionEnvironment: JavaSE-11
 Require-Bundle: org.junit.jupiter.api;bundle-version="5.5.0",
  org.mockito,
  org.eclipse.ease.ui.scripts,
diff --git a/tests/org.eclipse.ease.test/build.properties b/tests/org.eclipse.ease.test/build.properties
index 34d2e4d..e3023e1 100644
--- a/tests/org.eclipse.ease.test/build.properties
+++ b/tests/org.eclipse.ease.test/build.properties
@@ -1,4 +1,5 @@
 source.. = src/
 output.. = bin/
 bin.includes = META-INF/,\
-               .
+               .,\
+               fragment.xml
diff --git a/tests/org.eclipse.ease.test/fragment.xml b/tests/org.eclipse.ease.test/fragment.xml
new file mode 100644
index 0000000..2a96bb0
--- /dev/null
+++ b/tests/org.eclipse.ease.test/fragment.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.4"?>
+<fragment>
+   <extension-point id="test" name="Test" schema="schema/test.exsd"/>
+   <extension
+         point="org.eclipse.ease.test">
+      <entry
+            ID="stringEntry"
+            class="java.lang.String">
+      </entry>
+      <entry
+            ID="stringbuilderEntry"
+            class="java.lang.StringBuilder">
+      </entry>
+   </extension>
+
+</fragment>
diff --git a/tests/org.eclipse.ease.test/schema/test.exsd b/tests/org.eclipse.ease.test/schema/test.exsd
new file mode 100644
index 0000000..02ac772
--- /dev/null
+++ b/tests/org.eclipse.ease.test/schema/test.exsd
@@ -0,0 +1,109 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!-- Schema file written by PDE -->
+<schema targetNamespace="org.eclipse.ease.test" xmlns="http://www.w3.org/2001/XMLSchema">
+<annotation>
+      <appinfo>
+         <meta.schema plugin="org.eclipse.ease.test" id="test" name="Test"/>
+      </appinfo>
+      <documentation>
+         [Enter description of this extension point.]
+      </documentation>
+   </annotation>
+
+   <element name="extension">
+      <annotation>
+         <appinfo>
+            <meta.element />
+         </appinfo>
+      </annotation>
+      <complexType>
+         <choice minOccurs="1" maxOccurs="unbounded">
+            <element ref="entry"/>
+         </choice>
+         <attribute name="point" type="string" use="required">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="id" type="string">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="name" type="string">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+               <appinfo>
+                  <meta.attribute translatable="true"/>
+               </appinfo>
+            </annotation>
+         </attribute>
+      </complexType>
+   </element>
+
+   <element name="entry">
+      <complexType>
+         <attribute name="ID" type="string" use="required">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="class" type="string" use="required">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+               <appinfo>
+                  <meta.attribute kind="java" basedOn="java.lang.String:"/>
+               </appinfo>
+            </annotation>
+         </attribute>
+      </complexType>
+   </element>
+
+   <annotation>
+      <appinfo>
+         <meta.section type="since"/>
+      </appinfo>
+      <documentation>
+         [Enter the first release in which this extension point appears.]
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appinfo>
+         <meta.section type="examples"/>
+      </appinfo>
+      <documentation>
+         [Enter extension point usage example here.]
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appinfo>
+         <meta.section type="apiinfo"/>
+      </appinfo>
+      <documentation>
+         [Enter API information here.]
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appinfo>
+         <meta.section type="implementation"/>
+      </appinfo>
+      <documentation>
+         [Enter information about supplied implementation of this extension point.]
+      </documentation>
+   </annotation>
+
+
+</schema>
diff --git a/tests/org.eclipse.ease.test/src/org/eclipse/ease/AbstractCodeParserTest.java b/tests/org.eclipse.ease.test/src/org/eclipse/ease/AbstractCodeParserTest.java
index b29636b..9e08e09 100644
--- a/tests/org.eclipse.ease.test/src/org/eclipse/ease/AbstractCodeParserTest.java
+++ b/tests/org.eclipse.ease.test/src/org/eclipse/ease/AbstractCodeParserTest.java
@@ -25,7 +25,7 @@
 public class AbstractCodeParserTest {
 
 	// classic implementation of a HeaderParser
-	private class HeaderParser extends AbstractCodeParser {
+	private static class HeaderParser extends AbstractCodeParser {
 
 		@Override
 		protected boolean hasBlockComment() {
@@ -54,38 +54,43 @@
 
 			return super.stripCommentLine(commentLine);
 		}
+
+		@Override
+		public ICompletionContext getContext(IScriptEngine scriptEngine, Object resource, String contents, int position, int selectionRange) {
+			return null;
+		}
 	}
 
 	// @formatter:off
-	private static final String TEMPLATE_BLOCK_HEADER = 
-			  "/**\n" 
+	private static final String TEMPLATE_BLOCK_HEADER =
+			  "/**\n"
 			+ " * This is a block comment with a keyword.\n"
 			+ " * \n"
 			+ " * key: word\n"
 			+ " * menu: no menu selected\n"
 			+ " * multi: this is a multi\n"
-			+ " * line keyword\n" 
+			+ " * line keyword\n"
 			+ " **/\n";
 
 	private static final String TEMPLATE_LINE_HEADER =
 			  "//\n"
 			+ "// This is a block comment with a keyword.\n"
-			+ "// \n" 
+			+ "// \n"
 			+ " // key: word\n"
-			+ "// menu: no menu selected\n" 
-			+ "// multi: this is a multi\n" 
-			+ "// line keyword\n" 
+			+ "// menu: no menu selected\n"
+			+ "// multi: this is a multi\n"
+			+ "// line keyword\n"
 			+ "//\n";
 
 	private static final String TEMPLATE_NO_COMMENT =
 			  "var a = 13;\n"
 			+ TEMPLATE_BLOCK_HEADER;
-	
+
 	private static final String TEMPLATE_WHITESPACE_BEFORE_BLOCK_HEADER =
 			  "\n"
 			+ "\t\t\n"
 			+ "        \n"
-			+ "     " 
+			+ "     "
 			+ TEMPLATE_BLOCK_HEADER;
 	// @formatter:on
 
@@ -133,7 +138,7 @@
 		assertEquals("this is a multi line keyword", keywords.get("multi"));
 	}
 
-	private static final InputStream toStream(String data) {
+	private static InputStream toStream(String data) {
 		return new ByteArrayInputStream(data.getBytes());
 	}
 }
diff --git a/tests/org.eclipse.ease.test/src/org/eclipse/ease/tools/PlatformExtensionTest.java b/tests/org.eclipse.ease.test/src/org/eclipse/ease/tools/PlatformExtensionTest.java
new file mode 100644
index 0000000..b7baa97
--- /dev/null
+++ b/tests/org.eclipse.ease.test/src/org/eclipse/ease/tools/PlatformExtensionTest.java
@@ -0,0 +1,86 @@
+package org.eclipse.ease.tools;

+

+import static org.junit.jupiter.api.Assertions.assertEquals;

+import static org.junit.jupiter.api.Assertions.assertNotNull;

+import static org.junit.jupiter.api.Assertions.assertNull;

+import static org.junit.jupiter.api.Assertions.assertThrows;

+import static org.junit.jupiter.api.Assertions.assertTrue;

+

+import java.util.Collection;

+import java.util.Objects;

+

+import org.eclipse.core.runtime.CoreException;

+import org.junit.jupiter.api.DisplayName;

+import org.junit.jupiter.api.Test;

+

+public class PlatformExtensionTest {

+

+	private static final String EXTENSION_POINT = "org.eclipse.ease.test";

+

+	@Test

+	@DisplayName("createFor('{ease}.notThere') returns empty collection")

+	public void createFor_returns_empty_collection() {

+		final Collection<PlatformExtension> extensions = PlatformExtension.createFor("org.eclipse.ease.notthere");

+		assertNotNull(extensions);

+		assertTrue(extensions.isEmpty());

+	}

+

+	@Test

+	@DisplayName("createFor('{ease}.test') returns populated collection")

+	public void createFor_returns_non_empty_collection() {

+		final Collection<PlatformExtension> extensions = PlatformExtension.createFor(EXTENSION_POINT);

+		assertEquals(2, extensions.size());

+	}

+

+	@Test

+	@DisplayName("createForName('{ease}.test', 'entry') returns populated collection")

+	public void createForName_returns_non_empty_collection() {

+		final Collection<PlatformExtension> extensions = PlatformExtension.createForName(EXTENSION_POINT, "entry");

+		assertEquals(2, extensions.size());

+	}

+

+	@Test

+	@DisplayName("getConfigurationElement() != null")

+	public void getConfigurationElement_is_not_null() {

+		final Collection<PlatformExtension> extensions = PlatformExtension.createFor(EXTENSION_POINT);

+

+		for (final PlatformExtension extension : extensions)

+			assertNotNull(extension.getConfigurationElement());

+	}

+

+	@Test

+	@DisplayName("getAttribute() != null for known attribute")

+	public void getAttribute_is_not_null_for_known_attribute() {

+		final Collection<PlatformExtension> extensions = PlatformExtension.createFor(EXTENSION_POINT);

+		final PlatformExtension extension = extensions.iterator().next();

+

+		assertNotNull(extension.getAttribute("ID"));

+	}

+

+	@Test

+	@DisplayName("getAttribute() == null for unknown attribute")

+	public void getAttribute_is_null_for_unknown_attribute() {

+		final Collection<PlatformExtension> extensions = PlatformExtension.createFor(EXTENSION_POINT);

+		final PlatformExtension extension = extensions.iterator().next();

+

+		assertNull(extension.getAttribute("unknown"));

+	}

+

+	@Test

+	@DisplayName("createInstance() != null for valid type")

+	public void createInstance_returns_class_instance() throws CoreException {

+		final Collection<PlatformExtension> extensions = PlatformExtension.createFor(EXTENSION_POINT);

+		final PlatformExtension extension = extensions.stream().filter(e -> Objects.equals("stringEntry", e.getAttribute("ID"))).findAny().orElseThrow();

+

+		assertNotNull(extension.createInstance("class", String.class));

+	}

+

+	@Test

+	@DisplayName("createInstance() throws ClassCastException for invalid type")

+	public void createInstance_throws_for_invalid_type() throws CoreException {

+		final Collection<PlatformExtension> extensions = PlatformExtension.createFor(EXTENSION_POINT);

+		final PlatformExtension extension = extensions.stream().filter(e -> Objects.equals("stringEntry", e.getAttribute("ID"))).findAny().orElseThrow();

+

+		assertThrows(ClassCastException.class, () -> extension.createInstance("class", StringBuilder.class));

+	}

+}

diff --git a/tests/org.eclipse.ease.ui.completions.java.test/.checkstyle b/tests/org.eclipse.ease.ui.completions.java.test/.checkstyle
new file mode 100644
index 0000000..83a40d4
--- /dev/null
+++ b/tests/org.eclipse.ease.ui.completions.java.test/.checkstyle
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>

+

+<fileset-config file-format-version="1.2.0" simple-config="false" sync-formatter="false">

+  <local-check-config name="Eclipse.org Checkstyle" location="/org.eclipse.ease.releng/checkstyle/checkstyle_rules_unittest.xml" type="project" description="">

+    <additional-data name="protect-config-file" value="false"/>

+  </local-check-config>

+  <fileset name="All files" enabled="true" check-config-name="Eclipse.org Checkstyle" local="true">

+    <file-match-pattern match-pattern=".java$" include-pattern="true"/>

+  </fileset>

+</fileset-config>

diff --git a/tests/org.eclipse.ease.ui.completions.java.test/.classpath b/tests/org.eclipse.ease.ui.completions.java.test/.classpath
new file mode 100644
index 0000000..856ddfc
--- /dev/null
+++ b/tests/org.eclipse.ease.ui.completions.java.test/.classpath
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>

+<classpath>

+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8" />

+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins" />

+	<classpathentry kind="src" path="src">

+		<attributes>

+			<attribute name="test" value="true" />

+		</attributes>

+	</classpathentry>

+	<classpathentry kind="output" path="target/classes"/>

+</classpath>

diff --git a/tests/org.eclipse.ease.ui.completions.java.test/.eclipse-pmd b/tests/org.eclipse.ease.ui.completions.java.test/.eclipse-pmd
new file mode 100644
index 0000000..00a904c
--- /dev/null
+++ b/tests/org.eclipse.ease.ui.completions.java.test/.eclipse-pmd
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>

+<eclipse-pmd xmlns="http://acanda.ch/eclipse-pmd/0.8" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://acanda.ch/eclipse-pmd/0.8 http://acanda.ch/eclipse-pmd/eclipse-pmd-0.8.xsd">

+  <analysis enabled="true" />

+  <rulesets>

+    <ruleset name="Eclipse EASE Unittest PMD Rules" ref="$org.eclipse.ease.releng/pmd/pmd_rules_unittest.xml" refcontext="workspace" />

+  </rulesets>

+</eclipse-pmd>
\ No newline at end of file
diff --git a/tests/org.eclipse.ease.ui.completions.java.test/.project b/tests/org.eclipse.ease.ui.completions.java.test/.project
new file mode 100644
index 0000000..0f147ec
--- /dev/null
+++ b/tests/org.eclipse.ease.ui.completions.java.test/.project
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.eclipse.ease.ui.completions.java.test</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.ManifestBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.SchemaBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>ch.acanda.eclipse.pmd.builder.PMDBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>edu.umd.cs.findbugs.plugin.eclipse.findbugsBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>net.sf.eclipsecs.core.CheckstyleBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.eclipse.pde.PluginNature</nature>
+		<nature>ch.acanda.eclipse.pmd.builder.PMDNature</nature>
+		<nature>edu.umd.cs.findbugs.plugin.eclipse.findbugsNature</nature>
+		<nature>net.sf.eclipsecs.core.CheckstyleNature</nature>
+	</natures>
+</projectDescription>
diff --git a/tests/org.eclipse.ease.ui.completions.java.test/.settings/com.github.spotbugs.plugin.eclipse.prefs b/tests/org.eclipse.ease.ui.completions.java.test/.settings/com.github.spotbugs.plugin.eclipse.prefs
new file mode 100644
index 0000000..53e9365
--- /dev/null
+++ b/tests/org.eclipse.ease.ui.completions.java.test/.settings/com.github.spotbugs.plugin.eclipse.prefs
@@ -0,0 +1,2 @@
+dontRemindAboutFullBuild=true

+eclipse.preferences.version=1

diff --git a/tests/org.eclipse.ease.ui.completions.java.test/.settings/edu.umd.cs.findbugs.core.prefs b/tests/org.eclipse.ease.ui.completions.java.test/.settings/edu.umd.cs.findbugs.core.prefs
new file mode 100644
index 0000000..bf5403e
--- /dev/null
+++ b/tests/org.eclipse.ease.ui.completions.java.test/.settings/edu.umd.cs.findbugs.core.prefs
@@ -0,0 +1,145 @@
+#SpotBugs User Preferences

+#Thu Mar 11 18:38:53 CET 2021

+detectorExplicitSerialization=ExplicitSerialization|true

+detectorMultithreadedInstanceAccess=MultithreadedInstanceAccess|true

+detectorConfusionBetweenInheritedAndOuterMethod=ConfusionBetweenInheritedAndOuterMethod|true

+detectorWrongMapIterator=WrongMapIterator|true

+detectorUnnecessaryMath=UnnecessaryMath|true

+detectorUselessSubclassMethod=UselessSubclassMethod|false

+filter_settings=Low|BAD_PRACTICE,CORRECTNESS,EXPERIMENTAL,I18N,MALICIOUS_CODE,MT_CORRECTNESS,PERFORMANCE,SECURITY,STYLE|false|20

+detectorURLProblems=URLProblems|true

+detectorIteratorIdioms=IteratorIdioms|true

+detectorMutableEnum=MutableEnum|true

+detectorFindNonShortCircuit=FindNonShortCircuit|true

+detectorSynchronizeAndNullCheckField=SynchronizeAndNullCheckField|true

+detectorVolatileUsage=VolatileUsage|true

+detectorFindNakedNotify=FindNakedNotify|true

+detectorFindUninitializedGet=FindUninitializedGet|true

+detectorFindUseOfNonSerializableValue=FindUseOfNonSerializableValue|true

+detectorFindJSR166LockMonitorenter=FindJSR166LockMonitorenter|true

+detectorQuestionableBooleanAssignment=QuestionableBooleanAssignment|true

+detectorSwitchFallthrough=SwitchFallthrough|true

+detectorFindLocalSelfAssignment2=FindLocalSelfAssignment2|true

+detectorConfusedInheritance=ConfusedInheritance|true

+detectorSynchronizationOnSharedBuiltinConstant=SynchronizationOnSharedBuiltinConstant|true

+detectorMutableStaticFields=MutableStaticFields|true

+detectorInvalidJUnitTest=InvalidJUnitTest|true

+detectorInfiniteLoop=InfiniteLoop|true

+detectorFindRunInvocations=FindRunInvocations|true

+detectorBadSyntaxForRegularExpression=BadSyntaxForRegularExpression|true

+detectorXMLFactoryBypass=XMLFactoryBypass|true

+detectorFindOpenStream=FindOpenStream|true

+detectorCheckExpectedWarnings=CheckExpectedWarnings|false

+detectorHugeSharedStringConstants=HugeSharedStringConstants|true

+detectorLostLoggerDueToWeakReference=LostLoggerDueToWeakReference|true

+detectorStringConcatenation=StringConcatenation|true

+detectorLoadOfKnownNullValue=LoadOfKnownNullValue|true

+detectorFinalizerNullsFields=FinalizerNullsFields|true

+detectorFindFieldSelfAssignment=FindFieldSelfAssignment|true

+detectorInefficientToArray=InefficientToArray|false

+detectorDontCatchIllegalMonitorStateException=DontCatchIllegalMonitorStateException|true

+detectorInconsistentAnnotations=InconsistentAnnotations|true

+detectorBadlyOverriddenAdapter=BadlyOverriddenAdapter|true

+detectorInstantiateStaticClass=InstantiateStaticClass|true

+detectorCheckRelaxingNullnessAnnotation=CheckRelaxingNullnessAnnotation|true

+detectorMethodReturnCheck=MethodReturnCheck|true

+detectorEqualsOperandShouldHaveClassCompatibleWithThis=EqualsOperandShouldHaveClassCompatibleWithThis|true

+detectorFindDoubleCheck=FindDoubleCheck|true

+detectorFindBadForLoop=FindBadForLoop|true

+detectorDefaultEncodingDetector=DefaultEncodingDetector|true

+detectorFindInconsistentSync2=FindInconsistentSync2|true

+detectorFindSpinLoop=FindSpinLoop|true

+detectorFindMaskedFields=FindMaskedFields|true

+detectorBooleanReturnNull=BooleanReturnNull|true

+detectorFindUnsyncGet=FindUnsyncGet|true

+detectorCrossSiteScripting=CrossSiteScripting|true

+detectorDroppedException=DroppedException|true

+detectorFindDeadLocalStores=FindDeadLocalStores|true

+detectorCheckImmutableAnnotation=CheckImmutableAnnotation|true

+detectorInfiniteRecursiveLoop=InfiniteRecursiveLoop|true

+detectorFindRefComparison=FindRefComparison|true

+detectorFindRoughConstants=FindRoughConstants|true

+detectorMutableLock=MutableLock|true

+detectorFindNullDeref=FindNullDeref|true

+detectorFindReturnRef=FindReturnRef|true

+detectorSynchronizeOnClassLiteralNotGetClass=SynchronizeOnClassLiteralNotGetClass|true

+detectorFindUselessControlFlow=FindUselessControlFlow|true

+detectorOverridingEqualsNotSymmetrical=OverridingEqualsNotSymmetrical|true

+detectorIDivResultCastToDouble=IDivResultCastToDouble|true

+detectorReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass=ReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass|true

+detectorFindSelfComparison=FindSelfComparison|true

+detectorFindFloatEquality=FindFloatEquality|true

+detectorFindComparatorProblems=FindComparatorProblems|true

+detectorRepeatedConditionals=RepeatedConditionals|true

+filter_settings_neg=NOISE|

+detectorInefficientMemberAccess=InefficientMemberAccess|false

+detectorFindUncalledPrivateMethods=FindUncalledPrivateMethods|true

+detectorNumberConstructor=NumberConstructor|true

+detectorDontAssertInstanceofInTests=DontAssertInstanceofInTests|true

+detectorFindFinalizeInvocations=FindFinalizeInvocations|true

+detectorFindNullDerefsInvolvingNonShortCircuitEvaluation=FindNullDerefsInvolvingNonShortCircuitEvaluation|true

+detectorDontIgnoreResultOfPutIfAbsent=DontIgnoreResultOfPutIfAbsent|true

+detectorFindUnconditionalWait=FindUnconditionalWait|true

+detectorFindTwoLockWait=FindTwoLockWait|true

+detectorFindSleepWithLockHeld=FindSleepWithLockHeld|true

+detectorFindUnreleasedLock=FindUnreleasedLock|true

+detectorInefficientIndexOf=InefficientIndexOf|false

+detectorDoInsideDoPrivileged=DoInsideDoPrivileged|true

+detectorFindEmptySynchronizedBlock=FindEmptySynchronizedBlock|true

+detectorOverridingMethodsMustInvokeSuperDetector=OverridingMethodsMustInvokeSuperDetector|true

+detectorWaitInLoop=WaitInLoop|true

+detectorIntCast2LongAsInstant=IntCast2LongAsInstant|true

+detectorBadUseOfReturnValue=BadUseOfReturnValue|true

+detectorFindSqlInjection=FindSqlInjection|true

+detectorUnreadFields=UnreadFields|true

+detectorSynchronizingOnContentsOfFieldToProtectField=SynchronizingOnContentsOfFieldToProtectField|true

+detectorFindUselessObjects=FindUselessObjects|true

+detectorBadAppletConstructor=BadAppletConstructor|false

+detectorInheritanceUnsafeGetResource=InheritanceUnsafeGetResource|true

+detectorSerializableIdiom=SerializableIdiom|true

+detectorNaming=Naming|true

+detectorNoteUnconditionalParamDerefs=NoteUnconditionalParamDerefs|true

+detectorFormatStringChecker=FormatStringChecker|true

+detectorSuspiciousThreadInterrupted=SuspiciousThreadInterrupted|true

+detectorEmptyZipFileEntry=EmptyZipFileEntry|false

+detectorFindCircularDependencies=FindCircularDependencies|false

+detectorPreferZeroLengthArrays=PreferZeroLengthArrays|true

+detectorAtomicityProblem=AtomicityProblem|true

+detectorRuntimeExceptionCapture=RuntimeExceptionCapture|true

+detectorInitializationChain=InitializationChain|true

+detectorInitializeNonnullFieldsInConstructor=InitializeNonnullFieldsInConstructor|true

+detectorOptionalReturnNull=OptionalReturnNull|true

+detectorStartInConstructor=StartInConstructor|true

+detectorFindUnsatisfiedObligation=FindUnsatisfiedObligation|true

+detectorRedundantConditions=RedundantConditions|true

+effort=max

+detectorRedundantInterfaces=RedundantInterfaces|true

+detectorDuplicateBranches=DuplicateBranches|true

+detectorCheckTypeQualifiers=CheckTypeQualifiers|true

+detectorComparatorIdiom=ComparatorIdiom|true

+detectorFindBadCast2=FindBadCast2|true

+detectorFindMismatchedWaitOrNotify=FindMismatchedWaitOrNotify|true

+excludefilter0=../../releng/org.eclipse.ease.releng/spotbugs/spotbugs_filter_unittest.xml|true

+detectorBadResultSetAccess=BadResultSetAccess|true

+detectorIncompatMask=IncompatMask|true

+detectorCovariantArrayAssignment=CovariantArrayAssignment|false

+detectorDumbMethodInvocations=DumbMethodInvocations|true

+run_at_full_build=true

+detectorStaticCalendarDetector=StaticCalendarDetector|true

+detectorUncallableMethodOfAnonymousClass=UncallableMethodOfAnonymousClass|true

+detectorVarArgsProblems=VarArgsProblems|true

+detectorInefficientInitializationInsideLoop=InefficientInitializationInsideLoop|false

+detectorCloneIdiom=CloneIdiom|true

+detectorFindHEmismatch=FindHEmismatch|true

+detectorAppendingToAnObjectOutputStream=AppendingToAnObjectOutputStream|true

+detectorFindSelfComparison2=FindSelfComparison2|true

+detectorLazyInit=LazyInit|true

+detectorFindUnrelatedTypesInGenericContainer=FindUnrelatedTypesInGenericContainer|true

+detectorDontUseEnum=DontUseEnum|true

+detectorFindPuzzlers=FindPuzzlers|true

+detectorCallToUnsupportedMethod=CallToUnsupportedMethod|false

+detectorSuperfluousInstanceOf=SuperfluousInstanceOf|true

+detectorReadReturnShouldBeChecked=ReadReturnShouldBeChecked|true

+detector_threshold=3

+detectorPublicSemaphores=PublicSemaphores|false

+detectorDumbMethods=DumbMethods|true

diff --git a/tests/org.eclipse.ease.ui.completions.java.test/.settings/org.eclipse.core.resources.prefs b/tests/org.eclipse.ease.ui.completions.java.test/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..4824b80
--- /dev/null
+++ b/tests/org.eclipse.ease.ui.completions.java.test/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1

+encoding/<project>=UTF-8

diff --git a/tests/org.eclipse.ease.ui.completions.java.test/.settings/org.eclipse.jdt.core.prefs b/tests/org.eclipse.ease.ui.completions.java.test/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..ac87d4f
--- /dev/null
+++ b/tests/org.eclipse.ease.ui.completions.java.test/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,325 @@
+eclipse.preferences.version=1

+org.eclipse.jdt.core.codeComplete.argumentPrefixes=

+org.eclipse.jdt.core.codeComplete.argumentSuffixes=

+org.eclipse.jdt.core.codeComplete.fieldPrefixes=f

+org.eclipse.jdt.core.codeComplete.fieldSuffixes=

+org.eclipse.jdt.core.codeComplete.localPrefixes=

+org.eclipse.jdt.core.codeComplete.localSuffixes=

+org.eclipse.jdt.core.codeComplete.staticFieldPrefixes=

+org.eclipse.jdt.core.codeComplete.staticFieldSuffixes=

+org.eclipse.jdt.core.codeComplete.staticFinalFieldPrefixes=

+org.eclipse.jdt.core.codeComplete.staticFinalFieldSuffixes=

+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled

+org.eclipse.jdt.core.compiler.codegen.methodParameters=generate

+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8

+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve

+org.eclipse.jdt.core.compiler.compliance=1.8

+org.eclipse.jdt.core.compiler.debug.lineNumber=generate

+org.eclipse.jdt.core.compiler.debug.localVariable=generate

+org.eclipse.jdt.core.compiler.debug.sourceFile=generate

+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error

+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error

+org.eclipse.jdt.core.compiler.source=1.8

+org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647

+org.eclipse.jdt.core.formatter.align_type_members_on_columns=false

+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16

+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0

+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16

+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16

+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16

+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16

+org.eclipse.jdt.core.formatter.alignment_for_assignment=0

+org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16

+org.eclipse.jdt.core.formatter.alignment_for_compact_if=16

+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80

+org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0

+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16

+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header=0

+org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0

+org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16

+org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references=0

+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16

+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16

+org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80

+org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16

+org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16

+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16

+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16

+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16

+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16

+org.eclipse.jdt.core.formatter.alignment_for_type_arguments=0

+org.eclipse.jdt.core.formatter.alignment_for_type_parameters=0

+org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16

+org.eclipse.jdt.core.formatter.blank_lines_after_imports=1

+org.eclipse.jdt.core.formatter.blank_lines_after_package=1

+org.eclipse.jdt.core.formatter.blank_lines_before_field=0

+org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0

+org.eclipse.jdt.core.formatter.blank_lines_before_imports=1

+org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1

+org.eclipse.jdt.core.formatter.blank_lines_before_method=1

+org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1

+org.eclipse.jdt.core.formatter.blank_lines_before_package=0

+org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1

+org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1

+org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line

+org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line

+org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line

+org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line

+org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line

+org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line

+org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line

+org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line

+org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line

+org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line

+org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line

+org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line

+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false

+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false

+org.eclipse.jdt.core.formatter.comment.format_block_comments=true

+org.eclipse.jdt.core.formatter.comment.format_header=false

+org.eclipse.jdt.core.formatter.comment.format_html=true

+org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true

+org.eclipse.jdt.core.formatter.comment.format_line_comments=true

+org.eclipse.jdt.core.formatter.comment.format_source_code=true

+org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true

+org.eclipse.jdt.core.formatter.comment.indent_root_tags=true

+org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert

+org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert

+org.eclipse.jdt.core.formatter.comment.line_length=160

+org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true

+org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true

+org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false

+org.eclipse.jdt.core.formatter.compact_else_if=true

+org.eclipse.jdt.core.formatter.continuation_indentation=2

+org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2

+org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off

+org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on

+org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false

+org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true

+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true

+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true

+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true

+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true

+org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true

+org.eclipse.jdt.core.formatter.indent_empty_lines=false

+org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true

+org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true

+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true

+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false

+org.eclipse.jdt.core.formatter.indentation.size=4

+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant=insert

+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert

+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert

+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert

+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert

+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert

+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert

+org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert

+org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert

+org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert

+org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert

+org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert

+org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert

+org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert

+org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert

+org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert

+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert

+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert

+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert

+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert

+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert

+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert

+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert

+org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert

+org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert

+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert

+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert

+org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert

+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert

+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert

+org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert

+org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert

+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert

+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert

+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert

+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert

+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert

+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert

+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert

+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert

+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert

+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert

+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert

+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert

+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert

+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert

+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert

+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert

+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert

+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert

+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert

+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert

+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert

+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert

+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert

+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert

+org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert

+org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert

+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert

+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert

+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert

+org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert

+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert

+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert

+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert

+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert

+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert

+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert

+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert

+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert

+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert

+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert

+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert

+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert

+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert

+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert

+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert

+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert

+org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert

+org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert

+org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert

+org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert

+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert

+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert

+org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert

+org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert

+org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert

+org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert

+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert

+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert

+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert

+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert

+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert

+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert

+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert

+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert

+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert

+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert

+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert

+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert

+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert

+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert

+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert

+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert

+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert

+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert

+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert

+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert

+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert

+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert

+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert

+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert

+org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert

+org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert

+org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert

+org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert

+org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert

+org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert

+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert

+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert

+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert

+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert

+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert

+org.eclipse.jdt.core.formatter.join_lines_in_comments=true

+org.eclipse.jdt.core.formatter.join_wrapped_lines=true

+org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false

+org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false

+org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false

+org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false

+org.eclipse.jdt.core.formatter.lineSplit=160

+org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false

+org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false

+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0

+org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1

+org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines

+org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause=common_lines

+org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration=common_lines

+org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment=common_lines

+org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement=common_lines

+org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration=common_lines

+org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration=common_lines

+org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation=common_lines

+org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement=common_lines

+org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause=common_lines

+org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true

+org.eclipse.jdt.core.formatter.tabulation.char=tab

+org.eclipse.jdt.core.formatter.tabulation.size=4

+org.eclipse.jdt.core.formatter.use_on_off_tags=true

+org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false

+org.eclipse.jdt.core.formatter.wrap_before_assignment_operator=false

+org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true

+org.eclipse.jdt.core.formatter.wrap_before_conditional_operator=true

+org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true

+org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true

+org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter

diff --git a/tests/org.eclipse.ease.ui.completions.java.test/.settings/org.eclipse.jdt.ui.prefs b/tests/org.eclipse.ease.ui.completions.java.test/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 0000000..4a6e7c5
--- /dev/null
+++ b/tests/org.eclipse.ease.ui.completions.java.test/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,130 @@
+cleanup.add_default_serial_version_id=false
+cleanup.add_generated_serial_version_id=true
+cleanup.add_missing_annotations=true
+cleanup.add_missing_deprecated_annotations=true
+cleanup.add_missing_methods=false
+cleanup.add_missing_nls_tags=false
+cleanup.add_missing_override_annotations=true
+cleanup.add_missing_override_annotations_interface_methods=true
+cleanup.add_serial_version_id=true
+cleanup.always_use_blocks=true
+cleanup.always_use_parentheses_in_expressions=true
+cleanup.always_use_this_for_non_static_field_access=false
+cleanup.always_use_this_for_non_static_method_access=false
+cleanup.convert_functional_interfaces=false
+cleanup.convert_to_enhanced_for_loop=false
+cleanup.correct_indentation=true
+cleanup.format_source_code=true
+cleanup.format_source_code_changes_only=false
+cleanup.insert_inferred_type_arguments=false
+cleanup.make_local_variable_final=false
+cleanup.make_parameters_final=true
+cleanup.make_private_fields_final=true
+cleanup.make_type_abstract_if_missing_method=false
+cleanup.make_variable_declarations_final=true
+cleanup.never_use_blocks=false
+cleanup.never_use_parentheses_in_expressions=false
+cleanup.organize_imports=true
+cleanup.qualify_static_field_accesses_with_declaring_class=false
+cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+cleanup.qualify_static_member_accesses_with_declaring_class=true
+cleanup.qualify_static_method_accesses_with_declaring_class=false
+cleanup.remove_private_constructors=true
+cleanup.remove_redundant_type_arguments=true
+cleanup.remove_trailing_whitespaces=true
+cleanup.remove_trailing_whitespaces_all=true
+cleanup.remove_trailing_whitespaces_ignore_empty=false
+cleanup.remove_unnecessary_casts=false
+cleanup.remove_unnecessary_nls_tags=true
+cleanup.remove_unused_imports=true
+cleanup.remove_unused_local_variables=false
+cleanup.remove_unused_private_fields=true
+cleanup.remove_unused_private_members=false
+cleanup.remove_unused_private_methods=true
+cleanup.remove_unused_private_types=true
+cleanup.sort_members=false
+cleanup.sort_members_all=false
+cleanup.use_anonymous_class_creation=false
+cleanup.use_blocks=false
+cleanup.use_blocks_only_for_return_and_throw=false
+cleanup.use_lambda=true
+cleanup.use_parentheses_in_expressions=true
+cleanup.use_this_for_non_static_field_access=true
+cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+cleanup.use_this_for_non_static_method_access=true
+cleanup.use_this_for_non_static_method_access_only_if_necessary=true
+cleanup.use_type_arguments=false
+cleanup_profile=_EASE Development
+cleanup_settings_version=2
+eclipse.preferences.version=1
+editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
+formatter_profile=_Eclipse EASE Development
+formatter_settings_version=12
+org.eclipse.jdt.ui.exception.name=e
+org.eclipse.jdt.ui.gettersetter.use.is=true
+org.eclipse.jdt.ui.ignorelowercasenames=true
+org.eclipse.jdt.ui.importorder=java;javax;org;com;
+org.eclipse.jdt.ui.javadoc=true
+org.eclipse.jdt.ui.keywordthis=false
+org.eclipse.jdt.ui.ondemandthreshold=99
+org.eclipse.jdt.ui.overrideannotation=true
+org.eclipse.jdt.ui.staticondemandthreshold=99
+org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates><template autoinsert\="true" context\="gettercomment_context" deleted\="false" description\="Comment for getter method" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.gettercomment" name\="gettercomment">/**\n * @return the ${bare_field_name}\n */</template><template autoinsert\="true" context\="settercomment_context" deleted\="false" description\="Comment for setter method" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.settercomment" name\="settercomment">/**\n * @param ${param} the ${bare_field_name} to set\n */</template><template autoinsert\="true" context\="constructorcomment_context" deleted\="false" description\="Comment for created constructors" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.constructorcomment" name\="constructorcomment">/**\n * ${tags}\n */</template><template autoinsert\="false" context\="filecomment_context" deleted\="false" description\="Comment for created Java files" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.filecomment" name\="filecomment">/*******************************************************************************\n * Copyright (c) ${year} ${user} and others.\n * All rights reserved. This program and the accompanying materials\n * are made available under the terms of the Eclipse Public License v2.0\n * which accompanies this distribution, and is available at\n * https\://www.eclipse.org/legal/epl-2.0/\n *\n * SPDX-License_Identifier\: EPL-2.0\n *\n * Contributors\:\n *     ${user} - initial API and implementation\n *******************************************************************************/\n</template><template autoinsert\="false" context\="typecomment_context" deleted\="false" description\="Comment for created types" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.typecomment" name\="typecomment">/**\n * ${tags}\n */</template><template autoinsert\="true" context\="fieldcomment_context" deleted\="false" description\="Comment for fields" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.fieldcomment" name\="fieldcomment">/**\n * \n */</template><template autoinsert\="true" context\="methodcomment_context" deleted\="false" description\="Comment for non-overriding methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.methodcomment" name\="methodcomment">/**\n * ${tags}\n */</template><template autoinsert\="false" context\="overridecomment_context" deleted\="false" description\="Comment for overriding methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.overridecomment" name\="overridecomment"/><template autoinsert\="true" context\="delegatecomment_context" deleted\="false" description\="Comment for delegate methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.delegatecomment" name\="delegatecomment">/**\n * ${tags}\n * ${see_to_target}\n */</template><template autoinsert\="true" context\="newtype_context" deleted\="false" description\="Newly created files" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.newtype" name\="newtype">${filecomment}\n${package_declaration}\n\n${typecomment}\n${type_declaration}</template><template autoinsert\="true" context\="classbody_context" deleted\="false" description\="Code in new class type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.classbody" name\="classbody">\n</template><template autoinsert\="true" context\="interfacebody_context" deleted\="false" description\="Code in new interface type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.interfacebody" name\="interfacebody">\n</template><template autoinsert\="true" context\="enumbody_context" deleted\="false" description\="Code in new enum type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.enumbody" name\="enumbody">\n</template><template autoinsert\="true" context\="annotationbody_context" deleted\="false" description\="Code in new annotation type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.annotationbody" name\="annotationbody">\n</template><template autoinsert\="false" context\="catchblock_context" deleted\="false" description\="Code in new catch blocks" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.catchblock" name\="catchblock">// ${todo} handle this exception (but for now, at least know it happened)\nthrow new RuntimeException(${exception_var});\n</template><template autoinsert\="true" context\="methodbody_context" deleted\="false" description\="Code in created method stubs" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.methodbody" name\="methodbody">// ${todo} Auto-generated method stub\n${body_statement}</template><template autoinsert\="true" context\="constructorbody_context" deleted\="false" description\="Code in created constructor stubs" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.constructorbody" name\="constructorbody">${body_statement}\n// ${todo} Auto-generated constructor stub</template><template autoinsert\="true" context\="getterbody_context" deleted\="false" description\="Code in created getters" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.getterbody" name\="getterbody">return ${field};</template><template autoinsert\="true" context\="setterbody_context" deleted\="false" description\="Code in created setters" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.setterbody" name\="setterbody">${field} \= ${param};</template><template autoinsert\="true" context\="modulecomment_context" deleted\="false" description\="Comment for modules" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.modulecomment" name\="modulecomment">/**\n * @author ${user}\n *\n * ${tags}\n */</template><template autoinsert\="true" context\="recordbody_context" deleted\="false" description\="Code in new record type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.recordbody" name\="recordbody">\n</template></templates>
+sp_cleanup.add_default_serial_version_id=true
+sp_cleanup.add_generated_serial_version_id=false
+sp_cleanup.add_missing_annotations=true
+sp_cleanup.add_missing_deprecated_annotations=true
+sp_cleanup.add_missing_methods=false
+sp_cleanup.add_missing_nls_tags=false
+sp_cleanup.add_missing_override_annotations=true
+sp_cleanup.add_missing_override_annotations_interface_methods=true
+sp_cleanup.add_serial_version_id=false
+sp_cleanup.always_use_blocks=true
+sp_cleanup.always_use_parentheses_in_expressions=true
+sp_cleanup.always_use_this_for_non_static_field_access=false
+sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_functional_interfaces=true
+sp_cleanup.convert_to_enhanced_for_loop=true
+sp_cleanup.correct_indentation=true
+sp_cleanup.format_source_code=true
+sp_cleanup.format_source_code_changes_only=false
+sp_cleanup.insert_inferred_type_arguments=false
+sp_cleanup.make_local_variable_final=true
+sp_cleanup.make_parameters_final=false
+sp_cleanup.make_private_fields_final=true
+sp_cleanup.make_type_abstract_if_missing_method=false
+sp_cleanup.make_variable_declarations_final=true
+sp_cleanup.never_use_blocks=false
+sp_cleanup.never_use_parentheses_in_expressions=false
+sp_cleanup.on_save_use_additional_actions=true
+sp_cleanup.organize_imports=true
+sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_with_declaring_class=true
+sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
+sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_type_arguments=true
+sp_cleanup.remove_trailing_whitespaces=true
+sp_cleanup.remove_trailing_whitespaces_all=true
+sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=true
+sp_cleanup.remove_unused_imports=true
+sp_cleanup.remove_unused_local_variables=false
+sp_cleanup.remove_unused_private_fields=true
+sp_cleanup.remove_unused_private_members=false
+sp_cleanup.remove_unused_private_methods=true
+sp_cleanup.remove_unused_private_types=true
+sp_cleanup.sort_members=false
+sp_cleanup.sort_members_all=false
+sp_cleanup.use_anonymous_class_creation=false
+sp_cleanup.use_blocks=false
+sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_lambda=true
+sp_cleanup.use_parentheses_in_expressions=true
+sp_cleanup.use_this_for_non_static_field_access=false
+sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+sp_cleanup.use_this_for_non_static_method_access=false
+sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
diff --git a/tests/org.eclipse.ease.ui.completions.java.test/META-INF/MANIFEST.MF b/tests/org.eclipse.ease.ui.completions.java.test/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..f5cf2d3
--- /dev/null
+++ b/tests/org.eclipse.ease.ui.completions.java.test/META-INF/MANIFEST.MF
@@ -0,0 +1,12 @@
+Manifest-Version: 1.0

+Bundle-ManifestVersion: 2

+Bundle-Name: Java Code Completion Support Unit Tests

+Bundle-SymbolicName: org.eclipse.ease.ui.completions.java.test

+Automatic-Module-Name: org.eclipse.ease.ui.completions.java.test

+Bundle-Version: 0.9.0.qualifier

+Bundle-Vendor: Eclipse.org

+Bundle-RequiredExecutionEnvironment: JavaSE-1.8

+Fragment-Host: org.eclipse.ease.ui.completions.java

+Require-Bundle: org.junit.jupiter.api,

+ org.mockito,

+ org.junit.jupiter.params

diff --git a/tests/org.eclipse.ease.ui.completions.java.test/build.properties b/tests/org.eclipse.ease.ui.completions.java.test/build.properties
new file mode 100644
index 0000000..3f1d08a
--- /dev/null
+++ b/tests/org.eclipse.ease.ui.completions.java.test/build.properties
@@ -0,0 +1,3 @@
+source.. = src/

+bin.includes = .,\

+               META-INF/
\ No newline at end of file
diff --git a/tests/org.eclipse.ease.ui.completions.java.test/pom.xml b/tests/org.eclipse.ease.ui.completions.java.test/pom.xml
new file mode 100644
index 0000000..bfc8c47
--- /dev/null
+++ b/tests/org.eclipse.ease.ui.completions.java.test/pom.xml
@@ -0,0 +1,15 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0"

+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

+

+	<modelVersion>4.0.0</modelVersion>

+	<artifactId>org.eclipse.ease.ui.completions.java.test</artifactId>

+	<packaging>eclipse-test-plugin</packaging>

+	

+	<parent>

+		<groupId>org.eclipse.ease</groupId>

+		<artifactId>org.eclipse.ease.core.tests</artifactId>

+		<version>0.9.0-SNAPSHOT</version>

+		<relativePath>..</relativePath>

+	</parent>

+</project>

diff --git a/tests/org.eclipse.ease.ui.completions.java.test/src/org/eclipse/ease/ui/completions/java/provider/JavaClassCompletionProviderTest.java b/tests/org.eclipse.ease.ui.completions.java.test/src/org/eclipse/ease/ui/completions/java/provider/JavaClassCompletionProviderTest.java
new file mode 100644
index 0000000..4b55dee
--- /dev/null
+++ b/tests/org.eclipse.ease.ui.completions.java.test/src/org/eclipse/ease/ui/completions/java/provider/JavaClassCompletionProviderTest.java
@@ -0,0 +1,91 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License_Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.ui.completions.java.provider;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Collection;
+
+import org.eclipse.ease.ui.completion.BasicContext;
+import org.eclipse.ease.ui.completion.ScriptCompletionProposal;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+public class JavaClassCompletionProviderTest {
+
+	@BeforeAll
+	public static void beforeAll() {
+		// preload java packages
+
+		while (!JavaResources.getInstance().getPackages().contains("java.io")) {
+			try {
+				Thread.sleep(300);
+			} catch (final InterruptedException e) {
+			}
+		}
+	}
+
+	private JavaClassCompletionProvider fProvider;
+
+	@BeforeEach
+	public void beforeEach() {
+		fProvider = new JavaClassCompletionProvider();
+	}
+
+	@ParameterizedTest(name = "for context ''{0}''")
+	@DisplayName("isActive() = true")
+	@ValueSource(strings = { "Fil", "Foo", "java.io", "java.io.", "java.io.F", "java.io.File" })
+	public void isActive_equals_true(String input) {
+		assertTrue(fProvider.isActive(createContext(input)));
+	}
+
+	@ParameterizedTest(name = "for context ''{0}''")
+	@DisplayName("isActive() = false")
+	@ValueSource(strings = { "Fi", "java." })
+	public void isActive_equals_false(String input) {
+		assertFalse(fProvider.isActive(createContext(input)));
+	}
+
+	@Test
+	@DisplayName("prepareProposals() contains 'FileReader' for package context")
+	public void prepareProposals_instance_contains_class_for_package_context() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider.getProposals(createContext("java.io."));
+
+		final ScriptCompletionProposal proposal = findProposal(proposals, "FileReader");
+		assertNotNull(proposal);
+	}
+
+	@Test
+	@DisplayName("prepareProposals() contains 'FileReader' for global context")
+	public void prepareProposals_instance_contains_class_for_global_context() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider.getProposals(createContext("File"));
+
+		final ScriptCompletionProposal proposal = findProposal(proposals, "FileReader");
+		assertNotNull(proposal);
+	}
+
+	private ScriptCompletionProposal findProposal(Collection<ScriptCompletionProposal> proposals, String displayString) {
+		return proposals.stream().filter(p -> p.getDisplayString().startsWith(displayString)).findFirst().orElseGet(() -> null);
+	}
+
+	private BasicContext createContext(String input) {
+		return new BasicContext(null, null, input, input.length());
+	}
+}
diff --git a/tests/org.eclipse.ease.ui.completions.java.test/src/org/eclipse/ease/ui/completions/java/provider/JavaMethodCompletionProviderTest.java b/tests/org.eclipse.ease.ui.completions.java.test/src/org/eclipse/ease/ui/completions/java/provider/JavaMethodCompletionProviderTest.java
new file mode 100644
index 0000000..e7488ea
--- /dev/null
+++ b/tests/org.eclipse.ease.ui.completions.java.test/src/org/eclipse/ease/ui/completions/java/provider/JavaMethodCompletionProviderTest.java
@@ -0,0 +1,116 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License_Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.ui.completions.java.provider;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Collection;
+
+import org.eclipse.ease.ui.completion.BasicContext;
+import org.eclipse.ease.ui.completion.ScriptCompletionProposal;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+public class JavaMethodCompletionProviderTest {
+
+	@ParameterizedTest(name = "for context ''{0}''")
+	@DisplayName("isActive() = true")
+	@ValueSource(strings = { "java.io.File()", "java.io.File().", "java.io.File().exis", "java.io.File", "java.io.File.", "java.io.File.exis" })
+	public void isActive_equals_true(String input) {
+		assertTrue(new JavaMethodCompletionProvider().isActive(createContext(input)));
+	}
+
+	@ParameterizedTest(name = "for context ''{0}''")
+	@DisplayName("isActive() = false")
+	@ValueSource(strings = { "java.io", "java.io.File(", "File()" })
+	public void isActive_equals_false(String input) {
+		assertFalse(new JavaMethodCompletionProvider().isActive(createContext(input)));
+	}
+
+	@Test
+	@DisplayName("prepareProposals() instance contains instance methods")
+	public void prepareProposals_instance_contains_instance_methods() {
+		final Collection<ScriptCompletionProposal> proposals = new JavaMethodCompletionProvider().getProposals(createContext("java.io.File()."));
+
+		final ScriptCompletionProposal proposal = findProposal(proposals, "exists()");
+		assertNotNull(proposal);
+	}
+
+	@Test
+	@DisplayName("prepareProposals() instance does not contain static methods")
+	public void prepareProposals_instance_does_not_contain_static_methods() {
+		final Collection<ScriptCompletionProposal> proposals = new JavaMethodCompletionProvider().getProposals(createContext("java.io.File()."));
+
+		final ScriptCompletionProposal proposal = findProposal(proposals, "createTempFile(");
+		assertNull(proposal);
+	}
+
+	@Test
+	@DisplayName("prepareProposals() class contains static methods")
+	public void prepareProposals_class_contains_static_methods() {
+		final Collection<ScriptCompletionProposal> proposals = new JavaMethodCompletionProvider().getProposals(createContext("java.io.File."));
+
+		final ScriptCompletionProposal proposal = findProposal(proposals, "createTempFile(");
+		assertNotNull(proposal);
+	}
+
+	@Test
+	@DisplayName("prepareProposals() class does not contain instance methods")
+	public void prepareProposals_class_does_not_contain_isntance_methods() {
+		final Collection<ScriptCompletionProposal> proposals = new JavaMethodCompletionProvider().getProposals(createContext("java.io.File."));
+
+		final ScriptCompletionProposal proposal = findProposal(proposals, "exists()");
+		assertNull(proposal);
+	}
+
+	@Test
+	@DisplayName("prepareProposals() instance does not contain static fields")
+	public void prepareProposals_instance_does_not_contain_static_fields() {
+		final Collection<ScriptCompletionProposal> proposals = new JavaMethodCompletionProvider().getProposals(createContext("java.io.File()."));
+
+		final ScriptCompletionProposal proposal = findProposal(proposals, "pathSeparator");
+		assertNull(proposal);
+	}
+
+	@Test
+	@DisplayName("prepareProposals() class contains static fields")
+	public void prepareProposals_class_contains_static_fields() {
+		final Collection<ScriptCompletionProposal> proposals = new JavaMethodCompletionProvider().getProposals(createContext("java.io.File."));
+
+		final ScriptCompletionProposal proposal = findProposal(proposals, "pathSeparator");
+		assertNotNull(proposal);
+	}
+
+	@Test
+	@DisplayName("prepareProposals() instance contains method with filter")
+	public void prepareProposals_instance_contains_method_with_filter() {
+		final Collection<ScriptCompletionProposal> proposals = new JavaMethodCompletionProvider().getProposals(createContext("java.io.File().exi"));
+
+		final ScriptCompletionProposal proposal = findProposal(proposals, "exists()");
+		assertNotNull(proposal);
+	}
+
+	private ScriptCompletionProposal findProposal(Collection<ScriptCompletionProposal> proposals, String displayString) {
+		return proposals.stream().filter(p -> p.getDisplayString().startsWith(displayString)).findFirst().orElseGet(() -> null);
+	}
+
+	private BasicContext createContext(String input) {
+		return new BasicContext(null, null, input, input.length());
+	}
+}
diff --git a/tests/org.eclipse.ease.ui.completions.java.test/src/org/eclipse/ease/ui/completions/java/provider/JavaPackagesCompletionProviderTest.java b/tests/org.eclipse.ease.ui.completions.java.test/src/org/eclipse/ease/ui/completions/java/provider/JavaPackagesCompletionProviderTest.java
new file mode 100644
index 0000000..3fb0137
--- /dev/null
+++ b/tests/org.eclipse.ease.ui.completions.java.test/src/org/eclipse/ease/ui/completions/java/provider/JavaPackagesCompletionProviderTest.java
@@ -0,0 +1,142 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License_Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.ui.completions.java.provider;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Collection;
+
+import org.eclipse.ease.ui.completion.BasicContext;
+import org.eclipse.ease.ui.completion.ScriptCompletionProposal;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+public class JavaPackagesCompletionProviderTest {
+
+	@BeforeAll
+	public static void beforeAll() {
+		// preload java packages
+
+		while (!JavaResources.getInstance().getPackages().contains("java.io")) {
+			try {
+				Thread.sleep(300);
+			} catch (final InterruptedException e) {
+			}
+		}
+	}
+
+	private JavaPackagesCompletionProvider fProvider;
+
+	@BeforeEach
+	public void beforeEach() {
+		fProvider = new JavaPackagesCompletionProvider();
+	}
+
+	@ParameterizedTest(name = "for context ''{0}''")
+	@DisplayName("isActive() = true")
+	@ValueSource(strings = { "", "j", "java", "java.", "java.i", "java.lang.ref", "foo(", "foo(co", "java.io.File(", "java.io.File(jav",
+			"java.io.File().exists(", "java.io.File().exists(org", "new java", "f=java" })
+	public void isActive_equals_true(String input) {
+		assertTrue(fProvider.isActive(createContext(input)));
+	}
+
+	@ParameterizedTest(name = "for context ''{0}''")
+	@DisplayName("isActive() = false")
+	@ValueSource(strings = { "foo()", "foo().bar" })
+	public void isActive_equals_false(String input) {
+		assertFalse(fProvider.isActive(createContext(input)));
+	}
+
+	@ParameterizedTest(name = "''{0}''")
+	@DisplayName("prepareProposals() contains root package")
+	@ValueSource(strings = { "java", "com", "org" })
+	public void prepareProposals_contains_root_package(String rootPackage) {
+		final Collection<ScriptCompletionProposal> proposals = fProvider.getProposals(createContext(""));
+
+		final ScriptCompletionProposal proposal = findProposal(proposals, rootPackage);
+		assertNotNull(proposal);
+	}
+
+	@Test
+	@DisplayName("prepareProposals() contains 'java.io'")
+	public void prepareProposals_instance_contains_sub_package() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider.getProposals(createContext("java."));
+
+		final ScriptCompletionProposal proposal = findProposal(proposals, "java.io");
+		assertNotNull(proposal);
+	}
+
+	@Test
+	@DisplayName("prepareProposals() contains 'java.lang.reflect'")
+	public void prepareProposals_instance_contains_3rd_level() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider.getProposals(createContext("java.lang."));
+
+		final ScriptCompletionProposal proposal = findProposal(proposals, "java.lang.reflect");
+		assertNotNull(proposal);
+	}
+
+	@Test
+	@DisplayName("prepareProposals() contains 'java' after 'new' keyword")
+	public void prepareProposals_instance_contains_root_package_after_new_keyword() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider.getProposals(createContext("new java"));
+
+		final ScriptCompletionProposal proposal = findProposal(proposals, "java");
+		assertNotNull(proposal);
+	}
+
+	@Test
+	@DisplayName("proposal 'java' replaces filter")
+	public void proposal_java_replaces_filter() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider.getProposals(createContext("ja"));
+
+		final ScriptCompletionProposal proposal = findProposal(proposals, "java");
+		assertEquals("java.", proposal.getContent());
+		assertEquals(5, proposal.getCursorPosition());
+	}
+
+	@Test
+	@DisplayName("proposal 'java' appends to prefix")
+	public void proposal_java_appends_to_prefix() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider.getProposals(createContext("new ja"));
+
+		final ScriptCompletionProposal proposal = findProposal(proposals, "java");
+		assertEquals("new java.", proposal.getContent());
+		assertEquals(9, proposal.getCursorPosition());
+	}
+
+	@Test
+	@DisplayName("proposal 'java' preserves suffix")
+	public void proposal_java_preserves_suffix() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider.getProposals(new BasicContext(null, null, "jaio.File", 2));
+
+		final ScriptCompletionProposal proposal = findProposal(proposals, "java");
+		assertEquals("java.io.File", proposal.getContent());
+		assertEquals(5, proposal.getCursorPosition());
+	}
+
+	private ScriptCompletionProposal findProposal(Collection<ScriptCompletionProposal> proposals, String displayString) {
+		return proposals.stream().filter(p -> p.getDisplayString().startsWith(displayString)).findFirst().orElseGet(() -> null);
+	}
+
+	private BasicContext createContext(String input) {
+		return new BasicContext(null, null, input, input.length());
+	}
+}
diff --git a/tests/org.eclipse.ease.ui.completions.java.test/src/org/eclipse/ease/ui/completions/java/provider/JavaResourcesTest.java b/tests/org.eclipse.ease.ui.completions.java.test/src/org/eclipse/ease/ui/completions/java/provider/JavaResourcesTest.java
new file mode 100644
index 0000000..faab457
--- /dev/null
+++ b/tests/org.eclipse.ease.ui.completions.java.test/src/org/eclipse/ease/ui/completions/java/provider/JavaResourcesTest.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License_Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.ui.completions.java.provider;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+public class JavaResourcesTest {
+
+	@BeforeAll
+	public static void beforeAll() {
+		// preload java packages
+
+		while (!JavaResources.getInstance().getPackages().contains("java.io")) {
+			try {
+				Thread.sleep(300);
+			} catch (final InterruptedException e) {
+			}
+		}
+	}
+
+	@Test
+	@DisplayName("singleton exists")
+	public void singleton_exists() {
+		assertNotNull(JavaResources.getInstance());
+	}
+
+	@Test
+	@DisplayName("getClasses() is populated")
+	public void getClasses_is_populated() {
+		assertFalse(JavaResources.getInstance().getClasses().isEmpty());
+	}
+
+	@Test
+	@DisplayName("getPackages() is populated")
+	public void getPackages_is_populated() {
+		assertFalse(JavaResources.getInstance().getPackages().isEmpty());
+	}
+
+	@Test
+	@DisplayName("getClasses(<filter>) is populated")
+	public void getClasses_with_filter_is_populated() {
+		assertFalse(JavaResources.getInstance().getClasses("java.io").isEmpty());
+	}
+
+	@Test
+	@DisplayName("getClasses(java.io) contains File class")
+	public void getClasses_with_filter_contains_File_class() {
+		assertTrue(JavaResources.getInstance().getClasses("java.io").contains("File"));
+	}
+}
diff --git a/tests/org.eclipse.ease.ui.test/.classpath b/tests/org.eclipse.ease.ui.test/.classpath
index f193818..a621c21 100644
--- a/tests/org.eclipse.ease.ui.test/.classpath
+++ b/tests/org.eclipse.ease.ui.test/.classpath
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
 	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
 	<classpathentry kind="src" path="src">
 		<attributes>
diff --git a/tests/org.eclipse.ease.ui.test/.settings/org.eclipse.jdt.core.prefs b/tests/org.eclipse.ease.ui.test/.settings/org.eclipse.jdt.core.prefs
index 64e8545..7004b85 100644
--- a/tests/org.eclipse.ease.ui.test/.settings/org.eclipse.jdt.core.prefs
+++ b/tests/org.eclipse.ease.ui.test/.settings/org.eclipse.jdt.core.prefs
@@ -11,17 +11,21 @@
 org.eclipse.jdt.core.codeComplete.staticFinalFieldSuffixes=
 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
 org.eclipse.jdt.core.compiler.codegen.methodParameters=generate
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=11
 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.compliance=11
 org.eclipse.jdt.core.compiler.debug.lineNumber=generate
 org.eclipse.jdt.core.compiler.debug.localVariable=generate
 org.eclipse.jdt.core.compiler.debug.sourceFile=generate
 org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
 org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.source=1.8
+org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
+org.eclipse.jdt.core.compiler.release=enabled
+org.eclipse.jdt.core.compiler.source=11
 org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647
 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
+org.eclipse.jdt.core.formatter.alignment_for_additive_operator=16
 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
@@ -29,19 +33,22 @@
 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
 org.eclipse.jdt.core.formatter.alignment_for_assignment=0
-org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator=16
 org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
 org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
 org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
 org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
 org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header=0
+org.eclipse.jdt.core.formatter.alignment_for_logical_operator=16
 org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
 org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
+org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator=16
 org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references=0
 org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
 org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
 org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
 org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_string_concatenation=16
 org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
 org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
 org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
@@ -130,11 +137,12 @@
 org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
 org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
 org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_after_additive_operator=insert
 org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
 org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
 org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
 org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
-org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator=insert
 org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
 org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
 org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
@@ -165,6 +173,8 @@
 org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
 org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
 org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert
+org.eclipse.jdt.core.formatter.insert_space_after_logical_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator=insert
 org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
 org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
 org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
@@ -189,13 +199,17 @@
 org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
 org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
 org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_relational_operator=insert
 org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
 org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert
+org.eclipse.jdt.core.formatter.insert_space_after_shift_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation=insert
 org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_additive_operator=insert
 org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
 org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
 org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
-org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_bitwise_operator=insert
 org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
 org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
 org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
@@ -243,6 +257,8 @@
 org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
 org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
 org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert
+org.eclipse.jdt.core.formatter.insert_space_before_logical_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator=insert
 org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
 org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
 org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
@@ -279,9 +295,12 @@
 org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
 org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
 org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_relational_operator=insert
 org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
 org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
 org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_shift_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation=insert
 org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
 org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
 org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
@@ -317,9 +336,13 @@
 org.eclipse.jdt.core.formatter.tabulation.size=4
 org.eclipse.jdt.core.formatter.use_on_off_tags=true
 org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
+org.eclipse.jdt.core.formatter.wrap_before_additive_operator=true
 org.eclipse.jdt.core.formatter.wrap_before_assignment_operator=false
-org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator=true
 org.eclipse.jdt.core.formatter.wrap_before_conditional_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_logical_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator=true
 org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
+org.eclipse.jdt.core.formatter.wrap_before_string_concatenation=true
 org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
 org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter
diff --git a/tests/org.eclipse.ease.ui.test/META-INF/MANIFEST.MF b/tests/org.eclipse.ease.ui.test/META-INF/MANIFEST.MF
index 48ec14d..989f8d4 100644
--- a/tests/org.eclipse.ease.ui.test/META-INF/MANIFEST.MF
+++ b/tests/org.eclipse.ease.ui.test/META-INF/MANIFEST.MF
@@ -4,12 +4,13 @@
 Bundle-SymbolicName: org.eclipse.ease.ui.test;singleton:=true
 Bundle-Version: 0.9.0.qualifier
 Fragment-Host: org.eclipse.ease.ui
-Bundle-RequiredExecutionEnvironment: JavaSE-1.8
-Require-Bundle: org.junit.jupiter.api;bundle-version="5.5.0",
+Bundle-RequiredExecutionEnvironment: JavaSE-11
+Require-Bundle: org.junit.jupiter.api,
  org.eclipse.help,
  org.eclipse.help.ui,
  org.eclipse.help.webapp,
  org.eclipse.equinox.http.jetty,
  org.eclipse.ui.browser,
- org.mockito
+ org.mockito,
+ org.junit.jupiter.params
 Automatic-Module-Name: org.eclipse.ease.ui.test
diff --git a/tests/org.eclipse.ease.ui.test/fragment.xml b/tests/org.eclipse.ease.ui.test/fragment.xml
index 97f910b..cdd3fe4 100644
--- a/tests/org.eclipse.ease.ui.test/fragment.xml
+++ b/tests/org.eclipse.ease.ui.test/fragment.xml
@@ -9,6 +9,23 @@
             name="Sample"

             visible="true">

       </module>

+      <module

+            class="org.eclipse.ease.ui.test.RootModule"

+            id="org.eclipse.ease.ui.test.rootModule"

+            name="Test Root"

+            visible="true">

+      </module>

+      <category

+            id="org.eclipse.ease.ui.test.category"

+            name="Testing">

+      </category>

+      <module

+            category="org.eclipse.ease.ui.test.category"

+            class="org.eclipse.ease.ui.test.SubModule"

+            id="org.eclipse.ease.ui.test.subModule"

+            name="Test Sub"

+            visible="true">

+      </module>

    </extension>

    <extension

          point="org.eclipse.help.toc">

diff --git a/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/BasicContextTest.java b/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/BasicContextTest.java
new file mode 100644
index 0000000..6d3f713
--- /dev/null
+++ b/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/BasicContextTest.java
@@ -0,0 +1,175 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License_Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.ui.completion;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.ease.IScriptEngine;
+import org.eclipse.ease.modules.EnvironmentModule;
+import org.eclipse.ease.modules.IEnvironment;
+import org.eclipse.ease.modules.ModuleDefinition;
+import org.eclipse.ease.service.EngineDescription;
+import org.eclipse.ease.service.ScriptType;
+import org.eclipse.ease.ui.test.SubModule;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+public class BasicContextTest {
+
+	@Test
+	@DisplayName("getFilter() is empty for 'java.io.File()'")
+	public void getFilter_is_empty() {
+		final BasicContext context = createContext("java.io.File()");
+
+		assertEquals("", context.getFilter());
+	}
+
+	@Test
+	@DisplayName("getFilter() is empty for ''")
+	public void getFilter_is_empty_for_empty_input() {
+		final BasicContext context = createContext("");
+
+		assertEquals("", context.getFilter());
+	}
+
+	@Test
+	@DisplayName("getFilter() = 12 for 'java.io.File().exists(12'")
+	public void getFilter_contains_parameter_prefix() {
+		final BasicContext context = createContext("java.io.File().exists(12");
+
+		assertEquals("12", context.getFilter());
+	}
+
+	@Test
+	@DisplayName("getFilter() = 123 for 'java.io.File().exists(\"123'")
+	public void getFilter_removes_string_literal_start() {
+		final BasicContext context = createContext("java.io.File().exists(\"123");
+
+		assertEquals("123", context.getFilter());
+	}
+
+	@Test
+	@DisplayName("getFilter() = 123 abc for 'java.io.File().exists(\"123 abc'")
+	public void getFilter_keeps_spaces_in_string_literal() {
+		final BasicContext context = createContext("java.io.File().exists(\"123 abc");
+
+		assertEquals("123 abc", context.getFilter());
+	}
+
+	@Test
+	@DisplayName("getFilterToken() is empty for ''")
+	public void getFilterToken_is_empty_for_empty_input() {
+		final BasicContext context = createContext("");
+
+		assertEquals("", context.getFilterToken());
+	}
+
+	@Test
+	@DisplayName("getFilterToken() = 12 for 'java.io.File().exists(12'")
+	public void getFilterToken_contains_parameter_prefix() {
+		final BasicContext context = createContext("java.io.File().exists(12");
+
+		assertEquals("12", context.getFilterToken());
+	}
+
+	@Test
+	@DisplayName("getFilterToken() = \"123 for 'java.io.File().exists(\"123'")
+	public void getFilterToken_removes_string_literal_start() {
+		final BasicContext context = createContext("java.io.File().exists(\"123");
+
+		assertEquals("\"123", context.getFilterToken());
+	}
+
+	@Test
+	@DisplayName("getScriptEngine() returns engine")
+	public void getScriptEngine_returns_engine() {
+		final IScriptEngine scriptEngine = createEngine();
+
+		assertEquals(scriptEngine, new BasicContext(scriptEngine, "", 0).getScriptEngine());
+	}
+
+	@Test
+	@DisplayName("getLoadedModules() contains environment on resource")
+	public void getLoadedModules_contains_environment_on_resource() {
+
+		final IScriptEngine scriptEngine = createEngine();
+		when(scriptEngine.getVariables()).thenReturn(Collections.emptyMap());
+
+		final List<ModuleDefinition> modules = createContext("").getLoadedModules();
+		assertEquals(1, modules.size());
+		assertEquals(EnvironmentModule.MODULE_NAME, modules.get(0).getPath().toString());
+	}
+
+	@Test
+	@DisplayName("getLoadedModules() does not load environment twice on resource")
+	public void getLoadedModules_does_not_contain_environment_twice_on_resource() {
+
+		final IScriptEngine scriptEngine = createEngine();
+		when(scriptEngine.getVariables()).thenReturn(Collections.emptyMap());
+
+		final List<ModuleDefinition> modules = createContext("loadModule(\"/Environment\")\n").getLoadedModules();
+		assertEquals(1, modules.size());
+		assertEquals(EnvironmentModule.MODULE_NAME, modules.get(0).getPath().toString());
+	}
+
+	@Test
+	@DisplayName("getLoadedModules() detects loadModule() commands")
+	public void getLoadedModules_detects_loadModule_commands() {
+
+		final IScriptEngine scriptEngine = createEngine();
+		when(scriptEngine.getVariables()).thenReturn(Collections.emptyMap());
+
+		final List<ModuleDefinition> modules = createContext("loadModule(\"/Testing/Test Sub\")\n").getLoadedModules();
+		assertEquals(2, modules.size());
+		assertEquals("/Testing/Test Sub", modules.get(0).getPath().toString());
+		assertEquals(EnvironmentModule.MODULE_NAME, modules.get(1).getPath().toString());
+	}
+
+	@Test
+	@DisplayName("getLoadedModules() detects modules from ScriptEngine")
+	public void getLoadedModules_detects_modules_from_ScriptEngine() {
+
+		final Map<String, Object> variables = new HashMap<>();
+		variables.put(IEnvironment.MODULE_PREFIX + "_one", new SubModule());
+
+		final IScriptEngine scriptEngine = createEngine();
+		when(scriptEngine.getVariables()).thenReturn(variables);
+
+		final List<ModuleDefinition> modules = new BasicContext(scriptEngine, "", 0).getLoadedModules();
+		assertEquals(1, modules.size());
+		assertEquals("/Testing/Test Sub", modules.get(0).getPath().toString());
+	}
+
+	private IScriptEngine createEngine() {
+		final EngineDescription engineDescription = mock(EngineDescription.class);
+		when(engineDescription.getSupportedScriptTypes()).thenReturn(Arrays.asList(new ScriptType(null)));
+
+		final IScriptEngine engine = mock(IScriptEngine.class);
+		when(engine.getDescription()).thenReturn(engineDescription);
+
+		return engine;
+	}
+
+	private BasicContext createContext(String input) {
+		return new BasicContext(null, null, input, input.length());
+	}
+}
diff --git a/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/CodeCompletionAggregatorTest.java b/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/CodeCompletionAggregatorTest.java
new file mode 100644
index 0000000..c8f756f
--- /dev/null
+++ b/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/CodeCompletionAggregatorTest.java
@@ -0,0 +1,104 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License_Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.ui.completion;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.ease.ICompletionContext;
+import org.eclipse.ease.service.ScriptType;
+import org.eclipse.ease.ui.completion.provider.ICompletionProvider;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
+
+public class CodeCompletionAggregatorTest {
+
+	private static List<ScriptCompletionProposal> createProposal(String displayString) {
+		return Arrays.asList(new ScriptCompletionProposal(null, displayString, null, null, 0, null));
+	}
+
+	private ScriptType fScriptType;
+	private CodeCompletionAggregator fAggregator;
+
+	@BeforeEach
+	public void beforeEach() {
+		fScriptType = mock(ScriptType.class);
+		when(fScriptType.getName()).thenReturn("TestEngine");
+
+		fAggregator = new CodeCompletionAggregator(null, fScriptType);
+	}
+
+	@Test
+	@DisplayName("getProposals() contains proposal from local provider")
+	public void getProposals_contains_proposal_from_local_provider() {
+		final ICompletionProvider localProvider = mock(ICompletionProvider.class);
+		when(localProvider.getProposals(ArgumentMatchers.any())).thenReturn(createProposal("test1"));
+		when(localProvider.isActive(ArgumentMatchers.any())).thenReturn(true);
+
+		fAggregator.addCompletionProvider(localProvider);
+
+		final List<ScriptCompletionProposal> proposals = fAggregator.getProposals("foo", 0, null);
+		assertTrue(proposals.stream().filter(p -> "test1".equals(p.getDisplayString())).findAny().isPresent());
+	}
+
+	@Test
+	@DisplayName("context for '|'")
+	public void context_for_empty_input() {
+		final ICompletionContext context = captureContext("");
+
+		assertTrue(context.getTokens().isEmpty());
+	}
+
+	@Test
+	@DisplayName("context for 'java.|'")
+	public void context_for_root_package() {
+		final ICompletionContext context = captureContext("java.");
+
+		assertArrayEquals(new Object[] { "java", "." }, context.getTokens().toArray());
+	}
+
+	@Test
+	@DisplayName("context for 'java.|another'")
+	public void context_for_root_package_with_suffix() {
+		final ICompletionContext context = captureContext("java.another", 5);
+
+		assertArrayEquals(new Object[] { "java", "." }, context.getTokens().toArray());
+	}
+
+	private ICompletionContext captureContext(String input) {
+		return captureContext(input, input.length());
+	}
+
+	private ICompletionContext captureContext(String input, int offset) {
+		final ICompletionProvider localProvider = mock(ICompletionProvider.class);
+
+		final ArgumentCaptor<ICompletionContext> contextCaptor = ArgumentCaptor.forClass(ICompletionContext.class);
+		fAggregator.addCompletionProvider(localProvider);
+
+		fAggregator.getProposals(input, offset, null);
+
+		verify(localProvider).isActive(contextCaptor.capture());
+
+		return contextCaptor.getValue();
+	}
+}
diff --git a/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/CompletionContextTest.java b/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/CompletionContextTest.java
deleted file mode 100644
index d4fa0b7..0000000
--- a/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/CompletionContextTest.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Christian Pontesegger and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v2.0
- * which accompanies this distribution, and is available at
- * https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License_Identifier: EPL-2.0
- *
- * Contributors:
- *     Christian Pontesegger - initial API and implementation
- *******************************************************************************/
-
-package org.eclipse.ease.ui.completion;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-import java.io.File;
-
-import org.eclipse.ease.ICompletionContext.Type;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-public class CompletionContextTest {
-
-	private CompletionContext fContext;
-
-	@BeforeEach
-	public void setUp() throws Exception {
-		fContext = new CompletionContext(null, null) {
-
-			@Override
-			protected boolean isLiteral(final char candidate) {
-				return (candidate == '"') || (candidate == '\'');
-			}
-		};
-	}
-
-	@Test
-	public void replaceSimpleStringLiterals() {
-		assertEquals("print('')", fContext.replaceStringLiterals("print('')"));
-		assertEquals("print('')", fContext.replaceStringLiterals("print('Hello world')"));
-		assertEquals("print('')", fContext.replaceStringLiterals("print('Hello \" world')"));
-
-		assertEquals("print(\"\")", fContext.replaceStringLiterals("print(\"\")"));
-		assertEquals("print(\"\")", fContext.replaceStringLiterals("print(\"Hello world\")"));
-		assertEquals("print(\"\")", fContext.replaceStringLiterals("print(\"Hello ' world\")"));
-	}
-
-	@Test
-	public void replaceEscapedStringLiterals() {
-		assertEquals("print('')", fContext.replaceStringLiterals("print('Hello \\'world')"));
-		assertEquals("print(\"\")", fContext.replaceStringLiterals("print(\"Hello \\\"world\")"));
-	}
-
-	@Test
-	public void replaceMultipleStringLiterals() {
-		assertEquals("print('', \"\")", fContext.replaceStringLiterals("print('', \"\")"));
-		assertEquals("print('', \"\")", fContext.replaceStringLiterals("print('Hello world', \"Hello world\")"));
-	}
-
-	@Test
-	public void simplifyCalls() {
-		fContext.calculateContext(null, "create(java.lang.String('').is", 22, 0);
-		assertEquals("is", fContext.getFilter());
-	}
-
-	@Test
-	public void resolveStaticClass() {
-		fContext.calculateContext(null, "java.io.File.", 0, 0);
-		assertEquals("", fContext.getFilter());
-		assertEquals(File.class, fContext.getReferredClazz());
-		assertEquals(Type.STATIC_CLASS, fContext.getType());
-	}
-
-	@Test
-	public void resolveStaticClassWithFilter() {
-		fContext.calculateContext(null, "java.io.File.exi", 0, 0);
-		assertEquals("exi", fContext.getFilter());
-		assertEquals(File.class, fContext.getReferredClazz());
-		assertEquals(Type.STATIC_CLASS, fContext.getType());
-	}
-
-	@Test
-	public void resolveClass() {
-		fContext.calculateContext(null, "new java.io.File().", 0, 0);
-		assertEquals("", fContext.getFilter());
-		assertEquals(File.class, fContext.getReferredClazz());
-		assertEquals(Type.CLASS_INSTANCE, fContext.getType());
-	}
-
-	@Test
-	public void resolveClassWithParameters() {
-		fContext.calculateContext(null, "new java.io.File(\"/some/path\").", 0, 0);
-		assertEquals("", fContext.getFilter());
-		assertEquals(File.class, fContext.getReferredClazz());
-		assertEquals(Type.CLASS_INSTANCE, fContext.getType());
-	}
-
-	@Test
-	public void resolveClassWithFilter() {
-		fContext.calculateContext(null, "new java.io.File(\"/some/path\").exi", 0, 0);
-		assertEquals("exi", fContext.getFilter());
-		assertEquals(File.class, fContext.getReferredClazz());
-		assertEquals(Type.CLASS_INSTANCE, fContext.getType());
-	}
-}
diff --git a/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/ScriptCompletionProposalTest.java b/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/ScriptCompletionProposalTest.java
new file mode 100644
index 0000000..4c11fcc
--- /dev/null
+++ b/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/ScriptCompletionProposalTest.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License_Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.ui.completion;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.eclipse.ease.ICompletionContext;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+public class ScriptCompletionProposalTest {
+
+	@Test
+	@DisplayName("getContent() sets replacement text")
+	public void getContent_sets_replacement_text() {
+		final ScriptCompletionProposal proposal = new ScriptCompletionProposal(createContext(""), "foo", "foo()", null, 0, null);
+
+		assertEquals("foo()", proposal.getContent());
+	}
+
+	@Test
+	@DisplayName("getContent() appends replacement text")
+	public void getContent_appends_replacement_text() {
+		final ScriptCompletionProposal proposal = new ScriptCompletionProposal(createContext("bar."), "foo", "foo()", null, 0, null);
+
+		assertEquals("bar.foo()", proposal.getContent());
+	}
+
+	@Test
+	@DisplayName("getContent() inserts replacement text")
+	public void getContent_inserts_replacement_text() {
+		final ScriptCompletionProposal proposal = new ScriptCompletionProposal(new BasicContext(null, null, "AAA.BBB", 4), "foo", "foo()", null, 0, null);
+
+		assertEquals("AAA.foo()BBB", proposal.getContent());
+	}
+
+	@Test
+	@DisplayName("getCursorPosition() = end of set text")
+	public void getCursorPosition_equals_end_of_set_text() {
+		final ScriptCompletionProposal proposal = new ScriptCompletionProposal(createContext(""), "foo", "foo()", null, 0, null);
+
+		assertEquals(5, proposal.getCursorPosition());
+	}
+
+	@Test
+	@DisplayName("getCursorPosition() = end of appended text")
+	public void getCursorPosition_equals_end_of_appended_text() {
+		final ScriptCompletionProposal proposal = new ScriptCompletionProposal(createContext("bar."), "foo", "foo()", null, 0, null);
+
+		assertEquals(9, proposal.getCursorPosition());
+	}
+
+	@Test
+	@DisplayName("getCursorPosition() = end of inserted text")
+	public void getCursorPosition_equals_end_of_inserted_text() {
+		final ScriptCompletionProposal proposal = new ScriptCompletionProposal(new BasicContext(null, null, "AAA.BBB", 4), "foo", "foo()", null, 0, null);
+
+		assertEquals(9, proposal.getCursorPosition());
+	}
+
+	private ICompletionContext createContext(String input) {
+		return new BasicContext(null, null, input, input.length());
+	}
+}
diff --git a/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/provider/AbstractCompletionProviderTest.java b/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/provider/AbstractCompletionProviderTest.java
new file mode 100644
index 0000000..fe1d28d
--- /dev/null
+++ b/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/provider/AbstractCompletionProviderTest.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License_Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.ui.completion.provider;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.eclipse.ease.ICompletionContext;
+import org.eclipse.ease.ui.completion.BasicContext;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+public class AbstractCompletionProviderTest {
+
+	@ParameterizedTest(name = "for ''{0}''")
+	@DisplayName("isMethodParameter(foo, 0) = true")
+	@ValueSource(strings = { "foo(", "com.test.foo(" })
+	public void isMethodParameter_equals_true(String input) {
+		assertTrue(new TestCompletionProvider("foo", 0).isActive(new BasicContext(null, null, input, input.length())));
+	}
+
+	@ParameterizedTest(name = "for ''{0}''")
+	@DisplayName("isMethodParameter(foo, 0) = false")
+	@ValueSource(strings = { "bar(", "foo", "foo(," })
+	public void isMethodParameter_equals_false(String input) {
+		assertFalse(new TestCompletionProvider("foo", 0).isActive(new BasicContext(null, null, input, input.length())));
+	}
+
+	@ParameterizedTest(name = "for ''{0}''")
+	@DisplayName("isMethodParameter(foo, 2) = true")
+	@ValueSource(strings = { "foo(a, b, ", "foo(a, b, cpart", "com.test.foo(1,2,", "com.test.foo(1,bar(1,2,3)," })
+	public void isMethodParameter_equals_true_for_parameter_index(String input) {
+		assertTrue(new TestCompletionProvider("foo", 2).isActive(new BasicContext(null, null, input, input.length())));
+	}
+
+	@ParameterizedTest(name = "for ''{0}''")
+	@DisplayName("isMethodParameter(foo, 2) = false")
+	@ValueSource(strings = { "bar(1,2,", "foo", "foo(1,", "foo(1,2,3," })
+	public void isMethodParameter_equals_false_for_parameter_index(String input) {
+		assertFalse(new TestCompletionProvider("foo", 2).isActive(new BasicContext(null, null, input, input.length())));
+	}
+
+	private static final class TestCompletionProvider extends AbstractCompletionProvider {
+
+		private final String fMethodName;
+		private final int fParameterIndex;
+
+		private TestCompletionProvider(String methodName, int parameterIndex) {
+			fMethodName = methodName;
+			fParameterIndex = parameterIndex;
+		}
+
+		@Override
+		public boolean isActive(ICompletionContext context) {
+			return super.isActive(context) && isMethodParameter(context, fMethodName, fParameterIndex);
+		}
+
+		@Override
+		protected void prepareProposals(ICompletionContext context) {
+			// nothing to do
+		}
+	}
+}
diff --git a/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/provider/AbstractFileLocationCompletionProviderTest.java b/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/provider/AbstractFileLocationCompletionProviderTest.java
index 1a197c5..5a78724 100644
--- a/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/provider/AbstractFileLocationCompletionProviderTest.java
+++ b/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/provider/AbstractFileLocationCompletionProviderTest.java
@@ -13,16 +13,12 @@
 
 package org.eclipse.ease.ui.completion.provider;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
 
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.util.Collection;
-import java.util.HashSet;
 
 import org.eclipse.core.resources.IFile;
 import org.eclipse.core.resources.IFolder;
@@ -31,10 +27,11 @@
 import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.ease.ICompletionContext;
+import org.eclipse.ease.ui.completion.BasicContext;
 import org.eclipse.ease.ui.completion.ScriptCompletionProposal;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 
 public class AbstractFileLocationCompletionProviderTest {
@@ -89,7 +86,7 @@
 		if (!fFolder.exists())
 			fFolder.create(0, true, null);
 
-		fFile1 = fFolder.getFile(FILE1_NAME);
+		fFile1 = fProject.getFile(FILE1_NAME);
 		if (!fFile1.exists())
 			fFile1.create(new ByteArrayInputStream(FILE1_CONTENT.getBytes("UTF-8")), false, null);
 
@@ -117,140 +114,157 @@
 		}
 	}
 
-	@Disabled
 	@Test
-	public void checkOperatingSystem() {
-		assertTrue(isLinux() || isWindows(), "Operating system is: " + System.getProperty("os.name"));
+	@DisplayName("getProposals('') contains 'workspace://'")
+	public void getProposals_contains_workspace_root() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider.getProposals(createContext(""));
+		assertNotNull(findProposal(proposals, "workspace://"));
 	}
 
-	@Disabled
 	@Test
-	public void absoluteRootWorkspaceProposals() {
-
-		final ICompletionContext context = mock(ICompletionContext.class);
-		when(context.getOriginalCode()).thenReturn("");
-		when(context.getFilter()).thenReturn("");
-		when(context.getResource()).thenReturn(null);
-
-		final Collection<? extends ScriptCompletionProposal> proposals = fProvider.getProposals(context);
-		final Collection<String> content = extractDisplayedContent(proposals);
-
-		assertTrue(content.contains("workspace://"), "<workspace://> proposal missing");
-		assertTrue(content.contains("file:///"), "<file:///> proposal missing");
-		assertEquals(2, proposals.size());
+	@DisplayName("getProposals('') contains 'file://'")
+	public void getProposals_contains_filesystem_root() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider.getProposals(createContext(""));
+		assertNotNull(findProposal(proposals, "file://"));
 	}
 
-	@Disabled
 	@Test
-	public void workspaceProposals() {
-
-		final ICompletionContext context = mock(ICompletionContext.class);
-		when(context.getOriginalCode()).thenReturn("workspace://");
-		when(context.getFilter()).thenReturn("workspace://");
-		when(context.getResource()).thenReturn(null);
-
-		final Collection<? extends ScriptCompletionProposal> proposals = fProvider.getProposals(context);
-		final Collection<String> content = extractDisplayedContent(proposals);
-
-		assertTrue(content.contains(PROJECT_NAME), "<" + PROJECT_NAME + "> proposal missing");
-		assertEquals(1, proposals.size());
+	@DisplayName("getProposals('') does not contain 'project://' without resource")
+	public void getProposals_does_not_contain_project_root_without_resource() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider.getProposals(createContext(""));
+		assertNull(findProposal(proposals, "project://"));
 	}
 
-	@Disabled
 	@Test
-	public void workspaceProjectProposals() {
-
-		final ICompletionContext context = mock(ICompletionContext.class);
-		when(context.getOriginalCode()).thenReturn("workspace://" + PROJECT_NAME + "/");
-		when(context.getFilter()).thenReturn("workspace://" + PROJECT_NAME + "/");
-		when(context.getResource()).thenReturn(null);
-
-		final Collection<? extends ScriptCompletionProposal> proposals = fProvider.getProposals(context);
-		final Collection<String> content = extractDisplayedContent(proposals);
-
-		assertTrue(content.contains(".."), "<..> proposal missing");
-		assertTrue(content.contains(FOLDER_NAME), "<" + FOLDER_NAME + "> proposal missing");
-		assertTrue(content.contains(".project"), "<.project> proposal missing");
-		assertEquals(3, proposals.size());
+	@DisplayName("getProposals('') does not contain 'project://' with file resource")
+	public void getProposals_does_not_contain_project_root_with_file_resource() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider.getProposals(createContext("", fFsFile1));
+		assertNull(findProposal(proposals, "project://"));
 	}
 
-	@Disabled
 	@Test
-	public void workspaceFolderProposals() {
-
-		final ICompletionContext context = mock(ICompletionContext.class);
-		when(context.getOriginalCode()).thenReturn("workspace://" + PROJECT_NAME + "/" + FOLDER_NAME + "/");
-		when(context.getFilter()).thenReturn("workspace://" + PROJECT_NAME + "/" + FOLDER_NAME + "/");
-		when(context.getResource()).thenReturn(null);
-
-		final Collection<? extends ScriptCompletionProposal> proposals = fProvider.getProposals(context);
-		final Collection<String> content = extractDisplayedContent(proposals);
-
-		assertTrue(content.contains(".."), "<..> proposal missing");
-		assertTrue(content.contains(FILE1_NAME), "<" + FILE1_NAME + "> proposal missing");
-		assertTrue(content.contains(FILE2_NAME), "<" + FILE2_NAME + "> proposal missing");
-		assertEquals(3, proposals.size());
+	@DisplayName("getProposals('') contains 'project://' with workspace resource")
+	public void getProposals_contains_project_root_with_workspace_resource() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider.getProposals(createContext("", fFile1));
+		assertNotNull(findProposal(proposals, "project://"));
 	}
 
-	@Disabled
 	@Test
-	public void relativeRootWorkspaceProposals() {
-
-		final ICompletionContext context = mock(ICompletionContext.class);
-		when(context.getOriginalCode()).thenReturn("");
-		when(context.getFilter()).thenReturn("");
-		when(context.getResource()).thenReturn(fFile1);
-
-		final Collection<? extends ScriptCompletionProposal> proposals = fProvider.getProposals(context);
-		final Collection<String> content = extractDisplayedContent(proposals);
-
-		assertTrue(content.contains("workspace://"), "<workspace://> proposal missing");
-		assertTrue(content.contains("file:///"), "<file:///> proposal missing");
-		assertTrue(content.contains("project://"), "<project://> proposal missing");
-		assertTrue(content.contains(".."), "<..> proposal missing");
-		assertTrue(content.contains(FILE1_NAME), "<" + FILE1_NAME + "> proposal missing");
-		assertTrue(content.contains(FILE2_NAME), "<" + FILE2_NAME + "> proposal missing");
-		assertEquals(6, proposals.size());
+	@DisplayName("getProposals('workspace://') contains projects")
+	public void getProposals_contains_projects_for_workspace_root() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider.getProposals(createContext("workspace://"));
+		assertNotNull(findProposal(proposals, fProject.getName()));
 	}
 
-	@Disabled
 	@Test
-	public void fileSystemRootProposals() {
-		final ICompletionContext context = mock(ICompletionContext.class);
-		when(context.getOriginalCode()).thenReturn("file:///");
-		when(context.getFilter()).thenReturn("file:///");
-		when(context.getResource()).thenReturn(null);
-
-		final Collection<? extends ScriptCompletionProposal> proposals = fProvider.getProposals(context);
-		final Collection<String> content = extractDisplayedContent(proposals);
-
-		assertFalse(content.contains(".."), "<..> proposal exists");
-		assertFalse(proposals.isEmpty());
+	@DisplayName("getProposals('workspace://') does not contain contains '..'")
+	public void getProposals_doe_not_contain_back_proposal() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider.getProposals(createContext("workspace://"));
+		assertNull(findProposal(proposals, ".."));
 	}
 
-	@Disabled
 	@Test
-	public void fileSystemFolderProposals() {
-		final ICompletionContext context = mock(ICompletionContext.class);
-		when(context.getOriginalCode()).thenReturn(fFsFolder.getAbsolutePath());
-		when(context.getFilter()).thenReturn(fFsFolder.getAbsolutePath());
-		when(context.getResource()).thenReturn(null);
-
-		final Collection<? extends ScriptCompletionProposal> proposals = fProvider.getProposals(context);
-		final Collection<String> content = extractDisplayedContent(proposals);
-
-		assertTrue(content.contains(".."), "<..> proposal missing");
-		assertTrue(content.contains(FILE1_NAME), "<" + FILE1_NAME + "> proposal missing");
-		assertTrue(content.contains(FILE2_NAME), "<" + FILE2_NAME + "> proposal missing");
-		assertEquals(3, proposals.size());
+	@DisplayName("getProposals('workspace://<project>') contains project root folders")
+	public void getProposals_contains_project_root_folders_for_workspace_project() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider.getProposals(createContext("workspace://" + fProject.getName() + "/"));
+		assertNotNull(findProposal(proposals, fFolder.getName()));
 	}
 
-	private static Collection<String> extractDisplayedContent(Collection<? extends ScriptCompletionProposal> proposals) {
-		final Collection<String> content = new HashSet<>();
+	@Test
+	@DisplayName("getProposals('workspace://<project>') contains project root files")
+	public void getProposals_contains_project_root_files_for_workspace_project() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider.getProposals(createContext("workspace://" + fProject.getName() + "/"));
+		assertNotNull(findProposal(proposals, fFile1.getName()));
+	}
 
-		for (final ScriptCompletionProposal proposal : proposals)
-			content.add(proposal.getDisplayString());
+	@Test
+	@DisplayName("getProposals('workspace://<project>/<folder>') contains folder files")
+	public void getProposals_contains_files_for_workspace_folder() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider
+				.getProposals(createContext("workspace://" + fProject.getName() + "/" + fFolder.getName() + "/"));
+		assertNotNull(findProposal(proposals, fFile2.getName()));
+	}
 
-		return content;
+	@Test
+	@DisplayName("getProposals('workspace://<project>/<folder>') contains '..' proposal")
+	public void getProposals_contains_back_for_workspace_folder() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider
+				.getProposals(createContext("workspace://" + fProject.getName() + "/" + fFolder.getName() + "/"));
+		assertNotNull(findProposal(proposals, ".."));
+	}
+
+	@DisplayName("getProposals('project://') contains project root folders")
+	public void getProposals_contains_project_root_folders_for_project_root() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider.getProposals(createContext("project://", fFile2));
+		assertNotNull(findProposal(proposals, fFolder.getName()));
+	}
+
+	@Test
+	@DisplayName("getProposals('project://') contains project root files")
+	public void getProposals_contains_project_root_files_for_project_root() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider.getProposals(createContext("project://", fFile2));
+		assertNotNull(findProposal(proposals, fFile1.getName()));
+	}
+
+	@Test
+	@DisplayName("getProposals('project://<folder>') contains folder files")
+	public void getProposals_contains_files_for_project_folder() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider.getProposals(createContext("project://" + fFolder.getName() + "/", fFile2));
+		assertNotNull(findProposal(proposals, fFile2.getName()));
+	}
+
+	@Test
+	@DisplayName("getProposals('project://<folder>') contains '..' proposal")
+	public void getProposals_contains_back_for_project_folder() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider.getProposals(createContext("project://" + fFolder.getName() + "/", fFile2));
+		assertNotNull(findProposal(proposals, ".."));
+	}
+
+	@Test
+	@DisplayName("getProposals('file://') contains root file systems")
+	public void getProposals_contains_root_file_systems() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider.getProposals(createContext("file://"));
+		for (final File root : File.listRoots())
+			assertNotNull(findProposal(proposals, root.getName()), String.format("Root entry not found for %s", root.getName()));
+	}
+
+	@Test
+	@DisplayName("getProposals('') contains files for project")
+	public void getProposals_contains_files_for_project() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider.getProposals(createContext("", fProject));
+		assertNotNull(findProposal(proposals, fFile1.getName()));
+	}
+
+	@Test
+	@DisplayName("getProposals('') contains '..' for project")
+	public void getProposals_contains_back_for_project() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider.getProposals(createContext("", fProject));
+		assertNotNull(findProposal(proposals, ".."));
+	}
+
+	@Test
+	@DisplayName("getProposals('') contains files for filesystem")
+	public void getProposals_contains_files_for_filesystem() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider.getProposals(createContext("", fFsFile1));
+		assertNotNull(findProposal(proposals, fFsFile1.getName()));
+	}
+
+	@Test
+	@DisplayName("getProposals('') contains '..' for filesystem")
+	public void getProposals_contains_back_for_filesystem() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider.getProposals(createContext("", fFsFile1));
+		assertNotNull(findProposal(proposals, ".."));
+	}
+
+	private ScriptCompletionProposal findProposal(Collection<ScriptCompletionProposal> proposals, String displayString) {
+		return proposals.stream().filter(p -> p.getDisplayString().startsWith(displayString)).findFirst().orElseGet(() -> null);
+	}
+
+	private ICompletionContext createContext(String input) {
+		return createContext(input, null);
+	}
+
+	private ICompletionContext createContext(String input, Object resource) {
+		return new BasicContext(null, resource, "\"" + input, input.length() + 1);
 	}
 }
diff --git a/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/provider/AbstractPathCompletionProviderTest.java b/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/provider/AbstractPathCompletionProviderTest.java
new file mode 100644
index 0000000..bc84f73
--- /dev/null
+++ b/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/provider/AbstractPathCompletionProviderTest.java
@@ -0,0 +1,163 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License_Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.ui.completion.provider;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.ease.ICompletionContext;
+import org.eclipse.ease.ui.completion.BasicContext;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+public class AbstractPathCompletionProviderTest {
+
+	@Test
+	@DisplayName("getPathsFromElements() is empty for no elements")
+	public void getPathsFromElements_is_empty_for_empty_element() {
+		assertTrue(new Provider().getPaths().isEmpty());
+	}
+
+	@Test
+	@DisplayName("getPathsFromElements() is empty for root elements only")
+	public void getPathsFromElements_is_empty_for_root_elements_only() {
+		assertTrue(new Provider().getPaths("foo", "bar").isEmpty());
+	}
+
+	@Test
+	@DisplayName("getPathsFromElements() contains parent paths")
+	public void getPathsFromElements_contains_parent_paths() {
+		final Collection<IPath> paths = new Provider().getPaths("foo/bar", "foo/another", "/2nd/path");
+
+		assertEquals(2, paths.size());
+		assertEquals(new Path("/foo"), new ArrayList<>(paths).get(0));
+		assertEquals(new Path("/2nd"), new ArrayList<>(paths).get(1));
+	}
+
+	@Test
+	@DisplayName("getPathsFromElements() contains all hierarchies")
+	public void getPathsFromElements_contains_all_hierarchies() {
+		final Collection<IPath> paths = new Provider().getPaths("first/second/third/fourth");
+
+		assertEquals(3, paths.size());
+		assertEquals(new Path("/first"), new ArrayList<>(paths).get(0));
+		assertEquals(new Path("/first/second"), new ArrayList<>(paths).get(1));
+		assertEquals(new Path("/first/second/third"), new ArrayList<>(paths).get(2));
+	}
+
+	@Test
+	@DisplayName("filter() removes nothing if context is empty")
+	public void filter_removes_nothing_if_context_is_empty() {
+
+		final Collection<String> elements = new Provider().filter(createContext(""), "a", "b", "c");
+
+		assertEquals(3, elements.size());
+		assertEquals("a", new ArrayList<>(elements).get(0));
+		assertEquals("b", new ArrayList<>(elements).get(1));
+		assertEquals("c", new ArrayList<>(elements).get(2));
+	}
+
+	@Test
+	@DisplayName("filter() removes mismatches on same level")
+	public void filter_removes_mismatches_on_same_level() {
+
+		final Collection<String> elements = new Provider().filter(createContext("a"), "a", "b", "c", "alpha");
+
+		assertEquals(2, elements.size());
+		assertEquals("a", new ArrayList<>(elements).get(0));
+		assertEquals("alpha", new ArrayList<>(elements).get(1));
+	}
+
+	@Test
+	@DisplayName("filter('') keeps only 1st level")
+	public void filter_keeps_only_1st_level_on_empty_filter() {
+
+		final Collection<String> elements = new Provider().filter(createContext(""), "first/second/third", "foo/bar", "root");
+
+		assertEquals(1, elements.size());
+		assertEquals("root", new ArrayList<>(elements).get(0));
+	}
+
+	@Test
+	@DisplayName("filter() keeps only 1st level on matching filter")
+	public void filter_keeps_only_1st_level_on_matching_filter() {
+
+		final Collection<String> elements = new Provider().filter(createContext("r"), "first/second/third", "foo/bar", "root");
+
+		assertEquals(1, elements.size());
+		assertEquals("root", new ArrayList<>(elements).get(0));
+	}
+
+	@Test
+	@DisplayName("filter() keeps only matching level on filter")
+	public void filter_keeps_only_lower_level_on_matching_filter() {
+
+		final Collection<String> elements = new Provider().filter(createContext("first/b"), "first", "first/bar", "first/mismatch", "first/bar/another");
+
+		assertEquals(1, elements.size());
+		assertEquals("first/bar", new ArrayList<>(elements).get(0));
+	}
+
+	@Test
+	@DisplayName("filter('foo') matches absolute paths")
+	public void filter_matches_relative_paths() {
+
+		final Collection<String> elements = new Provider().filter(createContext("foo"), "/fooBar", "/foo", "/foo/none", "/bar");
+
+		assertEquals(2, elements.size());
+		assertEquals("/fooBar", new ArrayList<>(elements).get(0));
+		assertEquals("/foo", new ArrayList<>(elements).get(1));
+	}
+
+	@Test
+	@DisplayName("filter('/first/') matches next level")
+	public void filter_matches_next_level() {
+
+		final Collection<String> elements = new Provider().filter(createContext("/first/"), "/first", "/first/second", "/first/second/third");
+
+		assertEquals(1, elements.size());
+		assertEquals("/first/second", new ArrayList<>(elements).get(0));
+	}
+
+	private ICompletionContext createContext(String input) {
+		return new BasicContext(null, null, input, input.length());
+	}
+
+	private class Provider extends AbstractPathCompletionProvider {
+
+		public Collection<IPath> getPaths(String... elements) {
+			return getPathsFromElements(Arrays.asList(elements));
+		}
+
+		public Collection<String> filter(ICompletionContext context, String... elements) {
+			return filter(Arrays.asList(elements), context);
+		}
+
+		@Override
+		protected IPath toPath(Object element) {
+			return new Path(String.valueOf(element));
+		}
+
+		@Override
+		protected void prepareProposals(ICompletionContext context) {
+			// nothing to do
+		}
+	}
+}
diff --git a/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/provider/LoadModuleCompletionProviderTest.java b/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/provider/LoadModuleCompletionProviderTest.java
new file mode 100644
index 0000000..50ee3cc
--- /dev/null
+++ b/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/provider/LoadModuleCompletionProviderTest.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License_Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.ui.completion.provider;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Collection;
+
+import org.eclipse.ease.ICompletionContext;
+import org.eclipse.ease.ui.completion.BasicContext;
+import org.eclipse.ease.ui.completion.ScriptCompletionProposal;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+public class LoadModuleCompletionProviderTest {
+
+	@Test
+	@DisplayName("isActive() = true for loadModule(|")
+	public void isActive_equals_true_for_method() {
+		assertTrue(new LoadModuleCompletionProvider().isActive(getContext("loadModule(")));
+	}
+
+	@Test
+	@DisplayName("isActive() = true for loadModule(\"Plat|")
+	public void isActive_equals_true_for_partial_module_name() {
+		assertTrue(new LoadModuleCompletionProvider().isActive(getContext("loadModule(\"Plat")));
+	}
+
+	@Test
+	@DisplayName("isActive() = false for loadModul(|")
+	public void isActive_equals_true_for_wrong_method_name() {
+		assertFalse(new LoadModuleCompletionProvider().isActive(getContext("loadModul(")));
+	}
+
+	@Test
+	@DisplayName("isActive() = false for loadModule(\"\", |")
+	public void isActive_equals_true_for_wrong_method_parameter() {
+		assertFalse(new LoadModuleCompletionProvider().isActive(getContext("loadModule(\"\",")));
+	}
+
+	@Test
+	@DisplayName("getProposals() contains root entry for loadModule(")
+	public void getProposals_contains_root_entry() {
+		final Collection<ScriptCompletionProposal> proposals = new LoadModuleCompletionProvider().getProposals(getContext("loadModule("));
+		assertNotNull(findProposal(proposals, "Test Root"));
+	}
+
+	@Test
+	@DisplayName("getProposals() contains root entry for loadModule(\"Te")
+	public void getProposals_contains_root_entry_with_relative_filter() {
+		final Collection<ScriptCompletionProposal> proposals = new LoadModuleCompletionProvider().getProposals(getContext("loadModule(\"Te"));
+		assertNotNull(findProposal(proposals, "Test Root"));
+	}
+
+	@Test
+	@DisplayName("getProposals() contains root entry for loadModule(\"/Te")
+	public void getProposals_contains_root_entry_with_absolute_filter() {
+		final Collection<ScriptCompletionProposal> proposals = new LoadModuleCompletionProvider().getProposals(getContext("loadModule(\"/Te"));
+		assertNotNull(findProposal(proposals, "Test Root"));
+	}
+
+	@Test
+	@DisplayName("getProposals() does not contain root entry for loadModule(\"/Foo")
+	public void getProposals_does_not_contain_root_entry_on_filter_mismatch() {
+		final Collection<ScriptCompletionProposal> proposals = new LoadModuleCompletionProvider().getProposals(getContext("loadModule(\"/foo"));
+		assertNull(findProposal(proposals, "Test Root"));
+	}
+
+	private ScriptCompletionProposal findProposal(Collection<ScriptCompletionProposal> proposals, String displayString) {
+		return proposals.stream().filter(p -> p.getDisplayString().startsWith(displayString)).findFirst().orElseGet(() -> null);
+	}
+
+	private ICompletionContext getContext(String input) {
+		return new BasicContext(null, null, input, input.length());
+	}
+}
diff --git a/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/provider/LoadedModuleCompletionProviderTest.java b/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/provider/LoadedModuleCompletionProviderTest.java
new file mode 100644
index 0000000..24465dc
--- /dev/null
+++ b/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/provider/LoadedModuleCompletionProviderTest.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License_Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.ui.completion.provider;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Collection;
+
+import org.eclipse.ease.ICompletionContext;
+import org.eclipse.ease.ui.completion.BasicContext;
+import org.eclipse.ease.ui.completion.ScriptCompletionProposal;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+public class LoadedModuleCompletionProviderTest {
+
+	@ParameterizedTest(name = "for ''{0}''")
+	@DisplayName("isActive() = true")
+	@ValueSource(strings = { "", "myMethod", "call(", "call(myMethod", "call(param, ", "call(param, myMe" })
+	public void isActive_equals_true(String input) {
+		assertTrue(new LoadedModuleCompletionProvider().isActive(createContext(input)));
+	}
+
+	@ParameterizedTest(name = "for ''{0}''")
+	@DisplayName("isActive() = false")
+	@ValueSource(strings = { "java.", "java.io", "call(java.io", "call(\"myMethod" })
+	public void isActive_equals_false(String input) {
+		assertFalse(new LoadedModuleCompletionProvider().isActive(createContext(input)));
+	}
+
+	@Test
+	@DisplayName("getProposals() contains method proposals")
+	public void getProposals_contains_module_methods() {
+		final Collection<ScriptCompletionProposal> proposals = new LoadedModuleCompletionProvider().getProposals(createContext("loadModule(\"/Test Root\")\n"));
+		assertNotNull(findProposal(proposals, "testMethod"));
+	}
+
+	@Test
+	@DisplayName("getProposals() contains constant proposals")
+	public void getProposals_contains_constant_methods() {
+		final Collection<ScriptCompletionProposal> proposals = new LoadedModuleCompletionProvider().getProposals(createContext("loadModule(\"/Test Root\")\n"));
+		assertNotNull(findProposal(proposals, "TEST_CONSTANT"));
+	}
+
+	private ScriptCompletionProposal findProposal(Collection<ScriptCompletionProposal> proposals, String displayString) {
+		return proposals.stream().filter(p -> p.getDisplayString().startsWith(displayString)).findFirst().orElseGet(() -> null);
+	}
+
+	private ICompletionContext createContext(String input) {
+		return new BasicContext(null, null, input, input.length());
+	}
+}
diff --git a/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/provider/VariablesCompletionProviderTest.java b/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/provider/VariablesCompletionProviderTest.java
new file mode 100644
index 0000000..6c71b92
--- /dev/null
+++ b/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/provider/VariablesCompletionProviderTest.java
@@ -0,0 +1,126 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License_Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.ui.completion.provider;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.ease.IScriptEngine;
+import org.eclipse.ease.modules.IEnvironment;
+import org.eclipse.ease.service.EngineDescription;
+import org.eclipse.ease.ui.completion.BasicContext;
+import org.eclipse.ease.ui.completion.ScriptCompletionProposal;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+public class VariablesCompletionProviderTest {
+
+	private VariablesCompletionProvider fProvider;
+
+	@BeforeEach
+	public void beforeEach() {
+		fProvider = new VariablesCompletionProvider();
+	}
+
+	@ParameterizedTest(name = "for context ''{0}''")
+	@DisplayName("isActive() = true")
+	@ValueSource(strings = { "", "foo", "Foo", "method(", "method(45,", "method(45, foo", "java.io.File(", "java.io.File().exists(" })
+	public void isActive_equals_true(String input) {
+		assertTrue(fProvider.isActive(createContext(input)));
+	}
+
+	@ParameterizedTest(name = "for context ''{0}''")
+	@DisplayName("isActive() = false")
+	@ValueSource(strings = { "com.", "method()" })
+	public void isActive_equals_false(String input) {
+		assertFalse(fProvider.isActive(createContext(input)));
+	}
+
+	@Test
+	@DisplayName("prepareProposals() contains string variable")
+	public void prepareProposals_instance_contains_string_variable() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider.getProposals(createContext(""));
+
+		final ScriptCompletionProposal proposal = findProposal(proposals, "myString");
+		assertNotNull(proposal);
+	}
+
+	@Test
+	@DisplayName("prepareProposals() contains string variable for filter input")
+	public void prepareProposals_instance_contains_string_variable_for_filter_input() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider.getProposals(createContext("myStr"));
+
+		final ScriptCompletionProposal proposal = findProposal(proposals, "myString");
+		assertNotNull(proposal);
+	}
+
+	@Test
+	@DisplayName("prepareProposals() contains string variable for filter input (case insensitive))")
+	public void prepareProposals_instance_contains_string_variable_for_filter_input_case_insensitive() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider.getProposals(createContext("MYSTR"));
+
+		final ScriptCompletionProposal proposal = findProposal(proposals, "myString");
+		assertNotNull(proposal);
+	}
+
+	@Test
+	@DisplayName("prepareProposals() contains integer variable")
+	public void prepareProposals_instance_contains_integer_variable() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider.getProposals(createContext(""));
+
+		final ScriptCompletionProposal proposal = findProposal(proposals, "myNumber");
+		assertNotNull(proposal);
+	}
+
+	@Test
+	@DisplayName("prepareProposals() does not contain internal variable")
+	public void prepareProposals_does_not_contain_internal_variable() {
+		final Collection<ScriptCompletionProposal> proposals = fProvider.getProposals(createContext(""));
+
+		final ScriptCompletionProposal proposal = findProposal(proposals, IEnvironment.MODULE_PREFIX + "SOMETHING");
+		assertNull(proposal);
+	}
+
+	private ScriptCompletionProposal findProposal(Collection<ScriptCompletionProposal> proposals, String displayString) {
+		return proposals.stream().filter(p -> p.getDisplayString().startsWith(displayString)).findFirst().orElseGet(() -> null);
+	}
+
+	private BasicContext createContext(String input) {
+		final Map<String, Object> variables = new HashMap<>();
+		variables.put("myString", "Hello world");
+		variables.put("myNumber", 42);
+		variables.put(IEnvironment.MODULE_PREFIX + "SOMETHING", "not visible");
+
+		final EngineDescription description = mock(EngineDescription.class);
+		when(description.getSupportedScriptTypes()).thenReturn(Collections.singletonList(null));
+
+		final IScriptEngine scriptEngine = mock(IScriptEngine.class);
+		when(scriptEngine.getVariables()).thenReturn(variables);
+		when(scriptEngine.getDescription()).thenReturn(description);
+
+		return new BasicContext(scriptEngine, input, input.length());
+	}
+}
diff --git a/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/tokenizer/BracketMatcherTest.java b/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/tokenizer/BracketMatcherTest.java
new file mode 100644
index 0000000..db06caf
--- /dev/null
+++ b/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/tokenizer/BracketMatcherTest.java
@@ -0,0 +1,78 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License_Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.ui.completion.tokenizer;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+public class BracketMatcherTest {
+
+	@Test
+	@DisplayName("getBrackets() is empty for 'some text'")
+	public void getBrackets_is_empty() {
+		assertTrue(new BracketMatcher("some text").getBrackets().isEmpty());
+	}
+
+	@Test
+	@DisplayName("getBrackets() detects 'single()'")
+	public void getBrackets_detects_1() {
+		assertArrayEquals(new Object[] { new Bracket(6, 7) }, new BracketMatcher("single()").getBrackets().toArray());
+	}
+
+	@Test
+	@DisplayName("getBrackets() detects 'single().inText'")
+	public void getBrackets_detects_2() {
+		assertArrayEquals(new Object[] { new Bracket(6, 7) }, new BracketMatcher("single().inText").getBrackets().toArray());
+	}
+
+	@Test
+	@DisplayName("getBrackets() detects 'one().two().three'")
+	public void getBrackets_detects_3() {
+		assertArrayEquals(new Object[] { new Bracket(9, 10), new Bracket(3, 4) }, new BracketMatcher("one().two().three").getBrackets().toArray());
+	}
+
+	@Test
+	@DisplayName("getBrackets() detects 'outer(inner())'")
+	public void getBrackets_detects_nested() {
+		assertArrayEquals(new Object[] { new Bracket(11, 12), new Bracket(5, 13) }, new BracketMatcher("outer(inner())").getBrackets().toArray());
+	}
+
+	@Test
+	@DisplayName("getBrackets() detects 'outer(inner('")
+	public void getBrackets_detects_open_brackets() {
+		assertArrayEquals(new Object[] { new Bracket(11, -1), new Bracket(5, -1) }, new BracketMatcher("outer(inner(").getBrackets().toArray());
+	}
+
+	@Test
+	@DisplayName("hasOpenBrackets() = false for 'outer(inner())'")
+	public void hasOpenBrackets_equals_false() {
+		assertFalse(new BracketMatcher("outer(inner())").hasOpenBrackets());
+	}
+
+	@Test
+	@DisplayName("hasOpenBrackets() = true for 'outer(inner(), text()'")
+	public void hasOpenBrackets_equals_true() {
+		assertTrue(new BracketMatcher("outer(inner(), text()").hasOpenBrackets());
+	}
+
+	@Test
+	@DisplayName("getOpenBrackets() detects 'outer(inner(), text('")
+	public void getOpenBrackets_returns_open_brackets_only() {
+		assertArrayEquals(new Object[] { new Bracket(19, -1), new Bracket(5, -1) }, new BracketMatcher("outer(inner(), text(").getOpenBrackets().toArray());
+	}
+}
diff --git a/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/tokenizer/InputTokenizerTest.java b/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/tokenizer/InputTokenizerTest.java
new file mode 100644
index 0000000..957cd2c
--- /dev/null
+++ b/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/completion/tokenizer/InputTokenizerTest.java
@@ -0,0 +1,289 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License_Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.ease.ui.completion.tokenizer;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+import org.eclipse.ease.ui.completion.tokenizer.InputTokenizer;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+public class InputTokenizerTest {
+
+	@ParameterizedTest(name = "for ''{0}''")
+	@DisplayName("isDelimiter() = true")
+	@ValueSource(strings = { ".", ",", "(", "()", ")" })
+	public void isDelimiter_equals_true(String input) {
+		assertTrue(InputTokenizer.isDelimiter(input));
+	}
+
+	@ParameterizedTest(name = "for ''{0}''")
+	@DisplayName("isDelimiter() = false")
+	@ValueSource(strings = { ". ", "test", "123" })
+	public void isDelimiter_equals_false(String input) {
+		assertFalse(InputTokenizer.isDelimiter(input));
+	}
+
+	@ParameterizedTest(name = "for ''{0}''")
+	@DisplayName("isTextFilter() = true")
+	@ValueSource(strings = { "foo", "bar" })
+	public void isTextFilter_equals_true(String input) {
+		assertTrue(InputTokenizer.isTextFilter(input));
+	}
+
+	@ParameterizedTest(name = "for ''{0}''")
+	@DisplayName("isTextFilter() = false")
+	@ValueSource(strings = { ".", "(", "()", ")", "," })
+	public void isTextFilter_equals_false(String input) {
+		assertFalse(InputTokenizer.isTextFilter(input));
+	}
+
+	@Test
+	@DisplayName("'' => {}")
+	public void getTokens_for_empty_input() {
+		final InputTokenizer tokenizer = new InputTokenizer();
+
+		assertArrayEquals(new Object[] {}, tokenizer.getTokens("").toArray());
+	}
+
+	@Test
+	@DisplayName("'foo' => 'foo'")
+	public void getTokens_1() {
+		final InputTokenizer tokenizer = new InputTokenizer();
+
+		assertArrayEquals(new Object[] { "foo" }, tokenizer.getTokens("foo").toArray());
+	}
+
+	@Test
+	@DisplayName("'java.i' => 'java', '.', 'i')")
+	public void getTokens_detects_package_fragments() {
+		final InputTokenizer tokenizer = new InputTokenizer();
+		assertArrayEquals(new Object[] { "java", ".", "i" }, tokenizer.getTokens("java.i").toArray());
+	}
+
+	@Test
+	@DisplayName("'java.io' => P(java.io)")
+	public void getTokens_detects_package() {
+		final InputTokenizer tokenizer = new InputTokenizer();
+		assertArrayEquals(new Object[] { Package.getPackage("java.io") }, tokenizer.getTokens("java.io").toArray());
+	}
+
+	@Test
+	@DisplayName("'java.io.' => P(java.io), '.'")
+	public void getTokens_detects_incomplete_package() {
+		final InputTokenizer tokenizer = new InputTokenizer();
+		assertArrayEquals(new Object[] { Package.getPackage("java.io"), "." }, tokenizer.getTokens("java.io.").toArray());
+	}
+
+	@Test
+	@DisplayName("'java.io.foo' => P(java.io), '.', 'foo'")
+	public void getTokens_detects_partial_package() {
+		final InputTokenizer tokenizer = new InputTokenizer();
+		assertArrayEquals(new Object[] { Package.getPackage("java.io"), ".", "foo" }, tokenizer.getTokens("java.io.foo").toArray());
+	}
+
+	@Test
+	@DisplayName("'java.io.File' => C(java.io.File)")
+	public void getTokens_detects_class() {
+		final InputTokenizer tokenizer = new InputTokenizer();
+		assertArrayEquals(new Object[] { File.class }, tokenizer.getTokens("java.io.File").toArray());
+	}
+
+	@Test
+	@DisplayName("'java.io.File.' => C(java.io.File), '.'")
+	public void getTokens_detects_class_2() {
+		final InputTokenizer tokenizer = new InputTokenizer();
+		assertArrayEquals(new Object[] { File.class, "." }, tokenizer.getTokens("java.io.File.").toArray());
+	}
+
+	@Test
+	@DisplayName("'java.io.File()' => C(java.io.File), '()'")
+	public void getTokens_detects_class_instantiation() {
+		final InputTokenizer tokenizer = new InputTokenizer();
+		assertArrayEquals(new Object[] { File.class, "()" }, tokenizer.getTokens("java.io.File()").toArray());
+	}
+
+	@Test
+	@DisplayName("'java.io.File(42, 23)' => C(java.io.File), '()'")
+	public void getTokens_detects_class_instantiation_2() {
+		final InputTokenizer tokenizer = new InputTokenizer();
+		assertArrayEquals(new Object[] { File.class, "()" }, tokenizer.getTokens("java.io.File(42, 23)").toArray());
+	}
+
+	@Test
+	@DisplayName("'java.io.File().' => C(java.io.File), '()', '.'")
+	public void getTokens_detects_class_instantiation_3() {
+		final InputTokenizer tokenizer = new InputTokenizer();
+		assertArrayEquals(new Object[] { File.class, "()", "." }, tokenizer.getTokens("java.io.File().").toArray());
+	}
+
+	@Test
+	@DisplayName("'java.io.Fi' => P(java.io), '.', 'Fi'")
+	public void getTokens_detects_package_with_class_prefix() {
+		final InputTokenizer tokenizer = new InputTokenizer();
+		assertArrayEquals(new Object[] { Package.getPackage("java.io"), ".", "Fi" }, tokenizer.getTokens("java.io.Fi").toArray());
+	}
+
+	@Test
+	@DisplayName("'java.io.File.createTempFile()' => C(java.io.File), M(createTempFile), '()'")
+	public void getTokens_detects_static_method_call() {
+		final InputTokenizer tokenizer = new InputTokenizer();
+		assertArrayEquals(new Object[] { File.class, getMethod(File.class, "createTempFile"), "()" },
+				tokenizer.getTokens("java.io.File.createTempFile()").toArray());
+	}
+
+	@Test
+	@DisplayName("'java.io.File.createTempFile().getName()' => C(java.io.File), M(createTempFile), '()', M(getName), '()'")
+	public void getTokens_detects_static_method_call_chain() {
+		final InputTokenizer tokenizer = new InputTokenizer();
+		assertArrayEquals(new Object[] { File.class, getMethod(File.class, "createTempFile"), "()", getMethod(File.class, "getName"), "()" },
+				tokenizer.getTokens("java.io.File.createTempFile().getName()").toArray());
+	}
+
+	@Test
+	@DisplayName("'java.io.File().getName()' => C(java.io.File), '()', M(getName), '()'")
+	public void getTokens_detects_method_call() {
+		final InputTokenizer tokenizer = new InputTokenizer();
+		assertArrayEquals(new Object[] { File.class, "()", getMethod(File.class, "getName"), "()" }, tokenizer.getTokens("java.io.File().getName()").toArray());
+	}
+
+	@Test
+	@DisplayName("'java.io.File.getName().getBytes()' => C(java.io.File), '()', M(getName), '()', M(getBytes), '()'")
+	public void getTokens_detects_method_call_chain() {
+		final InputTokenizer tokenizer = new InputTokenizer();
+		assertArrayEquals(new Object[] { File.class, getMethod(File.class, "getName"), "()", getMethod(String.class, "getBytes"), "()" },
+				tokenizer.getTokens("java.io.File.getName().getBytes()").toArray());
+	}
+
+	@Test
+	@DisplayName("'java.io.File().getName(' => C(java.io.File), '()', M(getName), '('")
+	public void getTokens_detects_method_call_parameter() {
+		final InputTokenizer tokenizer = new InputTokenizer();
+		assertArrayEquals(new Object[] { File.class, "()", getMethod(File.class, "getName"), "(" }, tokenizer.getTokens("java.io.File().getName(").toArray());
+	}
+
+	@Test
+	@DisplayName("'java.io.File().getName(1, 2,' => C(java.io.File), '()', M(getName), '(', ',', ','")
+	public void getTokens_detects_method_call_parameters() {
+		final InputTokenizer tokenizer = new InputTokenizer();
+		assertArrayEquals(new Object[] { File.class, "()", getMethod(File.class, "getName"), "(", ",", "," },
+				tokenizer.getTokens("java.io.File().getName(1, 2,").toArray());
+	}
+
+	@Test
+	@DisplayName("'new java' =>  'java'")
+	public void getTokens_removes_new_operator() {
+		final InputTokenizer tokenizer = new InputTokenizer();
+		assertArrayEquals(new Object[] { "java" }, tokenizer.getTokens("new java").toArray());
+	}
+
+	@Test
+	@DisplayName("'foo=java' =>  'java'")
+	public void getTokens_removes_left_hand_side_of_assignment() {
+		final InputTokenizer tokenizer = new InputTokenizer();
+		assertArrayEquals(new Object[] { "java" }, tokenizer.getTokens("foo=java").toArray());
+	}
+
+	@Test
+	@DisplayName("'java.io.File().' => C(java.io.File), '()', '.'")
+	public void getTokens_detects_method_call_request() {
+		final InputTokenizer tokenizer = new InputTokenizer();
+		assertArrayEquals(new Object[] { File.class, "()", "." }, tokenizer.getTokens("java.io.File().").toArray());
+	}
+
+	@Test
+	@DisplayName("'java.io.File().get' => C(java.io.File), '()', '.', 'get'")
+	public void getTokens_detects_method_call_request_with_filter() {
+		final InputTokenizer tokenizer = new InputTokenizer();
+		assertArrayEquals(new Object[] { File.class, "()", ".", "get" }, tokenizer.getTokens("java.io.File().get").toArray());
+	}
+
+	@Test
+	@DisplayName("'java.io.File().getName(1, 2, 3' => C(java.io.File), '()', M(getName), '(', ',', ',', '3'")
+	public void getTokens_detects_method_call_parameters_with_prefix() {
+		final InputTokenizer tokenizer = new InputTokenizer();
+		assertArrayEquals(new Object[] { File.class, "()", getMethod(File.class, "getName"), "(", ",", ",", "3" },
+				tokenizer.getTokens("java.io.File().getName(1, 2, 3").toArray());
+	}
+
+	@Test
+	@DisplayName("'java.io.File().getName(foo' => C(java.io.File), '()', M(getName), '(', 'foo'")
+	public void getTokens_detects_method_call_parameter_prefix() {
+		final InputTokenizer tokenizer = new InputTokenizer();
+		assertArrayEquals(new Object[] { File.class, "()", getMethod(File.class, "getName"), "(", "foo" },
+				tokenizer.getTokens("java.io.File().getName(foo").toArray());
+	}
+
+	@Test
+	@DisplayName("'foo()' => 'foo', '()'")
+	public void getTokens_detects_javascript_method_call() {
+		final InputTokenizer tokenizer = new InputTokenizer();
+		assertArrayEquals(new Object[] { "foo", "()" }, tokenizer.getTokens("foo()").toArray());
+	}
+
+	@Test
+	@DisplayName("'JavaScript()' => 'JavaScript', '()'")
+	public void getTokens_detects_javascript_class_instantiation() {
+		final InputTokenizer tokenizer = new InputTokenizer();
+		assertArrayEquals(new Object[] { "JavaScript", "()" }, tokenizer.getTokens("JavaScript()").toArray());
+	}
+
+	@Test
+	@DisplayName("'myVar' => 'myVar'")
+	public void getTokens_detects_javascript_variable() {
+		final InputTokenizer tokenizer = new InputTokenizer();
+		assertArrayEquals(new Object[] { "myVar" }, tokenizer.getTokens("myVar").toArray());
+	}
+
+	@Test
+	@DisplayName("'myVar.foo()' => 'myVar', '.', 'foo', '()'")
+	public void getTokens_detects_javascript_object_call() {
+		final InputTokenizer tokenizer = new InputTokenizer();
+		assertArrayEquals(new Object[] { "myVar", ".", "foo", "()" }, tokenizer.getTokens("myVar.foo()").toArray());
+	}
+
+	@Test
+	@DisplayName("'myVar' => C(java.lang.String), '()'")
+	public void getTokens_detects_resolved_javascript_variable() {
+		final InputTokenizer tokenizer = new InputTokenizer(v -> "myVar".equals(v) ? String.class : null);
+		assertArrayEquals(new Object[] { String.class, "()" }, tokenizer.getTokens("myVar").toArray());
+	}
+
+	@Test
+	@DisplayName("'myVar.' => C(java.lang.String), '()', '.'")
+	public void getTokens_detects_resolved_javascript_variable_2() {
+		final InputTokenizer tokenizer = new InputTokenizer(v -> "myVar".equals(v) ? String.class : null);
+		assertArrayEquals(new Object[] { String.class, "()", "." }, tokenizer.getTokens("myVar.").toArray());
+	}
+
+	@Test
+	@DisplayName("'myVar.startsWith(x)' => C(java.lang.String), '()', M(startsWith), '()'")
+	public void getTokens_detects_resolved_javascript_variable_with_method() {
+		final InputTokenizer tokenizer = new InputTokenizer(v -> "myVar".equals(v) ? String.class : null);
+		assertArrayEquals(new Object[] { String.class, "()", getMethod(String.class, "startsWith"), "()" },
+				tokenizer.getTokens("myVar.startsWith(x)").toArray());
+	}
+
+	private static Method getMethod(Class<?> clazz, String methodName) {
+		return Arrays.asList(clazz.getMethods()).stream().filter(m -> methodName.equals(m.getName())).findFirst().get();
+	}
+}
diff --git a/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/modules/ModulesToolsTest.java b/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/test/RootModule.java
similarity index 70%
copy from tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/modules/ModulesToolsTest.java
copy to tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/test/RootModule.java
index d20563c..f8ec394 100644
--- a/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/modules/ModulesToolsTest.java
+++ b/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/test/RootModule.java
@@ -11,8 +11,17 @@
  *     Christian Pontesegger - initial API and implementation
  *******************************************************************************/
 
-package org.eclipse.ease.ui.modules;
+package org.eclipse.ease.ui.test;
 
-public class ModulesToolsTest {
+import org.eclipse.ease.modules.WrapToScript;
 
+public class RootModule {
+
+	@WrapToScript
+	public static final String TEST_CONSTANT = "";
+
+	@WrapToScript
+	public void testMethod() {
+		// nothing to do
+	}
 }
diff --git a/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/modules/ModulesToolsTest.java b/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/test/SubModule.java
similarity index 88%
rename from tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/modules/ModulesToolsTest.java
rename to tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/test/SubModule.java
index d20563c..c818d61 100644
--- a/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/modules/ModulesToolsTest.java
+++ b/tests/org.eclipse.ease.ui.test/src/org/eclipse/ease/ui/test/SubModule.java
@@ -11,8 +11,8 @@
  *     Christian Pontesegger - initial API and implementation
  *******************************************************************************/
 
-package org.eclipse.ease.ui.modules;
+package org.eclipse.ease.ui.test;
 
-public class ModulesToolsTest {
+public class SubModule {
 
 }
diff --git a/tests/pom.xml b/tests/pom.xml
index e037d0a..7765cac 100644
--- a/tests/pom.xml
+++ b/tests/pom.xml
@@ -24,8 +24,17 @@
 				<artifactId>tycho-surefire-plugin</artifactId>
 				<version>${tycho.version}</version>
 				<configuration>
-					<providerHint>junit5</providerHint>
+					<providerHint>junit57</providerHint>
+					
+					<dependencies>
+						<!-- avoid CNFE in Surefire when looking for JUnitPlatformProvider -->
+						<dependency>
+							<artifactId>org.junit</artifactId>
+							<type>eclipse-plugin</type>
+						</dependency>
+					</dependencies>
 				</configuration>
+				
 			</plugin>
 
 			<!-- enable JaCoCo code coverage -->