Skip to content

Commit

Permalink
Completion to add override for inherited method
Browse files Browse the repository at this point in the history
eg. try completion at the `|` for the following cases:

```java
public Foo {
    |
}
```

```java
public Foo {
    toStr|
}
```

Closes eclipse-jdt#859

Signed-off-by: David Thompson <davthomp@redhat.com>
  • Loading branch information
datho7561 committed Sep 27, 2024
1 parent 8ae7a9a commit b089abb
Show file tree
Hide file tree
Showing 2 changed files with 320 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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<String> 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<String> 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<IType> findTypes(String namePrefix, String packageName) {
return findTypes(namePrefix, IJavaSearchConstants.TYPE, packageName);
}
Expand Down Expand Up @@ -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))
);
}

}
Original file line number Diff line number Diff line change
@@ -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('/', '.');
}

}

0 comments on commit b089abb

Please sign in to comment.