From b559035f762b0b3e65a45bcd5b4a7460ec026af4 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Fri, 27 Sep 2024 16:09:55 -0400 Subject: [PATCH] Completion to add override for inherited method eg. try completion at the `|` for the following cases: ```java public Foo { | } ``` ```java public Foo { toStr| } ``` Closes #859 Signed-off-by: David Thompson --- .../codeassist/DOMCompletionEngine.java | 132 ++++++++++++ .../DOMCompletionEngineBuilder.java | 188 ++++++++++++++++++ 2 files changed, 320 insertions(+) create mode 100644 org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/DOMCompletionEngineBuilder.java diff --git a/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/DOMCompletionEngine.java b/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/DOMCompletionEngine.java index d1d75e4c7fa..0da481c1224 100644 --- a/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/DOMCompletionEngine.java +++ b/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/DOMCompletionEngine.java @@ -42,6 +42,7 @@ import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.FieldAccess; +import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.IBinding; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.IPackageBinding; @@ -50,12 +51,14 @@ import org.eclipse.jdt.core.dom.LambdaExpression; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.MethodInvocation; +import org.eclipse.jdt.core.dom.Modifier; import org.eclipse.jdt.core.dom.ModuleDeclaration; import org.eclipse.jdt.core.dom.Name; import org.eclipse.jdt.core.dom.NodeFinder; import org.eclipse.jdt.core.dom.PrimitiveType; import org.eclipse.jdt.core.dom.QualifiedName; import org.eclipse.jdt.core.dom.SimpleName; +import org.eclipse.jdt.core.dom.SimpleType; import org.eclipse.jdt.core.dom.Statement; import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.VariableDeclaration; @@ -305,6 +308,30 @@ public void acceptConstructor(int modifiers, char[] simpleTypeName, int paramete if (context instanceof ModuleDeclaration mod) { findModules(this.prefix.toCharArray(), this.modelUnit.getJavaProject(), this.assistOptions, Set.of(mod.getName().toString())); } + if (context instanceof SimpleName) { + if (context.getParent() instanceof SimpleType simpleType + && simpleType.getParent() instanceof FieldDeclaration fieldDeclaration + && fieldDeclaration.getParent() instanceof AbstractTypeDeclaration typeDecl) { + // eg. + // public class Foo { + // ba| + // } + ITypeBinding typeDeclBinding = typeDecl.resolveBinding(); + findOverridableMethods(typeDeclBinding, this.modelUnit.getJavaProject(), context); + suggestDefaultCompletions = false; + } + } + if (context instanceof AbstractTypeDeclaration typeDecl) { + // eg. + // public class Foo { + // | + // } + ITypeBinding typeDeclBinding = typeDecl.resolveBinding(); + findOverridableMethods(typeDeclBinding, this.modelUnit.getJavaProject(), null); + suggestDefaultCompletions = false; + suggestPackageCompletions = false; + computeSuitableBindingFromContext = false; + } ASTNode current = this.toComplete; @@ -366,6 +393,72 @@ public void acceptConstructor(int modifiers, char[] simpleTypeName, int paramete this.requestor.endReporting(); } + private void findOverridableMethods(ITypeBinding typeBinding, IJavaProject javaProject, ASTNode toReplace) { + String originalPackageKey = typeBinding.getPackage().getKey(); + Set alreadySuggestedMethodKeys = new HashSet<>(); + if (typeBinding.getSuperclass() != null) { + findOverridableMethods0(typeBinding.getSuperclass(), alreadySuggestedMethodKeys, javaProject, originalPackageKey, toReplace); + } + for (ITypeBinding superInterface : typeBinding.getInterfaces()) { + findOverridableMethods0(superInterface, alreadySuggestedMethodKeys, javaProject, originalPackageKey, toReplace); + } + } + + private void findOverridableMethods0(ITypeBinding typeBinding, Set alreadySuggestedKeys, IJavaProject javaProject, String originalPackageKey, ASTNode toReplace) { + next : for (IMethodBinding method : typeBinding.getDeclaredMethods()) { + if (alreadySuggestedKeys.contains(method.getKey())) { + continue next; + } + if (method.isSynthetic() || method.isConstructor() + || (this.assistOptions.checkDeprecation && method.isDeprecated()) + || (method.getModifiers() & Modifier.STATIC) != 0 + || (method.getModifiers() & Modifier.PRIVATE) != 0 + || ((method.getModifiers() & (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED)) == 0) && !typeBinding.getPackage().getKey().equals(originalPackageKey)) { + continue next; + } + alreadySuggestedKeys.add(method.getKey()); + if ((method.getModifiers() & Modifier.FINAL) != 0) { + continue next; + } + if (isFailedMatch(this.prefix.toCharArray(), method.getName().toCharArray())) { + continue next; + } + InternalCompletionProposal proposal = createProposal(CompletionProposal.METHOD_DECLARATION); + proposal.setReplaceRange(this.offset, this.offset); + if (toReplace != null) { + proposal.setReplaceRange(toReplace.getStartPosition(), toReplace.getStartPosition() + toReplace.getLength()); + } + proposal.setName(method.getName().toCharArray()); + proposal.setFlags(method.getModifiers()); + proposal.setTypeName(method.getReturnType().getName().toCharArray()); + proposal.setDeclarationPackageName(typeBinding.getPackage().getName().toCharArray()); + proposal.setDeclarationTypeName(typeBinding.getQualifiedName().toCharArray()); + proposal.setDeclarationSignature(DOMCompletionEngineBuilder.getSignature(method.getDeclaringClass()).toCharArray()); + proposal.setKey(method.getKey().toCharArray()); + proposal.setSignature(DOMCompletionEngineBuilder.getSignature(method).toCharArray()); + proposal.setParameterNames(Stream.of(method.getParameterNames()).map(name -> name.toCharArray()).toArray(char[][]::new)); + + int relevance = RelevanceConstants.R_DEFAULT + + RelevanceConstants.R_RESOLVED + + RelevanceConstants.R_INTERESTING + + RelevanceConstants.R_METHOD_OVERIDE + + ((method.getModifiers() & Modifier.ABSTRACT) != 0 ? RelevanceConstants.R_ABSTRACT_METHOD : 0) + + RelevanceConstants.R_NON_RESTRICTED; + proposal.setRelevance(relevance); + + StringBuilder completion = new StringBuilder(); + DOMCompletionEngineBuilder.createMethod(method, completion); + proposal.setCompletion(completion.toString().toCharArray()); + this.requestor.accept(proposal); + } + if (typeBinding.getSuperclass() != null) { + findOverridableMethods0(typeBinding.getSuperclass(), alreadySuggestedKeys, javaProject, originalPackageKey, toReplace); + } + for (ITypeBinding superInterface : typeBinding.getInterfaces()) { + findOverridableMethods0(superInterface, alreadySuggestedKeys, javaProject, originalPackageKey, toReplace); + } + } + private Stream findTypes(String namePrefix, String packageName) { return findTypes(namePrefix, IJavaSearchConstants.TYPE, packageName); } @@ -688,4 +781,43 @@ private CompletionProposal toModuleCompletion(String moduleName, char[] prefix) proposal.setRequiredProposals(new CompletionProposal[0]); return proposal; } + + /** + * Returns an internal completion proposal of the given kind. + * + * Inspired by {@link CompletionEngine#createProposal} + * + * @param kind the kind of completion proposal (see the constants in {@link CompletionProposal}) + * @return an internal completion proposal of the given kind + */ + protected InternalCompletionProposal createProposal(int kind) { + InternalCompletionProposal proposal = (InternalCompletionProposal) CompletionProposal.create(kind, this.offset); + proposal.nameLookup = this.nameEnvironment.nameLookup; + proposal.completionEngine = this.nestedEngine; + return proposal; + } + + /** + * Returns true if the orphaned content DOESN'T match the given name (the completion suggestion), + * according to the matching rules the user has configured. + * + * Inspired by {@link CompletionEngine#isFailedMatch}. + * However, this version also checks that the length of the orphaned content is not longer than then suggestion. + * + * @param orphanedContent the orphaned content to be completed + * @param name the completion suggestion + * @return true if the orphaned content DOESN'T match the given name + */ + protected boolean isFailedMatch(char[] orphanedContent, char[] name) { + if (name.length < orphanedContent.length) { + return true; + } + return !( + (this.assistOptions.substringMatch && CharOperation.substringMatch(orphanedContent, name)) + || (this.assistOptions.camelCaseMatch && CharOperation.camelCaseMatch(orphanedContent, name)) + || (CharOperation.prefixEquals(orphanedContent, name, false)) + || (this.assistOptions.subwordMatch && CharOperation.subWordMatch(orphanedContent, name)) + ); + } + } diff --git a/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/DOMCompletionEngineBuilder.java b/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/DOMCompletionEngineBuilder.java new file mode 100644 index 00000000000..5d9e179c9b5 --- /dev/null +++ b/org.eclipse.jdt.core/codeassist/org/eclipse/jdt/internal/codeassist/DOMCompletionEngineBuilder.java @@ -0,0 +1,188 @@ +/******************************************************************************* + * Copyright (c) 2024 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 accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.internal.codeassist; + +import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; + +/** + * Builders adapted from org.eclipse.jdt.internal.codeassist.CompletionEngine in order to work with IBindings + */ +class DOMCompletionEngineBuilder { + + private static final String EXTENDS = "extends"; //$NON-NLS-1$ + private static final String THROWS = "throws"; //$NON-NLS-1$ + private static final String SUPER = "super"; //$NON-NLS-1$ + + static void createMethod(IMethodBinding methodBinding, StringBuilder completion) { + + // Modifiers + // flush uninteresting modifiers + int insertedModifiers = methodBinding.getModifiers() + & ~(ClassFileConstants.AccNative | ClassFileConstants.AccAbstract); + if (insertedModifiers != ClassFileConstants.AccDefault) { + ASTNode.printModifiers(insertedModifiers, completion); + } + + // Type parameters + + ITypeBinding[] typeVariableBindings = methodBinding.getTypeParameters(); + if (typeVariableBindings != null && typeVariableBindings.length != 0) { + completion.append('<'); + for (int i = 0; i < typeVariableBindings.length; i++) { + if (i != 0) { + completion.append(','); + completion.append(' '); + } + createTypeVariable(typeVariableBindings[i], completion); + } + completion.append('>'); + completion.append(' '); + } + + // Return type + createType(methodBinding.getReturnType(), completion); + completion.append(' '); + + // Selector (name) + completion.append(methodBinding.getName()); + + completion.append('('); + + // Parameters + ITypeBinding[] parameterTypes = methodBinding.getParameterTypes(); + String[] parameterNames = methodBinding.getParameterNames(); + int length = parameterTypes.length; + for (int i = 0; i < length; i++) { + if (i != 0) { + completion.append(','); + completion.append(' '); + } + createType(parameterTypes[i], completion); + completion.append(' '); + if (parameterNames != null) { + completion.append(parameterNames[i]); + } else { + completion.append('%'); + } + } + + completion.append(')'); + + // Exceptions + ITypeBinding[] exceptions = methodBinding.getExceptionTypes(); + + if (exceptions != null && exceptions.length > 0) { + completion.append(' '); + completion.append(THROWS); + completion.append(' '); + for (int i = 0; i < exceptions.length; i++) { + if (i != 0) { + completion.append(' '); + completion.append(','); + } + createType(exceptions[i], completion); + } + } + } + + static void createType(ITypeBinding type, StringBuilder completion) { + if (type.isWildcardType() || type.isIntersectionType()) { + completion.append('?'); + if (type.isUpperbound()) { + completion.append(' '); + completion.append(EXTENDS); + completion.append(' '); + createType(type.getBound(), completion); + if (type.getTypeBounds() != null) { + for (ITypeBinding bound : type.getTypeBounds()) { + completion.append(' '); + completion.append('&'); + completion.append(' '); + createType(bound, completion); + } + } + } else { + completion.append(' '); + completion.append(SUPER); + completion.append(' '); + createType(type.getBound(), completion); + } + } else if (type.isArray()) { + createType(type.getElementType(), completion); + int dim = type.getDimensions(); + for (int i = 0; i < dim; i++) { + completion.append("[]"); //$NON-NLS-1$ + } + } else if (type.isParameterizedType()) { + if (type.isMember()) { + createType(type.getDeclaringClass(), completion); + completion.append('.'); + completion.append(type.getName()); + } else { + completion.append(type.getQualifiedName()); + } + ITypeBinding[] typeArguments = type.getTypeArguments(); + if (typeArguments != null) { + completion.append('<'); + for (int i = 0, length = typeArguments.length; i < length; i++) { + if (i != 0) + completion.append(','); + createType(typeArguments[i], completion); + } + completion.append('>'); + } + } else { + completion.append(type.getQualifiedName()); + } + } + + static void createTypeVariable(ITypeBinding typeVariable, StringBuilder completion) { + completion.append(typeVariable.getName()); + + if (typeVariable.getSuperclass() != null + && typeVariable.getTypeBounds()[0].getKey().equals(typeVariable.getSuperclass().getKey())) { + completion.append(' '); + completion.append(EXTENDS); + completion.append(' '); + createType(typeVariable.getSuperclass(), completion); + } + if (typeVariable.getInterfaces() != null) { + if (!typeVariable.getTypeBounds()[0].getKey().equals(typeVariable.getSuperclass().getKey())) { + completion.append(' '); + completion.append(EXTENDS); + completion.append(' '); + } + for (int i = 0, length = typeVariable.getInterfaces().length; i < length; i++) { + if (i > 0 || typeVariable.getTypeBounds()[0].getKey().equals(typeVariable.getSuperclass().getKey())) { + completion.append(' '); + completion.append(EXTENDS); + completion.append(' '); + } + createType(typeVariable.getInterfaces()[i], completion); + } + } + } + + static String getSignature(IMethodBinding methodBinding) { + return methodBinding.getKey().replace('/', '.'); + } + + static String getSignature(ITypeBinding methodBinding) { + return methodBinding.getKey().replace('/', '.'); + } + +}