Bug 544108 - Add Chain Completion Feature

- Code was initially chain completion functionality from
  org.eclipse.recommenders.chain.rcp

- Change class package from recommenders -> jdt
- Remove unnecessary testing libraries
- Use null checks instead of com.google.common.base.Optional
- Use nested Map instead of Table
- Use String.indexOf and String.substring instead of StringUtils
- Use java.util.function.Predicate instead of Guava Predicate filters
- Use java.util.Arrays instead of Guava Sets
- Use Map<String, Integer> instead of Guava Multiset

- Hook preferences into JDT
- IRecommendersCompletionContext -> JavaContentAssistInvocationContext
- Use ExecutorService/Future instead of SimpleTimeLimiter
- Add completionProposal extension to plugin.xml of JDT UI
- Call codeComplete(..) on compilation unit to get extended context info

- o.e.jdt.internal.compiler.lookup.Binding -> o.e.jdt.core.dom.IBinding
- o.e.jdt.internal.compiler.ast.ASTNode -> o.e.jdt.core.dom.ASTNode

- Add tests for Chain Completion

Change-Id: Ia68c1ea015b6793327e2aea545696c8f8b068f4a
diff --git a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/text/Chain.java b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/text/Chain.java
new file mode 100644
index 0000000..580f45d
--- /dev/null
+++ b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/text/Chain.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2011 Stefan Henss.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Stefan Henß - initial API and implementation.
+ */
+package org.eclipse.jdt.internal.ui.text;
+
+import java.util.List;
+
+public class Chain {
+
+	private final List<ChainElement> elements;
+
+	private final int expectedDimensions;
+
+	public Chain(final List<ChainElement> elements, final int expectedDimensions) {
+		this.elements= elements;
+		this.expectedDimensions= expectedDimensions;
+	}
+
+	public List<ChainElement> getElements() {
+		return elements;
+	}
+
+	public int getExpectedDimensions() {
+		return expectedDimensions;
+	}
+}
diff --git a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/text/ChainElement.java b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/text/ChainElement.java
new file mode 100644
index 0000000..4633f13
--- /dev/null
+++ b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/text/ChainElement.java
@@ -0,0 +1,134 @@
+/**
+ * Copyright (c) 2010, 2019 Darmstadt University of Technology and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Marcel Bruch - initial API and implementation.
+ */
+package org.eclipse.jdt.internal.ui.text;
+
+import org.eclipse.osgi.util.NLS;
+
+import org.eclipse.jdt.core.IField;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.ILocalVariable;
+import org.eclipse.jdt.core.IMethod;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.core.dom.IBinding;
+import org.eclipse.jdt.core.dom.IMethodBinding;
+import org.eclipse.jdt.core.dom.ITypeBinding;
+import org.eclipse.jdt.core.dom.IVariableBinding;
+
+import org.eclipse.jdt.internal.core.manipulation.JavaManipulationPlugin;
+
+/**
+ * Represents a transition from Type A to Type B by some chain element ( {@link IField} access,
+ * {@link IMethod} call, or {@link ILocalVariable} (as entrypoints only)).
+ *
+ * @see ChainFinder
+ */
+public class ChainElement {
+
+	public enum ElementType {
+		METHOD, FIELD, LOCAL_VARIABLE
+	}
+
+	private final IBinding element;
+
+	private ITypeBinding returnType;
+
+	private int dimension;
+
+	private ElementType elementType;
+
+	private final boolean requireThis;
+
+	public ChainElement(final IBinding binding, final boolean requireThis) {
+		if (binding == null) {
+			throw new IllegalArgumentException("???"); //$NON-NLS-1$
+		}
+		element= binding;
+		this.requireThis= requireThis;
+		initializeReturnType();
+	}
+
+	private void initializeReturnType() {
+		switch (element.getKind()) {
+			case IBinding.VARIABLE:
+				IVariableBinding tmp= ((IVariableBinding) element);
+				returnType= tmp.getType();
+				if (tmp.isField()) {
+					elementType= ElementType.FIELD;
+				} else {
+					elementType= ElementType.LOCAL_VARIABLE;
+				}
+				break;
+			case IBinding.METHOD:
+				returnType= ((IMethodBinding) element).getReturnType();
+				elementType= ElementType.METHOD;
+				break;
+			case IBinding.TYPE:
+				returnType= ((ITypeBinding) element);
+				elementType= ElementType.FIELD;
+				break;
+			default:
+				JavaManipulationPlugin.logErrorMessage(NLS.bind("Cannot handle {0} as return type.", element));
+		}
+		dimension= returnType.getDimensions();
+	}
+
+	@SuppressWarnings("unchecked")
+	public <T extends IBinding> T getElementBinding() {
+		return (T) element;
+	}
+
+	public ElementType getElementType() {
+		return elementType;
+	}
+
+	public ITypeBinding getReturnType() {
+		return returnType;
+	}
+
+	public int getReturnTypeDimension() {
+		return dimension;
+	}
+
+	public boolean requiresThisForQualification() {
+		return requireThis;
+	}
+
+	@Override
+	public int hashCode() {
+		return element.hashCode();
+	}
+
+	@Override
+	public boolean equals(final Object obj) {
+		if (obj instanceof ChainElement) {
+			final ChainElement other= (ChainElement) obj;
+			return element.equals(other.element);
+		}
+		return false;
+	}
+
+	@Override
+	public String toString() {
+		if (elementType == ElementType.METHOD) {
+			final IMethodBinding m= (IMethodBinding) element;
+			IJavaElement e= m.getJavaElement();
+			StringBuilder ret= new StringBuilder(m.getName());
+			if (e instanceof IMethod) {
+				try {
+					return ret.append(((IMethod) e).getSignature()).toString();
+				} catch (JavaModelException e1) {
+					return ret.toString();
+				}
+			}
+		}
+		return element.toString();
+	}
+}
diff --git a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/text/ChainFinder.java b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/text/ChainFinder.java
new file mode 100644
index 0000000..6f5b635
--- /dev/null
+++ b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/text/ChainFinder.java
@@ -0,0 +1,172 @@
+/**
+ * Copyright (c) 2010, 2019 Darmstadt University of Technology and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Marcel Bruch - initial API and implementation.
+ *    Stefan Henss - re-implementation in response to https://bugs.eclipse.org/bugs/show_bug.cgi?id=376796.
+ */
+package org.eclipse.jdt.internal.ui.text;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.dom.IBinding;
+import org.eclipse.jdt.core.dom.ITypeBinding;
+
+public class ChainFinder {
+
+	private final List<ITypeBinding> expectedTypes;
+
+	private final List<String> excludedTypes;
+
+	private final IJavaElement invocationSite;
+
+	private final List<Chain> chains= new LinkedList<>();
+
+	private final Map<IBinding, ChainElement> edgeCache= new HashMap<>();
+
+	private final Map<Map<ITypeBinding, Boolean>, List<IBinding>> fieldsAndMethodsCache= new HashMap<>();
+
+	private final Map<Map<ChainElement, ITypeBinding>, Boolean> assignableCache= new HashMap<>();
+
+	public ChainFinder(final List<ITypeBinding> expectedTypes, final List<String> excludedTypes,
+			final IJavaElement invocationSite) {
+		this.expectedTypes= expectedTypes;
+		this.excludedTypes= excludedTypes;
+		this.invocationSite= invocationSite;
+	}
+
+	public void startChainSearch(final List<ChainElement> entrypoints, final int maxChains, final int minDepth,
+			final int maxDepth) {
+		for (final ITypeBinding expected : expectedTypes) {
+			if (expected != null && !ChainFinder.isFromExcludedType(excludedTypes, expected)) {
+				ITypeBinding expectedType= expected;
+				int expectedDimension= 0;
+				if (expectedType.isArray()) {
+					expectedDimension= expectedType.getDimensions();
+					expectedType= TypeBindingAnalyzer.removeArrayWrapper(expectedType);
+				}
+				searchChainsForExpectedType(expectedType, expectedDimension, entrypoints, maxChains, minDepth,
+						maxDepth);
+			}
+		}
+	}
+
+	private void searchChainsForExpectedType(final ITypeBinding expectedType, final int expectedDimensions,
+			final List<ChainElement> entrypoints, final int maxChains, final int minDepth, final int maxDepth) {
+		final LinkedList<LinkedList<ChainElement>> incompleteChains= prepareQueue(entrypoints);
+
+		while (!incompleteChains.isEmpty()) {
+			final LinkedList<ChainElement> chain= incompleteChains.poll();
+			final ChainElement edge= chain.getLast();
+			if (isValidEndOfChain(edge, expectedType, expectedDimensions)) {
+				if (chain.size() >= minDepth) {
+					chains.add(new Chain(chain, expectedDimensions));
+					if (chains.size() == maxChains) {
+						break;
+					}
+				}
+				continue;
+			}
+			if (chain.size() < maxDepth && incompleteChains.size() <= 50000) {
+				searchDeeper(chain, incompleteChains, edge.getReturnType());
+			}
+		}
+	}
+
+	/**
+	 * Returns the potentially incomplete list of call chains that could be found before a time out
+	 * happened. The contents of this list are mutable and may change as the search makes progress.
+	 *
+	 * @return The list of call chains
+	 */
+	public List<Chain> getChains() {
+		return chains;
+	}
+
+	private static LinkedList<LinkedList<ChainElement>> prepareQueue(final List<ChainElement> entrypoints) {
+		final LinkedList<LinkedList<ChainElement>> incompleteChains= new LinkedList<>();
+		for (final ChainElement entrypoint : entrypoints) {
+			final LinkedList<ChainElement> chain= new LinkedList<>();
+			chain.add(entrypoint);
+			incompleteChains.add(chain);
+		}
+		return incompleteChains;
+	}
+
+	public static boolean isFromExcludedType(final List<String> excluded, final IBinding binding) {
+		String tmp= String.valueOf(binding.getKey());
+		int index= tmp.indexOf(";"); //$NON-NLS-1$
+		final String key= index == -1 ? tmp : tmp.substring(0, index);
+		return excluded.contains(key);
+	}
+
+	private boolean isValidEndOfChain(final ChainElement edge, final ITypeBinding expectedType,
+			final int expectedDimension) {
+		if (edge.getElementBinding().getKind() == IBinding.TYPE) {
+			return false;
+		}
+		Boolean isAssignable= assignableCache.get(Collections.singletonMap(edge, expectedType));
+		if (isAssignable == null) {
+			isAssignable= TypeBindingAnalyzer.isAssignable(edge, expectedType, expectedDimension);
+			assignableCache.put(Collections.singletonMap(edge, expectedType), isAssignable);
+		}
+		return isAssignable.booleanValue();
+	}
+
+	private void searchDeeper(final LinkedList<ChainElement> chain,
+			final List<LinkedList<ChainElement>> incompleteChains, final ITypeBinding currentlyVisitedType) {
+		boolean staticOnly= false;
+		if (chain.getLast().getElementBinding().getKind() == IBinding.TYPE) {
+			staticOnly= true;
+		}
+		for (final IBinding element : findAllFieldsAndMethods(currentlyVisitedType, staticOnly)) {
+			final ChainElement newEdge= createEdge(element);
+			if (!chain.contains(newEdge)) {
+				incompleteChains.add(cloneChainAndAppendEdge(chain, newEdge));
+			}
+		}
+	}
+
+	private List<IBinding> findAllFieldsAndMethods(final ITypeBinding chainElementType, boolean staticOnly) {
+		List<IBinding> cached= fieldsAndMethodsCache.get(Collections.singletonMap(chainElementType, staticOnly));
+		if (cached == null) {
+			cached= new LinkedList<>();
+			Collection<IBinding> candidates= staticOnly
+					? TypeBindingAnalyzer.findAllPublicStaticFieldsAndNonVoidNonPrimitiveStaticMethods(chainElementType, invocationSite)
+					: TypeBindingAnalyzer.findVisibleInstanceFieldsAndRelevantInstanceMethods(chainElementType, invocationSite);
+			for (final IBinding binding : candidates) {
+				if (!ChainFinder.isFromExcludedType(excludedTypes, binding)) {
+					cached.add(binding);
+				}
+			}
+			fieldsAndMethodsCache.put(Collections.singletonMap(chainElementType, staticOnly), cached);
+		}
+		return cached;
+	}
+
+	private ChainElement createEdge(final IBinding member) {
+		ChainElement cached= edgeCache.get(member);
+		if (cached == null) {
+			cached= new ChainElement(member, false);
+			edgeCache.put(member, cached);
+		}
+		return cached;
+	}
+
+	private static LinkedList<ChainElement> cloneChainAndAppendEdge(final LinkedList<ChainElement> chain,
+			final ChainElement newEdge) {
+		final LinkedList<ChainElement> chainCopy= (LinkedList<ChainElement>) chain.clone();
+		chainCopy.add(newEdge);
+		return chainCopy;
+	}
+}
diff --git a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/text/TypeBindingAnalyzer.java b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/text/TypeBindingAnalyzer.java
new file mode 100644
index 0000000..f59d10a
--- /dev/null
+++ b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/text/TypeBindingAnalyzer.java
@@ -0,0 +1,370 @@
+/**
+ * Copyright (c) 2011, 2019 Stefan Henss and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Stefan Henß - initial API and implementation.
+ */
+package org.eclipse.jdt.internal.ui.text;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Predicate;
+
+import org.eclipse.jdt.core.CompletionContext;
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.IField;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IMember;
+import org.eclipse.jdt.core.IMethod;
+import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.core.Signature;
+import org.eclipse.jdt.core.dom.AST;
+import org.eclipse.jdt.core.dom.ASTParser;
+import org.eclipse.jdt.core.dom.IBinding;
+import org.eclipse.jdt.core.dom.IMethodBinding;
+import org.eclipse.jdt.core.dom.ITypeBinding;
+import org.eclipse.jdt.core.dom.IVariableBinding;
+import org.eclipse.jdt.core.dom.Modifier;
+
+import org.eclipse.jdt.internal.corext.dom.Bindings;
+import org.eclipse.jdt.internal.corext.dom.IASTSharedValues;
+import org.eclipse.jdt.internal.corext.template.java.SignatureUtil;
+
+public final class TypeBindingAnalyzer {
+
+	private static final Predicate<IVariableBinding> NON_STATIC_FIELDS_ONLY_FILTER = new Predicate<IVariableBinding>() {
+		@Override
+		public boolean test(IVariableBinding t) {
+			return !Modifier.isStatic(t.getModifiers());
+		}
+    };
+
+    private static final Predicate<IMethodBinding> RELEVANT_NON_STATIC_METHODS_ONLY_FILTER = new Predicate<IMethodBinding>() {
+		@Override
+		public boolean test(IMethodBinding m) {
+			return !Modifier.isStatic(m.getModifiers()) && !isVoid(m) & !m.isConstructor() && !hasPrimitiveReturnType(m);
+		}
+    };
+
+    private static final Predicate<IVariableBinding> STATIC_FIELDS_ONLY_FILTER = new Predicate<IVariableBinding>() {
+		@Override
+		public boolean test(IVariableBinding t) {
+			return Modifier.isStatic(t.getModifiers());
+		}
+    };
+
+    private static final Predicate<IMethodBinding> STATIC_NON_VOID_NON_PRIMITIVE_METHODS_ONLY_FILTER = new Predicate<IMethodBinding>() {
+		@Override
+		public boolean test(IMethodBinding m) {
+			return Modifier.isStatic(m.getModifiers()) && !isVoid(m) && !m.isConstructor() && hasPrimitiveReturnType(m);
+		}
+    };
+
+    private TypeBindingAnalyzer() {
+    }
+
+    private static boolean isVoid(final IMethodBinding m) {
+        return hasPrimitiveReturnType(m) && Bindings.isVoidType(m.getReturnType());
+    }
+
+    private static boolean hasPrimitiveReturnType(final IMethodBinding m) {
+        return m.getReturnType().isPrimitive();
+    }
+
+    public static Collection<IBinding> findVisibleInstanceFieldsAndRelevantInstanceMethods(final ITypeBinding type,
+            final IJavaElement invocationSite) {
+        return findFieldsAndMethods(type, invocationSite, NON_STATIC_FIELDS_ONLY_FILTER,
+                RELEVANT_NON_STATIC_METHODS_ONLY_FILTER);
+    }
+
+    public static Collection<IBinding> findAllPublicStaticFieldsAndNonVoidNonPrimitiveStaticMethods(
+            final ITypeBinding type, final IJavaElement invocationSite) {
+        return findFieldsAndMethods(type, invocationSite, STATIC_FIELDS_ONLY_FILTER,
+                STATIC_NON_VOID_NON_PRIMITIVE_METHODS_ONLY_FILTER);
+    }
+
+    private static Collection<IBinding> findFieldsAndMethods(final ITypeBinding type, final IJavaElement invocationSite,
+            final Predicate<IVariableBinding> fieldFilter, final Predicate<IMethodBinding> methodFilter) {
+        final Map<String, IBinding> tmp = new LinkedHashMap<>();
+        final IType invocationType = ((IMember) invocationSite).getCompilationUnit().findPrimaryType();
+        final ITypeBinding receiverType = getTypeBindingFrom(invocationType);
+        for (final ITypeBinding cur : findAllSupertypesIncludingArgument(type)) {
+            for (final IMethodBinding method : cur.getDeclaredMethods()) {
+                if (!methodFilter.test(method) || !methodCanBeSeenBy(method, receiverType)) {
+                    continue;
+                }
+                final String key = createMethodKey(method);
+                if (!tmp.containsKey(key)) {
+                    tmp.put(key, method);
+                }
+            }
+            for (final IVariableBinding field : cur.getDeclaredFields()) {
+                if (!fieldFilter.test(field) || !fieldCanBeSeenBy(field, receiverType)) {
+                    continue;
+                }
+                final String key = createFieldKey(field);
+                if (!tmp.containsKey(key)) {
+                    tmp.put(key, field);
+                }
+            }
+        }
+        return tmp.values();
+    }
+
+    private static List<ITypeBinding> findAllSupertypesIncludingArgument(final ITypeBinding type) {
+        final ITypeBinding base = removeArrayWrapper(type);
+        if (base.isPrimitive() || Bindings.isVoidType(type)) {
+            return Collections.emptyList();
+        }
+        final List<ITypeBinding> supertypes = new LinkedList<>();
+        final LinkedList<ITypeBinding> queue = new LinkedList<>();
+        queue.add(base);
+        while (!queue.isEmpty()) {
+            final ITypeBinding superType = queue.poll();
+            if (superType == null || supertypes.contains(superType)) {
+                continue;
+            }
+            supertypes.add(superType);
+            queue.add(superType.getSuperclass());
+            for (final ITypeBinding interfc : superType.getInterfaces()) {
+                queue.add(interfc);
+            }
+        }
+        return supertypes;
+    }
+
+    private static String createFieldKey(final IVariableBinding field) {
+		StringBuilder ret= new StringBuilder(field.getName());
+		try {
+			String typeSignature= ((IField)field.getJavaElement()).getTypeSignature();
+			return ret.append(typeSignature).toString();
+		} catch (JavaModelException e) {
+			return ret.toString();
+		}
+    }
+
+    private static String createMethodKey(final IMethodBinding method) {
+        if (method.getJavaElement() instanceof IMethod) {
+        StringBuilder ret= new StringBuilder().append(method.getName());
+            try {
+                IMethod m = (IMethod) method.getJavaElement();
+                String signature= String.valueOf(m.getSignature());
+                int index = signature.lastIndexOf(")"); //$NON-NLS-1$
+                final String signatureWithoutReturnType = index == -1 ? signature : signature.substring(0, index);
+                return ret.append(signatureWithoutReturnType).toString();
+            } catch (JavaModelException e) {
+                return ret.toString();
+            }
+        }
+        return null;
+    }
+
+    public static boolean isAssignable(final ChainElement edge, final ITypeBinding expectedType,
+            final int expectedDimension) {
+        if (expectedDimension <= edge.getReturnTypeDimension()) {
+            final ITypeBinding base = removeArrayWrapper(edge.getReturnType());
+            if (base.isAssignmentCompatible(expectedType)) {
+                return true;
+            }
+            final LinkedList<ITypeBinding> supertypes = new LinkedList<>();
+            supertypes.add(base);
+            String expectedSignature = expectedType.getBinaryName();
+
+            while (!supertypes.isEmpty()) {
+                final ITypeBinding type = supertypes.poll();
+                String typeSignature = type.getBinaryName();
+
+                if (typeSignature.equals(expectedSignature)) {
+                    return true;
+                }
+                final ITypeBinding superclass = type.getSuperclass();
+                if (superclass != null) {
+                    supertypes.add(superclass);
+                }
+                for (final ITypeBinding intf : type.getInterfaces()) {
+                    supertypes.add(intf);
+                }
+            }
+        }
+        return false;
+    }
+
+    public static ITypeBinding removeArrayWrapper(final ITypeBinding type) {
+        if (type.getComponentType() != null) {
+            ITypeBinding base = type;
+            while (base.getComponentType() != null) {
+                base = base.getComponentType();
+            }
+            return base;
+        } else {
+            return type;
+        }
+    }
+
+    public static List<ITypeBinding> resolveBindingsForExpectedTypes(final IJavaProject proj, final ICompilationUnit cu, final CompletionContext ctx) {
+        final List<ITypeBinding> bindings = new LinkedList<>();
+        final IType expectedTypeSig = getExpectedType(proj, ctx);
+		if (expectedTypeSig == null) {
+			ASTParser parser= createParser(cu);
+			AST ast= parser.createAST(null).getAST();
+			ITypeBinding binding= ast.resolveWellKnownType(TypeBindingAnalyzer.getExpectedFullyQualifiedTypeName(ctx));
+			int dim= TypeBindingAnalyzer.getArrayDimension(ctx.getExpectedTypesSignatures());
+			if (dim > 0) {
+				binding= binding.createArrayType(dim);
+			}
+			bindings.add(binding);
+		} else {
+			IBinding[] res= resolveBindingsForTypes(cu, new IJavaElement[] { expectedTypeSig });
+			if (res.length == 1 && res[0] instanceof ITypeBinding) {
+				bindings.add((ITypeBinding) res[0]);
+			}
+		}
+
+        return bindings;
+    }
+
+	public static IType getExpectedType(final IJavaProject proj, final CompletionContext ctx) {
+		IType expected= null;
+		String fqExpectedType= getExpectedFullyQualifiedTypeName(ctx);
+		if (fqExpectedType != null) {
+			try {
+				expected= proj.findType(fqExpectedType);
+			} catch (JavaModelException e) {
+				// do nothing
+			}
+		}
+		return expected;
+	}
+
+	public static String getExpectedFullyQualifiedTypeName(final CompletionContext ctx) {
+		String fqExpectedType= null;
+		final char[][] expectedTypes= ctx.getExpectedTypesSignatures();
+		if (expectedTypes != null && expectedTypes.length > 0) {
+			fqExpectedType= SignatureUtil.stripSignatureToFQN(new String(expectedTypes[0]));
+		}
+		return fqExpectedType;
+	}
+
+	private static int getArrayDimension(final char[][] expectedTypesSignatures) {
+		if (expectedTypesSignatures != null && expectedTypesSignatures.length > 0) {
+			return Signature.getArrayCount(new String(expectedTypesSignatures[0]));
+		}
+
+		return 0;
+	}
+
+	private static ITypeBinding getTypeBindingFrom(IType type) {
+		IBinding[] res= resolveBindingsForTypes(type.getCompilationUnit(), new IJavaElement [] { type });
+		if (res.length == 1 && res[0] instanceof ITypeBinding) {
+			return (ITypeBinding) res[0];
+		}
+
+		return null;
+	}
+
+	private static boolean methodCanBeSeenBy(IMethodBinding mb, ITypeBinding invocationType) {
+		if (Modifier.isPublic(mb.getModifiers())) {
+			return true;
+		}
+		if (Bindings.equals(invocationType, mb.getDeclaringClass())) {
+			return true;
+		}
+
+		String invocationPackage= invocationType.getPackage().getName();
+		String methodPackage= mb.getDeclaringClass().getPackage().getName();
+		if (Modifier.isProtected(mb.getModifiers())) {
+			if (invocationPackage.equals(methodPackage)) {
+				return false; // isSuper ?
+			}
+		}
+
+		if (Modifier.isPrivate(mb.getModifiers())) {
+			ITypeBinding mTypeRoot= mb.getDeclaringClass();
+			while (invocationType.getDeclaringClass() != null) {
+				mTypeRoot= mTypeRoot.getDeclaringClass();
+			}
+			ITypeBinding invTypeRoot= invocationType;
+			while (invTypeRoot.getDeclaringClass() != null) {
+				invTypeRoot= invTypeRoot.getDeclaringClass();
+			}
+			return Bindings.equals(invTypeRoot, mTypeRoot);
+		}
+
+		return invocationPackage.equals(methodPackage);
+	}
+
+	private static boolean fieldCanBeSeenBy(IVariableBinding fb, ITypeBinding invocationType) {
+		if (Modifier.isPublic(fb.getModifiers())) {
+			return true;
+		}
+
+		if (Bindings.equals(invocationType, fb.getDeclaringClass())) {
+			return true;
+		}
+
+		String invocationpackage = invocationType.getPackage().getName();
+		String fieldPackage = fb.getDeclaringClass().getPackage().getName();
+		if (Modifier.isProtected(fb.getModifiers())) {
+			if (Bindings.equals(invocationType, fb.getDeclaringClass())) {
+				return true;
+			}
+			if (invocationpackage.equals(fieldPackage)) {
+				return true;
+			}
+
+			ITypeBinding currType = invocationType.getSuperclass();
+			while (currType != null) {
+				if (Bindings.equals(currType, fb.getDeclaringClass())) {
+					return true;
+				}
+				currType = currType.getSuperclass();
+			}
+		}
+
+		if (Modifier.isPrivate(fb.getModifiers())) {
+			ITypeBinding fTypeRoot= fb.getDeclaringClass();
+			while (invocationType.getDeclaringClass() != null) {
+				fTypeRoot= fTypeRoot.getDeclaringClass();
+			}
+			ITypeBinding invTypeRoot= invocationType;
+			while (invTypeRoot.getDeclaringClass() != null) {
+				invTypeRoot= invTypeRoot.getDeclaringClass();
+			}
+			if (Bindings.equalDeclarations(fTypeRoot, invTypeRoot)) {
+				return true;
+			}
+		}
+
+		if (! invocationpackage.equals(fieldPackage)) {
+			return false;
+		}
+
+		return false;
+	}
+
+	public static IBinding[] resolveBindingsForTypes(ICompilationUnit cu, IJavaElement[] elements) {
+		ASTParser parser= createParser(cu);
+		return parser.createBindings(elements, null);
+	}
+
+	private static ASTParser createParser(ICompilationUnit cu) {
+		ASTParser parser= ASTParser.newParser(IASTSharedValues.SHARED_AST_LEVEL);
+		parser.setKind(ASTParser.K_COMPILATION_UNIT);
+		parser.setProject(cu.getJavaProject());
+		parser.setSource(cu);
+		parser.setResolveBindings(true);
+		parser.setBindingsRecovery(true);
+		parser.setStatementsRecovery(true);
+		return parser;
+	}
+}
diff --git a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/core/ChainCompletionTest.java b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/core/ChainCompletionTest.java
new file mode 100644
index 0000000..304869f
--- /dev/null
+++ b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/core/ChainCompletionTest.java
@@ -0,0 +1,436 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Red Hat Inc. and others.
+ * 
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Red Hat - Initial Contribution
+ *******************************************************************************/
+package org.eclipse.jdt.ui.tests.core;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.jdt.testplugin.JavaProjectHelper;
+
+import org.eclipse.swt.SWT;
+
+import org.eclipse.jface.text.Document;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.TextViewer;
+import org.eclipse.jface.text.contentassist.ICompletionProposal;
+
+import org.eclipse.ui.IEditorPart;
+
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IPackageFragment;
+import org.eclipse.jdt.core.IPackageFragmentRoot;
+import org.eclipse.jdt.core.JavaModelException;
+
+import org.eclipse.jdt.ui.text.java.JavaContentAssistInvocationContext;
+
+import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility;
+import org.eclipse.jdt.internal.ui.text.java.ChainCompletionProposalComputer;
+
+import junit.framework.TestCase;
+
+public class ChainCompletionTest extends TestCase {
+
+	private IJavaProject fJProject;
+
+	private IPackageFragmentRoot javaSrc;
+
+	private IPackageFragment pkg;
+
+	@Override
+	protected void setUp() throws Exception {
+		fJProject= JavaProjectHelper.createJavaProject("TestProject", "bin");
+		JavaProjectHelper.addRTJar18(fJProject);
+		javaSrc= JavaProjectHelper.addSourceContainer(fJProject, "src");
+		pkg= javaSrc.createPackageFragment("test", false, null);
+	}
+
+	@Override
+	protected void tearDown() throws Exception {
+		JavaProjectHelper.delete(fJProject);
+	}
+
+	public void testNullExpectedType() throws Exception {
+		StringBuffer buf= new StringBuffer();
+		buf.append("$package test;\n" +
+				"public class Foo {\n" +
+				"}");
+
+		int completionIndex= getCompletionIndex(buf);
+		ICompilationUnit cu= getCompilationUnit(pkg, buf, "Foo.java");
+
+		List<ICompletionProposal> proposals= computeCompletionProposals(cu, completionIndex);
+
+		assertEquals(0, proposals.size());
+	}
+
+	public void testBasic() throws Exception {
+		StringBuffer buf= new StringBuffer();
+		buf.append("package test;\n" +
+				"public class Foo {\n" +
+				"  public Bar getBar() {\n" +
+				"    return new Bar();\n" +
+				"  }\n" +
+				"  \n" +
+				"  public class Bar {\n" +
+				"    Baz getBaz () {\n" +
+				"      return new Baz();\n" +
+				"    }\n" +
+				"  }\n" +
+				"  \n" +
+				"  public class Baz {\n" +
+				"  }\n" +
+				"\n" +
+				"  public static void mainMethod () {\n" +
+				"    Foo f = new Foo();\n" +
+				"    Baz b = f.$\n" +
+				"  }\n" +
+				"\n" +
+				"}");
+
+		int completionIndex= getCompletionIndex(buf);
+		ICompilationUnit cu= getCompilationUnit(pkg, buf, "Foo.java");
+
+		List<ICompletionProposal> proposals= computeCompletionProposals(cu, completionIndex);
+
+		assertEquals(1, proposals.size());
+		assertEquals("getBar().getBaz() - 2 elements", proposals.get(0).getDisplayString());
+	}
+
+	public void testAccessMethodParameters() throws Exception {
+		StringBuffer buf= new StringBuffer();
+		buf.append("package test;\n" +
+				"\n" +
+				"import java.util.Iterator;\n" +
+				"import java.util.List;\n" +
+				"\n" +
+				"public class Foo {\n" +
+				"  public void method(final List list){\n" +
+				"    Iterator it = $\n" +
+				"  }\n" +
+				"}");
+
+		int completionIndex= getCompletionIndex(buf);
+		ICompilationUnit cu= getCompilationUnit(pkg, buf, "Foo.java");
+
+		List<ICompletionProposal> proposals= computeCompletionProposals(cu, completionIndex);
+
+		List<String> expected= Arrays.asList(
+				"list.iterator() - 2 elements",
+				"list.listIterator() - 2 elements",
+				"list.listIterator(int) - 2 elements",
+				"list.subList(int, int).iterator() - 3 elements",
+				"list.stream().iterator() - 3 elements");
+
+		assertProposalsExist(expected, proposals);
+	}
+
+	public void testAvoidRecursiveCallToMember() throws Exception {
+		StringBuffer buf= new StringBuffer();
+		buf.append("package test;\n" +
+				"\n" +
+				"import java.io.File;\n" +
+				"\n" +
+				"public class AvoidRecursiveCallsToMember {\n" +
+				"  public File findMe = new AtomicBoolean();\n" +
+				"  public AvoidRecursiveCallsToMember getSubElement() {\n" +
+				"    return new AvoidRecursiveCallsToMember();\n" +
+				"  }\n" +
+				"  public static void method2() {\n" +
+				"    final AvoidRecursiveCallsToMember useMe = new AvoidRecursiveCallsToMember();\n" +
+				"    final File c = useMe.get$\n" +
+				"  }\n" +
+				"}");
+
+		int completionIndex= getCompletionIndex(buf);
+		ICompilationUnit cu= getCompilationUnit(pkg, buf, "AvoidRecursiveCallsToMember.java");
+
+		List<ICompletionProposal> proposals= computeCompletionProposals(cu, completionIndex);
+
+		assertEquals(1, proposals.size());
+		assertEquals("getSubElement().findMe - 2 elements", proposals.get(0).getDisplayString());
+	}
+
+	public void testCompletionOnArrayMemberAccessInMethod1() throws Exception {
+		StringBuffer buf= new StringBuffer();
+		buf.append("package test;\n" +
+				"\n" +
+				"import java.lang.Boolean;\n" +
+				"import java.lang.Integer;\n" +
+				"\n" +
+				"public class CompletionOnArrayMemberAccessInMethod {\n" +
+				"  public Integer findUs[] = { new Integer(1), new Integer(2) };\n" +
+				"  public Boolean findUs1[][][] = new Boolean[1][1][1];\n" +
+				"\n" +
+				"  public static void method1() {\n" +
+				"    final CompletionOnArrayMemberAccessInMethod obj = new CompletionOnArrayMemberAccessInMethod();\n" +
+				"    final Integer c = $\n" +
+				"  }\n" +
+				"}");
+
+		int completionIndex= getCompletionIndex(buf);
+		ICompilationUnit cu= getCompilationUnit(pkg, buf, "CompletionOnArrayMemberAccessInMethod.java");
+
+		List<ICompletionProposal> proposals= computeCompletionProposals(cu, completionIndex);
+
+		List<String> expected= Arrays.asList("obj.findUs[] - 2 elements");
+		assertProposalsExist(expected, proposals);
+	}
+
+	public void testCompletionOnArrayWithCastsSupertype1() throws Exception {
+		StringBuffer buf= new StringBuffer();
+		buf.append("package test;\n" +
+				"\n" +
+				"import java.lang.Integer;\n" +
+				"import java.lang.Number;\n" +
+				"\n" +
+				"public class CompletionOnArrayWithCastsSupertype {\n" +
+				"  public Integer[][][] findme;\n" +
+				"  public int i;\n" +
+				"\n" +
+				"  public static void method1() {\n" +
+				"    final CompletionOnArrayWithCastsSupertype obj = new CompletionOnArrayWithCastsSupertype();\n" +
+				"    final Number c = $\n" +
+				"  }\n" +
+				"}");
+
+		int completionIndex= getCompletionIndex(buf);
+		ICompilationUnit cu= getCompilationUnit(pkg, buf, "CompletionOnArrayWithCastsSupertype.java");
+
+		List<ICompletionProposal> proposals= computeCompletionProposals(cu, completionIndex);
+
+		assertEquals(1, proposals.size());
+		assertEquals("obj.findme[][][] - 2 elements", proposals.get(0).getDisplayString());
+	}
+
+	public void testCompletionOnGenericTypeInMethod1() throws Exception {
+		StringBuffer buf= new StringBuffer();
+		buf.append("package test;\n" +
+				"\n" +
+				"import java.util.ArrayList;\n" +
+				"import java.util.List;\n" +
+				"\n" +
+				"public class CompletionOnGenericTypeInMethod {\n" +
+				"  public List<String> findMe = new ArrayList<String>();\n" +
+				"\n" +
+				"  public static void test_exactGenericType() {\n" +
+				"    final CompletionOnGenericTypeInMethod variable = new CompletionOnGenericTypeInMethod();\n" +
+				"    final List<String> c = $\n" +
+				"  }\n" +
+				"}");
+
+		int completionIndex= getCompletionIndex(buf);
+		ICompilationUnit cu= getCompilationUnit(pkg, buf, "CompletionOnGenericTypeInMethod.java");
+
+		List<ICompletionProposal> proposals= computeCompletionProposals(cu, completionIndex);
+
+		assertEquals(1, proposals.size());
+		assertEquals("variable.findMe - 2 elements", proposals.get(0).getDisplayString());
+	}
+
+	public void testCompletionOnMemberCallChainDepth1() throws Exception {
+		StringBuffer buf= new StringBuffer();
+		buf.append("package test;\n" +
+				"\n" +
+				"import java.io.File;\n" +
+				"\n" +
+				"public class A {\n" +
+				"  public B b = new B();\n" +
+				"  public class B {\n" +
+				"    public File findMember = new File(\"\");\n" +
+				"    public File findMethod() {\n" +
+				"      return null;\n" +
+				"    }\n" +
+				"  }\n" +
+				"  public static void mainMethod () {\n" +
+				"    A a = new A();\n" +
+				"    File c = $\n" +
+				"  }\n" +
+				"}");
+
+		int completionIndex= getCompletionIndex(buf);
+		ICompilationUnit cu= getCompilationUnit(pkg, buf, "A.java");
+
+		List<ICompletionProposal> proposals= computeCompletionProposals(cu, completionIndex);
+
+		List<String> expected= Arrays.asList(
+				"a.b.findMethod() - 3 elements",
+				"a.b.findMember - 3 elements");
+
+		assertProposalsExist(expected, proposals);
+	}
+
+	public void testCompletionOnMemberInMethodWithPrefix() throws Exception {
+		StringBuffer buf= new StringBuffer();
+		buf.append("package test;\n" +
+				"\n" +
+				"import java.io.File;\n" +
+				"\n" +
+				"public class CompletionOnMemberInMethodWithPrefix {\n" +
+				"\n" +
+				"  public File findMe;\n" +
+				"\n" +
+				"  public CompletionOnMemberInMethodWithPrefix getSubElement() {\n" +
+				"    return new CompletionOnMemberInMethodWithPrefix();\n" +
+				"  }\n" +
+				"\n" +
+				"  public static void method2() {\n" +
+				"    final CompletionOnMemberInMethodWithPrefix useMe = new CompletionOnMemberInMethodWithPrefix();\n" +
+				"    final File c = useMe.get$\n" +
+				"  }\n" +
+				"}");
+
+		int completionIndex= getCompletionIndex(buf);
+		ICompilationUnit cu= getCompilationUnit(pkg, buf, "CompletionOnMemberInMethodWithPrefix.java");
+
+		List<ICompletionProposal> proposals= computeCompletionProposals(cu, completionIndex);
+
+		assertEquals(1, proposals.size());
+		assertEquals("getSubElement().findMe - 2 elements", proposals.get(0).getDisplayString());
+	}
+
+	public void testCompletionOnMethodReturn() throws Exception {
+		StringBuffer buf= new StringBuffer();
+		buf.append("package test;\n" +
+				"\n" +
+				"import java.io.File;\n" +
+				"import java.util.Iterator;\n" +
+				"import java.util.LinkedList;\n" +
+				"import java.util.List;\n" +
+				"\n" +
+				"public class Foo {\n" +
+				"  public void method() {\n" +
+				"    final Iterator<File> c = $\n" +
+				"  }\n" +
+				"  private List<File> getList() {\n" +
+				"    return new LinkedList<File>();\n" +
+				"  }\n" +
+				"}");
+
+		int completionIndex= getCompletionIndex(buf);
+		ICompilationUnit cu= getCompilationUnit(pkg, buf, "Foo.java");
+
+		List<ICompletionProposal> proposals= computeCompletionProposals(cu, completionIndex);
+
+		List<String> expected= Arrays.asList(
+				"getList().iterator() - 2 elements",
+				"getList().listIterator() - 2 elements",
+				"getList().listIterator(int) - 2 elements",
+				"getList().subList(int, int).iterator() - 3 elements",
+				"getList().subList(int, int).listIterator() - 3 elements",
+				"getList().subList(int, int).listIterator(int) - 3 elements");
+
+		assertProposalsExist(expected, proposals);
+	}
+
+	public void testCompletionOnNonPublicMemberInMethod1() throws Exception {
+		StringBuffer buf= new StringBuffer();
+		buf.append("package test;\n" +
+				"\n" +
+				"public class CompletionOnNonPublicMemberInMethod {\n" +
+				"  protected Boolean findMe1 = new Boolean();\n" +
+				"  Integer findMe2 = new Integer();\n" +
+				"  private final Long findMe3 = new Long();\n" +
+				"\n" +
+				"  public static void test_protected() {\n" +
+				"    final CompletionOnNonPublicMemberInMethod useMe = new CompletionOnNonPublicMemberInMethod();\n" +
+				"    final Boolean c = $\n" +
+				"  }\n" +
+				"}");
+
+		int completionIndex= getCompletionIndex(buf);
+		ICompilationUnit cu= getCompilationUnit(pkg, buf, "CompletionOnNonPublicMemberInMethod.java");
+
+		List<ICompletionProposal> proposals= computeCompletionProposals(cu, completionIndex);
+
+		List<String> expected= Arrays.asList("useMe.findMe1 - 2 elements");
+		assertProposalsExist(expected, proposals);
+	}
+
+	public void testCompletionOnSuperTypeInMethod1() throws Exception {
+		StringBuffer buf= new StringBuffer();
+		buf.append("package test;\n" +
+				"\n" +
+				"import java.io.ByteArrayInputStream;\n" +
+				"import java.io.InputStream;\n" +
+				"\n" +
+				"public class CompletionOnSupertypeInMethod {\n" +
+				"  public ByteArrayInputStream findMe = new ByteArrayInputStream(new byte[] { 0, 1, 2, 3 });\n" +
+				"\n" +
+				"  public static void method() {\n" +
+				"    final CompletionOnSupertypeInMethod useMe = new CompletionOnSupertypeInMethod();\n" +
+				"    final InputStream c = $\n" +
+				"  }\n" +
+				"}");
+
+		int completionIndex= getCompletionIndex(buf);
+		ICompilationUnit cu= getCompilationUnit(pkg, buf, "CompletionOnSupertypeInMethod.java");
+
+		List<ICompletionProposal> proposals= computeCompletionProposals(cu, completionIndex);
+
+		assertEquals(1, proposals.size());
+		assertEquals("useMe.findMe - 2 elements", proposals.get(0).getDisplayString());
+	}
+
+	public void testCompletionOnSuperTypeMemberInMethod1() throws Exception {
+		StringBuffer buf= new StringBuffer();
+		buf.append("package test;\n" +
+				"\n" +
+				"public class CompletionOnSupertypeMemberInMethod {\n" +
+				"\n" +
+				"  public static class Subtype extends CompletionOnSupertypeMemberInMethod {\n" +
+				"  }\n" +
+				"\n" +
+				"  public Boolean findMe = new Boolean();\n" +
+				"\n" +
+				"  public static void test_onAttribute() {\n" +
+				"    final Subtype useMe = new Subtype();\n" +
+				"    final Boolean c = $\n" +
+				"  }\n" +
+				"}");
+
+		int completionIndex= getCompletionIndex(buf);
+		ICompilationUnit cu= getCompilationUnit(pkg, buf, "CompletionOnSupertypeMemberInMethod.java");
+
+		List<ICompletionProposal> proposals= computeCompletionProposals(cu, completionIndex);
+
+		List<String> expected= Arrays.asList("useMe.findMe - 2 elements");
+		assertProposalsExist(expected, proposals);
+	}
+
+	private ICompilationUnit getCompilationUnit(IPackageFragment pack, StringBuffer buf, String name) throws JavaModelException {
+		return pack.createCompilationUnit(name, buf.toString().replace("$", ""), false, null);
+	}
+
+	private int getCompletionIndex(StringBuffer buf) {
+		return buf.toString().indexOf('$');
+	}
+
+	private List<ICompletionProposal> computeCompletionProposals(ICompilationUnit cu, int completionIndex) throws Exception {
+		ChainCompletionProposalComputer comp= new ChainCompletionProposalComputer();
+
+		IEditorPart editor= EditorUtility.openInEditor(cu);
+		ITextViewer viewer= new TextViewer(editor.getSite().getShell(), SWT.NONE);
+		viewer.setDocument(new Document(cu.getSource()));
+		JavaContentAssistInvocationContext ctx= new JavaContentAssistInvocationContext(viewer, completionIndex, editor);
+
+		return comp.computeCompletionProposals(ctx, null);
+	}
+
+	private void assertProposalsExist(List<String> expected, List<ICompletionProposal> proposals) {
+		for (String propDisplay : expected) {
+			assertTrue(proposals.stream().anyMatch(p -> propDisplay.equals(p.getDisplayString())));
+		}
+	}
+
+}
diff --git a/org.eclipse.jdt.ui/plugin.properties b/org.eclipse.jdt.ui/plugin.properties
index 50f0f55..6afa8e9 100644
--- a/org.eclipse.jdt.ui/plugin.properties
+++ b/org.eclipse.jdt.ui/plugin.properties
@@ -63,6 +63,7 @@
 TemplateProposalCategory= Te&mplate Proposals
 TextProposalCategory= &Word Proposals
 SWTProposalCategory= &SWT Template Proposals
+ChainProposalCategory= &Chain Template Proposals
 
 templatesViewName= Templates
 
diff --git a/org.eclipse.jdt.ui/plugin.xml b/org.eclipse.jdt.ui/plugin.xml
index 20fe97b..a5858e8 100644
--- a/org.eclipse.jdt.ui/plugin.xml
+++ b/org.eclipse.jdt.ui/plugin.xml
@@ -7217,4 +7217,20 @@
         </enabledWhen>
       </codeMiningProvider>
    </extension>
+  <extension
+        id="javaChainProposalCategory"
+        name="%ChainProposalCategory"
+        point="org.eclipse.jdt.ui.javaCompletionProposalComputer">
+        <proposalCategory icon="icons/full/eview16/package.png"/>
+  </extension>
+  <extension
+        point="org.eclipse.jdt.ui.javaCompletionProposalComputer"
+        id="org.eclipse.jdt.ui.javaCompletionProposalComputer.chain">
+     <javaCompletionProposalComputer
+           activate="true"
+           categoryId="org.eclipse.jdt.ui.javaChainProposalCategory"
+           class="org.eclipse.jdt.internal.ui.text.java.ChainCompletionProposalComputer"
+           needsSortingAfterFiltering="false">
+     </javaCompletionProposalComputer>
+  </extension>
 </plugin>
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/java/ChainCompletionProposal.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/java/ChainCompletionProposal.java
new file mode 100644
index 0000000..417dfe7
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/java/ChainCompletionProposal.java
@@ -0,0 +1,150 @@
+/**
+ * Copyright (c) 2010, 2019 Darmstadt University of Technology and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Marcel Bruch - initial API and implementation.
+ */
+package org.eclipse.jdt.internal.ui.text.java;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.eclipse.jdt.core.dom.IBinding;
+import org.eclipse.jdt.core.dom.IMethodBinding;
+
+import org.eclipse.jdt.internal.ui.text.Chain;
+import org.eclipse.jdt.internal.ui.text.ChainElement;
+import org.eclipse.jdt.internal.ui.text.template.contentassist.TemplateProposal;
+import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal;
+import org.eclipse.jface.text.DocumentEvent;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IInformationControlCreator;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.contentassist.ICompletionProposalExtension2;
+import org.eclipse.jface.text.contentassist.ICompletionProposalExtension3;
+import org.eclipse.jface.text.contentassist.ICompletionProposalExtension4;
+import org.eclipse.jface.text.contentassist.ICompletionProposalExtension6;
+import org.eclipse.jface.text.contentassist.IContextInformation;
+import org.eclipse.jface.viewers.StyledString;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+
+/**
+ * This class basically delegates all events to a {@link TemplateProposal} but provides some
+ * auxiliary methods for testing such as {@link #getChainElementNames()}. It may be extended to
+ * track user click feedback to continuously improve chain completion.
+ */
+public class ChainCompletionProposal implements IJavaCompletionProposal, ICompletionProposalExtension2,
+		ICompletionProposalExtension3, ICompletionProposalExtension4, ICompletionProposalExtension6 {
+
+	private static final int CHAIN_PROPOSAL_BOOST= 100;
+
+	private final Chain chain;
+
+	private final TemplateProposal completion;
+
+	public ChainCompletionProposal(final TemplateProposal completion, final Chain chain) {
+		this.completion= completion;
+		this.chain= chain;
+	}
+
+	public List<String> getChainElementNames() {
+		final List<String> b= new LinkedList<>();
+		for (final ChainElement edge : chain.getElements()) {
+			final IBinding bind= edge.getElementBinding();
+			final char[] name= bind instanceof IMethodBinding ? ((IMethodBinding) bind).getName().toCharArray() : bind.getName().toCharArray();
+			b.add(String.valueOf(name));
+		}
+		return b;
+	}
+
+	@Override
+	public void apply(final IDocument document) {
+		throw new IllegalStateException("Applying proposals to documents is deprecated"); //$NON-NLS-1$
+	}
+
+	@Override
+	public void apply(final ITextViewer viewer, final char trigger, final int stateMask, final int offset) {
+		completion.apply(viewer, trigger, stateMask, offset);
+	}
+
+	@Override
+	public String getAdditionalProposalInfo() {
+		return completion.getAdditionalProposalInfo();
+	}
+
+	@Override
+	public String getDisplayString() {
+		return completion.getDisplayString();
+	}
+
+	@Override
+	public IContextInformation getContextInformation() {
+		return completion.getContextInformation();
+	}
+
+	@Override
+	public int getRelevance() {
+		return -chain.getElements().size() - CHAIN_PROPOSAL_BOOST;
+	}
+
+	@Override
+	public Point getSelection(final IDocument document) {
+		return completion.getSelection(document);
+	}
+
+	@Override
+	public Image getImage() {
+		return completion.getImage();
+	}
+
+	@Override
+	public void selected(final ITextViewer viewer, final boolean smartToggle) {
+		completion.selected(viewer, smartToggle);
+
+	}
+
+	@Override
+	public void unselected(final ITextViewer viewer) {
+		completion.unselected(viewer);
+	}
+
+	@Override
+	public boolean validate(final IDocument document, final int offset, final DocumentEvent event) {
+		return completion.validate(document, offset, event);
+	}
+
+	@Override
+	public String toString() {
+		return completion.getDisplayString();
+	}
+
+	@Override
+	public StyledString getStyledDisplayString() {
+		return completion.getStyledDisplayString();
+	}
+
+	@Override
+	public boolean isAutoInsertable() {
+		return completion.isAutoInsertable();
+	}
+
+	@Override
+	public IInformationControlCreator getInformationControlCreator() {
+		return completion.getInformationControlCreator();
+	}
+
+	@Override
+	public CharSequence getPrefixCompletionText(final IDocument document, final int completionOffset) {
+		return completion.getPrefixCompletionText(document, completionOffset);
+	}
+
+	@Override
+	public int getPrefixCompletionStart(final IDocument document, final int completionOffset) {
+		return completion.getPrefixCompletionStart(document, completionOffset);
+	}
+}
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/java/ChainCompletionProposalComputer.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/java/ChainCompletionProposalComputer.java
new file mode 100644
index 0000000..84bcf66
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/java/ChainCompletionProposalComputer.java
@@ -0,0 +1,200 @@
+/**
+ * Copyright (c) 2010, 2019 Darmstadt University of Technology and others
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Marcel Bruch - initial API and implementation.
+ */
+package org.eclipse.jdt.internal.ui.text.java;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.NullProgressMonitor;
+
+import org.eclipse.jface.text.contentassist.ICompletionProposal;
+import org.eclipse.jface.text.contentassist.IContextInformation;
+
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.core.dom.AST;
+import org.eclipse.jdt.core.dom.IBinding;
+import org.eclipse.jdt.core.dom.ITypeBinding;
+import org.eclipse.jdt.core.manipulation.JavaManipulation;
+
+import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.ASTCreator;
+
+import org.eclipse.jdt.ui.PreferenceConstants;
+import org.eclipse.jdt.ui.text.java.CompletionProposalCollector;
+import org.eclipse.jdt.ui.text.java.ContentAssistInvocationContext;
+import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal;
+import org.eclipse.jdt.ui.text.java.IJavaCompletionProposalComputer;
+import org.eclipse.jdt.ui.text.java.JavaContentAssistInvocationContext;
+
+import org.eclipse.jdt.internal.ui.text.Chain;
+import org.eclipse.jdt.internal.ui.text.ChainElement;
+import org.eclipse.jdt.internal.ui.text.ChainFinder;
+import org.eclipse.jdt.internal.ui.text.TypeBindingAnalyzer;
+import org.eclipse.jdt.internal.ui.text.template.contentassist.TemplateProposal;
+
+public class ChainCompletionProposalComputer implements IJavaCompletionProposalComputer {
+
+	public static final String CATEGORY_ID= "org.eclipse.jdt.ui.javaChainProposalCategory"; //$NON-NLS-1$
+
+	private JavaContentAssistInvocationContext ctx;
+
+	private CompletionProposalCollector collector;
+
+	private List<ChainElement> entrypoints;
+
+	private String error;
+
+	private IJavaElement invocationSite;
+
+	private String[] excludedTypes;
+
+	@Override
+	public List<ICompletionProposal> computeCompletionProposals(final ContentAssistInvocationContext context,
+			final IProgressMonitor monitor) {
+
+		if (!initializeRequiredContext(context)) {
+			return Collections.emptyList();
+		}
+		if (!shouldPerformCompletionOnExpectedType()) {
+			return Collections.emptyList();
+		}
+		if (!findEntrypoints()) {
+			return Collections.emptyList();
+		}
+		return executeCallChainSearch();
+	}
+
+	private boolean initializeRequiredContext(final ContentAssistInvocationContext context) {
+		if (!(context instanceof JavaContentAssistInvocationContext)) {
+			return false;
+		}
+
+		ctx= (JavaContentAssistInvocationContext) context;
+		collector= new CompletionProposalCollector(ctx.getCompilationUnit());
+		collector.setInvocationContext(ctx);
+		ICompilationUnit cu= ctx.getCompilationUnit();
+		int offset= ctx.getInvocationOffset();
+		try {
+			cu.codeComplete(offset, collector, new NullProgressMonitor());
+		} catch (JavaModelException e) {
+			// try to continue
+		}
+
+		invocationSite= ctx.getCoreContext().getEnclosingElement();
+		return true;
+	}
+
+	private boolean shouldPerformCompletionOnExpectedType() {
+		AST ast= ASTCreator.createAST(ctx.getCompilationUnit(), null).getAST();
+		ITypeBinding binding= ast.resolveWellKnownType(TypeBindingAnalyzer.getExpectedFullyQualifiedTypeName(ctx.getCoreContext()));
+		return binding != null || TypeBindingAnalyzer.getExpectedType(ctx.getProject(), ctx.getCoreContext()) != null;
+	}
+
+	private boolean findEntrypoints() {
+		excludedTypes= JavaManipulation.getPreference(PreferenceConstants.PREF_IGNORED_TYPES, ctx.getProject()).split("\\|"); //$NON-NLS-1$
+		for (int i= 0; i < excludedTypes.length; ++i) {
+			excludedTypes[i]= "L" + excludedTypes[i].replace('.', '/'); //$NON-NLS-1$
+		}
+
+		entrypoints= new LinkedList<>();
+		List<IJavaElement> elements= new LinkedList<>();
+		for (IJavaCompletionProposal prop : collector.getJavaCompletionProposals()) {
+			if (prop instanceof AbstractJavaCompletionProposal) {
+				AbstractJavaCompletionProposal aprop= (AbstractJavaCompletionProposal) prop;
+				IJavaElement element= aprop.getJavaElement();
+				if (element != null) {
+					elements.add(element);
+				} else {
+					IJavaElement[] visibleElements= ctx.getCoreContext().getVisibleElements(null);
+					for (IJavaElement ve : visibleElements) {
+						if (ve.getElementName().equals(aprop.getReplacementString())) {
+							elements.add(ve);
+						}
+					}
+				}
+			}
+		}
+
+		IBinding[] bindings= TypeBindingAnalyzer.resolveBindingsForTypes(ctx.getCompilationUnit(), elements.toArray(new IJavaElement[0]));
+		for (IBinding b : bindings) {
+			if (b != null && matchesExpectedPrefix(b) && !ChainFinder.isFromExcludedType(Arrays.asList(excludedTypes), b)) {
+				entrypoints.add(new ChainElement(b, false));
+			}
+		}
+
+		return !entrypoints.isEmpty();
+	}
+
+	private boolean matchesExpectedPrefix(final IBinding binding) {
+		String prefix= String.valueOf(ctx.getCoreContext().getToken());
+		return String.valueOf(binding.getName()).startsWith(prefix);
+	}
+
+	private List<ICompletionProposal> executeCallChainSearch() {
+		final int maxChains= Integer.parseInt(JavaManipulation.getPreference(PreferenceConstants.PREF_MAX_CHAINS, ctx.getProject()));
+		final int minDepth= Integer.parseInt(JavaManipulation.getPreference(PreferenceConstants.PREF_MIN_CHAIN_LENGTH, ctx.getProject()));
+		final int maxDepth= Integer.parseInt(JavaManipulation.getPreference(PreferenceConstants.PREF_MAX_CHAIN_LENGTH, ctx.getProject()));
+
+		final List<ITypeBinding> expectedTypes= TypeBindingAnalyzer.resolveBindingsForExpectedTypes(ctx.getProject(), ctx.getCompilationUnit(), ctx.getCoreContext());
+		final ChainFinder finder= new ChainFinder(expectedTypes, Arrays.asList(excludedTypes), invocationSite);
+		try {
+			ExecutorService executor= Executors.newSingleThreadExecutor();
+			Future<?> future= executor.submit(() -> finder.startChainSearch(entrypoints, maxChains, minDepth, maxDepth));
+			long timeout= Long.parseLong(JavaManipulation.getPreference(PreferenceConstants.PREF_TIMEOUT, ctx.getProject()));
+			future.get(timeout, TimeUnit.SECONDS);
+		} catch (final Exception e) {
+			setError("Timeout during call chain computation."); //$NON-NLS-1$
+		}
+		return buildCompletionProposals(finder.getChains());
+	}
+
+	private List<ICompletionProposal> buildCompletionProposals(final List<Chain> chains) {
+		final List<ICompletionProposal> proposals= new LinkedList<>();
+		for (final Chain chain : chains) {
+			final TemplateProposal proposal= ChainCompletionTemplateBuilder.create(chain, ctx);
+			final ChainCompletionProposal completionProposal= new ChainCompletionProposal(proposal, chain);
+			proposals.add(completionProposal);
+		}
+		return proposals;
+	}
+
+	private void setError(final String errorMessage) {
+		error= errorMessage;
+	}
+
+	@Override
+	public List<IContextInformation> computeContextInformation(final ContentAssistInvocationContext context,
+			final IProgressMonitor monitor) {
+		return Collections.emptyList();
+	}
+
+	@Override
+	public void sessionStarted() {
+		setError(null);
+	}
+
+	@Override
+	public String getErrorMessage() {
+		return error;
+	}
+
+	@Override
+	public void sessionEnded() {
+	}
+}
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/java/ChainCompletionTemplateBuilder.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/java/ChainCompletionTemplateBuilder.java
new file mode 100644
index 0000000..1b2656f
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/java/ChainCompletionTemplateBuilder.java
@@ -0,0 +1,166 @@
+/**
+ * Copyright (c) 2010, 2019 Darmstadt University of Technology and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Marcel Bruch - initial API and implementation.
+ *    Olav Lenz - externalize Strings.
+ */
+package org.eclipse.jdt.internal.ui.text.java;
+
+import static java.text.MessageFormat.format;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.swt.graphics.Image;
+
+import org.eclipse.text.templates.ContextTypeRegistry;
+
+import org.eclipse.jface.text.Region;
+import org.eclipse.jface.text.templates.DocumentTemplateContext;
+import org.eclipse.jface.text.templates.Template;
+import org.eclipse.jface.text.templates.TemplateContextType;
+
+import org.eclipse.jdt.core.dom.IMethodBinding;
+import org.eclipse.jdt.core.dom.ITypeBinding;
+
+import org.eclipse.jdt.internal.core.manipulation.BindingLabelProviderCore;
+import org.eclipse.jdt.internal.core.manipulation.JavaElementLabelsCore;
+import org.eclipse.jdt.internal.corext.template.java.JavaContext;
+import org.eclipse.jdt.internal.corext.template.java.JavaContextType;
+
+import org.eclipse.jdt.ui.text.java.JavaContentAssistInvocationContext;
+
+import org.eclipse.jdt.internal.ui.JavaPlugin;
+import org.eclipse.jdt.internal.ui.JavaPluginImages;
+import org.eclipse.jdt.internal.ui.text.Chain;
+import org.eclipse.jdt.internal.ui.text.ChainElement;
+import org.eclipse.jdt.internal.ui.text.template.contentassist.TemplateProposal;
+
+/**
+ * Creates the templates for a given call chain.
+ */
+public final class ChainCompletionTemplateBuilder {
+
+	private ChainCompletionTemplateBuilder() {
+	}
+
+	public static TemplateProposal create(final Chain chain, final JavaContentAssistInvocationContext context) {
+		final String title= createChainCode(chain, true, 0);
+		final String body= createChainCode(chain, false, chain.getExpectedDimensions());
+
+		final Template template= new Template(title,
+				format("{0,choice,1#1 element|1<{0,number,integer} elements}", chain.getElements().size()), //$NON-NLS-1$
+				"java", body, false); //$NON-NLS-1$
+		return createTemplateProposal(template, context);
+	}
+
+	private static String createChainCode(final Chain chain, final boolean createAsTitle, final int expectedDimension) {
+		final Map<String, Integer> varNames= new HashMap<>();
+		final StringBuilder sb= new StringBuilder(64);
+		for (final ChainElement edge : chain.getElements()) {
+			switch (edge.getElementType()) {
+				case FIELD:
+				case LOCAL_VARIABLE:
+					appendVariableString(edge, sb);
+					break;
+				case METHOD:
+					final IMethodBinding method= edge.getElementBinding();
+					if (createAsTitle) {
+						sb.append(BindingLabelProviderCore.getBindingLabel(method, JavaElementLabelsCore.ALL_DEFAULT));
+					} else {
+						sb.append(method.getName());
+						appendParameters(sb, method, varNames);
+					}
+					break;
+				default:
+			}
+			final boolean appendVariables= !createAsTitle;
+			appendArrayDimensions(sb, edge.getReturnTypeDimension(), expectedDimension, appendVariables, varNames);
+			sb.append("."); //$NON-NLS-1$
+		}
+		deleteLastChar(sb);
+		return sb.toString();
+	}
+
+	private static void appendVariableString(final ChainElement edge, final StringBuilder sb) {
+		if (edge.requiresThisForQualification() && sb.length() == 0) {
+			sb.append("this."); //$NON-NLS-1$
+		}
+		sb.append((edge.getElementBinding()).getName());
+	}
+
+	private static void appendParameters(final StringBuilder sb, final IMethodBinding method,
+			final Map<String, Integer> varNames) {
+		sb.append("("); //$NON-NLS-1$
+		for (final ITypeBinding parameter : method.getParameterTypes()) {
+			String tmp= String.valueOf(parameter.getName());
+			String parameterName= tmp.substring(0, 1).toLowerCase() + tmp.substring(1);
+			int index= parameterName.indexOf("<"); //$NON-NLS-1$
+			if (index != -1) {
+				parameterName= parameterName.substring(0, index);
+			}
+			appendTemplateVariable(sb, parameterName, varNames);
+			sb.append(", "); //$NON-NLS-1$
+		}
+		if (method.getParameterTypes().length > 0) {
+			deleteLastChar(sb);
+			deleteLastChar(sb);
+		}
+		sb.append(")"); //$NON-NLS-1$
+	}
+
+	private static void appendTemplateVariable(final StringBuilder sb, final String varname,
+			final Map<String, Integer> varNames) {
+		int val= varNames.containsKey(varname) ? varNames.get(varname).intValue() : 0;
+		varNames.put(varname, val + 1);
+		sb.append("${").append(varname); //$NON-NLS-1$
+		final int count= varNames.get(varname);
+		if (count > 1) {
+			sb.append(count);
+		}
+		sb.append("}"); //$NON-NLS-1$
+	}
+
+	private static void appendArrayDimensions(final StringBuilder sb, final int dimension, final int expectedDimension,
+			final boolean appendVariables, final Map<String, Integer> varNames) {
+		for (int i= dimension; i-- > expectedDimension;) {
+			sb.append("["); //$NON-NLS-1$
+			if (appendVariables) {
+				appendTemplateVariable(sb, "i", varNames); //$NON-NLS-1$
+			}
+			sb.append("]"); //$NON-NLS-1$
+		}
+	}
+
+	private static StringBuilder deleteLastChar(final StringBuilder sb) {
+		return sb.deleteCharAt(sb.length() - 1);
+	}
+
+	private static TemplateProposal createTemplateProposal(final Template template,
+			final JavaContentAssistInvocationContext contentAssistContext) {
+		final DocumentTemplateContext templateContext= createJavaContext(contentAssistContext);
+		final Region region= new Region(templateContext.getCompletionOffset(), templateContext.getCompletionLength());
+		final TemplateProposal proposal= new TemplateProposal(template, templateContext, region,
+				getChainCompletionIcon());
+		return proposal;
+	}
+
+	private static JavaContext createJavaContext(final JavaContentAssistInvocationContext contentAssistContext) {
+		final ContextTypeRegistry templateContextRegistry= JavaPlugin.getDefault().getTemplateContextRegistry();
+		final TemplateContextType templateContextType= templateContextRegistry.getContextType(JavaContextType.ID_ALL);
+		final JavaContext javaTemplateContext= new JavaContext(templateContextType, contentAssistContext.getDocument(),
+				contentAssistContext.getInvocationOffset(), contentAssistContext.getCoreContext().getToken().length,
+				contentAssistContext.getCompilationUnit());
+		javaTemplateContext.setForceEvaluation(true);
+		return javaTemplateContext;
+	}
+
+	private static Image getChainCompletionIcon() {
+		return JavaPlugin.getImageDescriptorRegistry().get(JavaPluginImages.DESC_MISC_PUBLIC);
+	}
+}
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/PreferenceConstants.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/PreferenceConstants.java
index cab794d..45fd68d 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/PreferenceConstants.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/PreferenceConstants.java
@@ -3874,6 +3874,61 @@
 	 */
 	public static final String EDITOR_JAVA_CODEMINING_SHOW_PARAMETER_NAMES = "java.codemining.parameterNames"; //$NON-NLS-1$
 
+	/**
+	 * A named preference that stores the maximum number of chain completions
+	 * to be proposed at one time.
+	 * <p>
+	 * Value is of type <code>Integer</code>
+	 * </p>
+	 *
+	 * @since 3.18
+	 */
+	public static final String PREF_MAX_CHAINS = "recommenders.chain.max_chains"; //$NON-NLS-1$
+
+    /**
+     * A named preference that stores the minimum number of chain sequences
+     * for a given completion.
+     * <p>
+     * Value is of type <code>Integer</code>
+     * </p>
+     *
+	 * @since 3.18
+	 */
+
+    public static final String PREF_MIN_CHAIN_LENGTH = "recommenders.chain.min_chain_length"; //$NON-NLS-1$
+    /**
+     * A named preference that stores the maximum number of chain sequences
+     * for a given completion.
+     * <p>
+     * Value is of type <code>Integer</code>
+     * </p>
+     *
+     * @since 3.18
+     */
+    public static final String PREF_MAX_CHAIN_LENGTH = "recommenders.chain.max_chain_length"; //$NON-NLS-1$
+
+    /**
+     * A named preference that stores the amount of time (in seconds) to
+     * allow for chain completion processing. The chain completion processor
+     * timeout value.
+     * <p>
+     * Value is of type <code>Integer</code>
+     * </p>
+     *
+	 * @since 3.18
+	 */
+    public static final String PREF_TIMEOUT = "recommenders.chain.timeout"; //$NON-NLS-1$
+
+    /**
+	 * A named preference that stores a '|' separated list of types to exclude
+	 * from chain completion processing.
+	 * <p>
+	 * Value is of type <code>String</code>
+	 * </p>
+	 *
+	 * @since 3.18
+	 */
+    public static final String PREF_IGNORED_TYPES = "recommenders.chain.ignore_types"; //$NON-NLS-1$
 
 	/**
 	 * Initializes the given preference store with the default values.
@@ -4032,6 +4087,11 @@
 		store.setDefault(PreferenceConstants.CODEASSIST_AUTOACTIVATION_DELAY, 0);
 		store.setDefault(PreferenceConstants.CODEASSIST_AUTOINSERT, true);
 		store.setDefault(PreferenceConstants.CODEASSIST_DISABLE_COMPLETION_PROPOSAL_TRIGGER_CHARS, false);
+		store.setDefault(PreferenceConstants.PREF_MIN_CHAIN_LENGTH, 2);
+		store.setDefault(PreferenceConstants.PREF_MAX_CHAIN_LENGTH, 4);
+		store.setDefault(PreferenceConstants.PREF_MAX_CHAINS, 20);
+		store.setDefault(PreferenceConstants.PREF_TIMEOUT, 3);
+		store.setDefault(PreferenceConstants.PREF_IGNORED_TYPES, "java.lang.Object"); //$NON-NLS-1$
 
 		// Set the value for the deprecated color constants
 		initializeDeprecatedColorConstants(store);
@@ -4045,7 +4105,7 @@
 		store.setDefault(PreferenceConstants.CODEASSIST_FILL_ARGUMENT_NAMES, true);
 		store.setDefault(PreferenceConstants.CODEASSIST_GUESS_METHOD_ARGUMENTS, false);
 		store.setDefault(PreferenceConstants.CODEASSIST_PREFIX_COMPLETION, false);
-		store.setDefault(PreferenceConstants.CODEASSIST_EXCLUDED_CATEGORIES, "org.eclipse.jdt.ui.textProposalCategory\0org.eclipse.jdt.ui.javaTypeProposalCategory\0org.eclipse.jdt.ui.javaNoTypeProposalCategory\0"); //$NON-NLS-1$
+		store.setDefault(PreferenceConstants.CODEASSIST_EXCLUDED_CATEGORIES, "org.eclipse.jdt.ui.textProposalCategory\0org.eclipse.jdt.ui.javaTypeProposalCategory\0org.eclipse.jdt.ui.javaNoTypeProposalCategory\0org.eclipse.jdt.ui.javaChainProposalCategory\0"); //$NON-NLS-1$
 		store.setDefault(PreferenceConstants.CODEASSIST_CATEGORY_ORDER, "org.eclipse.jdt.ui.spellingProposalCategory:65545\0org.eclipse.jdt.ui.javaTypeProposalCategory:65540\0org.eclipse.jdt.ui.javaNoTypeProposalCategory:65539\0org.eclipse.jdt.ui.textProposalCategory:65541\0org.eclipse.jdt.ui.javaAllProposalCategory:65542\0org.eclipse.jdt.ui.templateProposalCategory:2\0org.eclipse.jdt.ui.swtProposalCategory:3\0"); //$NON-NLS-1$
 		store.setDefault(PreferenceConstants.CODEASSIST_LRU_HISTORY, ""); //$NON-NLS-1$
 		store.setDefault(PreferenceConstants.CODEASSIST_SORTER, "org.eclipse.jdt.ui.RelevanceSorter"); //$NON-NLS-1$