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$