Skip to content

Commit

Permalink
GH-1298: added bean name code completion and navigation support for n…
Browse files Browse the repository at this point in the history
…ame attribute of resource annotation
  • Loading branch information
martinlippert committed Jul 19, 2024
1 parent 6b0bd5d commit bc5006f
Show file tree
Hide file tree
Showing 7 changed files with 478 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.springframework.ide.vscode.boot.java.beans.DependsOnCompletionProcessor;
import org.springframework.ide.vscode.boot.java.beans.ProfileCompletionProvider;
import org.springframework.ide.vscode.boot.java.beans.QualifierCompletionProvider;
import org.springframework.ide.vscode.boot.java.beans.ResourceCompletionProvider;
import org.springframework.ide.vscode.boot.java.data.DataRepositoryCompletionProcessor;
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCompletionEngine;
import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider;
Expand Down Expand Up @@ -121,6 +122,9 @@ BootJavaCompletionEngine javaCompletionEngine(
providers.put(Annotations.QUALIFIER, new AnnotationAttributeCompletionProcessor(javaProjectFinder, Map.of("value", new QualifierCompletionProvider(springIndex))));
providers.put(Annotations.PROFILE, new AnnotationAttributeCompletionProcessor(javaProjectFinder, Map.of("value", new ProfileCompletionProvider(springIndex))));

providers.put(Annotations.RESOURCE_JAVAX, new AnnotationAttributeCompletionProcessor(javaProjectFinder, Map.of("name", new ResourceCompletionProvider(springIndex))));
providers.put(Annotations.RESOURCE_JAKARTA, new AnnotationAttributeCompletionProcessor(javaProjectFinder, Map.of("name", new ResourceCompletionProvider(springIndex))));

return new BootJavaCompletionEngine(cuCache, providers, snippetManager);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import org.springframework.ide.vscode.boot.java.JavaDefinitionHandler;
import org.springframework.ide.vscode.boot.java.beans.DependsOnDefinitionProvider;
import org.springframework.ide.vscode.boot.java.beans.QualifierDefinitionProvider;
import org.springframework.ide.vscode.boot.java.beans.ResourceDefinitionProvider;
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeActionProvider;
import org.springframework.ide.vscode.boot.java.handlers.BootJavaReconcileEngine;
import org.springframework.ide.vscode.boot.java.handlers.JavaCodeActionHandler;
Expand Down Expand Up @@ -397,6 +398,7 @@ JavaDefinitionHandler javaDefinitionHandler(CompilationUnitCache cuCache, JavaPr
return new JavaDefinitionHandler(cuCache, projectFinder, List.of(
new ValueDefinitionProvider(),
new DependsOnDefinitionProvider(springIndex),
new ResourceDefinitionProvider(springIndex),
new QualifierDefinitionProvider(springIndex)));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,39 +61,41 @@ public void provideCompletions(ASTNode node, Annotation annotation, ITypeBinding

// case: @Qualifier(<*>)
if (node == annotation && doc.get(offset - 1, 2).endsWith("()")) {
createCompletionProposals(project, doc, node, completions, offset, offset, "", (beanName) -> "\"" + beanName + "\"");
createCompletionProposals(project, doc, node, "value", completions, offset, offset, "", (beanName) -> "\"" + beanName + "\"");
}
// case: @Qualifier(prefix<*>)
else if (node instanceof SimpleName && node.getParent() instanceof Annotation) {
computeProposalsForSimpleName(project, node, completions, offset, doc);
computeProposalsForSimpleName(project, node, "value", completions, offset, doc);
}
// case: @Qualifier(value=<*>)
else if (node instanceof SimpleName && node.getParent() instanceof MemberValuePair
&& "value".equals(((MemberValuePair)node.getParent()).getName().toString())) {
computeProposalsForSimpleName(project, node, completions, offset, doc);
&& completionProviders.containsKey(((MemberValuePair)node.getParent()).getName().toString())) {
String attributeName = ((MemberValuePair)node.getParent()).getName().toString();
computeProposalsForSimpleName(project, node, attributeName, completions, offset, doc);
}
// case: @Qualifier("prefix<*>")
else if (node instanceof StringLiteral && node.getParent() instanceof Annotation) {
if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) {
computeProposalsForStringLiteral(project, node, completions, offset, doc);
computeProposalsForStringLiteral(project, node, "value", completions, offset, doc);
}
}
// case: @Qualifier({"prefix<*>"})
else if (node instanceof StringLiteral && node.getParent() instanceof ArrayInitializer) {
if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) {
computeProposalsForInsideArrayInitializer(project, node, completions, offset, doc);
computeProposalsForInsideArrayInitializer(project, node, "value", completions, offset, doc);
}
}
// case: @Qualifier(value="prefix<*>")
else if (node instanceof StringLiteral && node.getParent() instanceof MemberValuePair
&& "value".equals(((MemberValuePair)node.getParent()).getName().toString())) {
&& completionProviders.containsKey(((MemberValuePair)node.getParent()).getName().toString())) {
if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) {
computeProposalsForStringLiteral(project, node, completions, offset, doc);
String attributeName = ((MemberValuePair)node.getParent()).getName().toString();
computeProposalsForStringLiteral(project, node, attributeName, completions, offset, doc);
}
}
// case: @Qualifier({<*>})
else if (node instanceof ArrayInitializer && node.getParent() instanceof Annotation) {
computeProposalsForArrayInitializr(project, (ArrayInitializer) node, completions, offset, doc);
computeProposalsForArrayInitializr(project, (ArrayInitializer) node, "value", completions, offset, doc);
}
}
catch (Exception e) {
Expand All @@ -104,12 +106,12 @@ else if (node instanceof ArrayInitializer && node.getParent() instanceof Annotat
/**
* create the concrete completion proposal
*/
private void createCompletionProposals(IJavaProject project, TextDocument doc, ASTNode node, Collection<ICompletionProposal> completions, int startOffset, int endOffset,
private void createCompletionProposals(IJavaProject project, TextDocument doc, ASTNode node, String attributeName, Collection<ICompletionProposal> completions, int startOffset, int endOffset,
String filterPrefix, Function<String, String> createReplacementText) {

Set<String> alreadyMentionedValues = alreadyMentionedValues(node);

AnnotationAttributeCompletionProvider completionProvider = this.completionProviders.get("value");
AnnotationAttributeCompletionProvider completionProvider = this.completionProviders.get(attributeName);
if (completionProvider != null) {
List<String> candidates = completionProvider.getCompletionCandidates(project);

Expand All @@ -135,7 +137,7 @@ private void createCompletionProposals(IJavaProject project, TextDocument doc, A
// internal computation of the right positions, prefixes, etc.
//

private void computeProposalsForSimpleName(IJavaProject project, ASTNode node, Collection<ICompletionProposal> completions, int offset, TextDocument doc) {
private void computeProposalsForSimpleName(IJavaProject project, ASTNode node, String attributeName, Collection<ICompletionProposal> completions, int offset, TextDocument doc) {
String prefix = identifyPropertyPrefix(node.toString(), offset - node.getStartPosition());

int startOffset = node.getStartPosition();
Expand All @@ -144,30 +146,30 @@ private void computeProposalsForSimpleName(IJavaProject project, ASTNode node, C
String proposalPrefix = "\"";
String proposalPostfix = "\"";

createCompletionProposals(project, doc, node, completions, startOffset, endOffset, prefix, (beanName) -> proposalPrefix + beanName + proposalPostfix);
createCompletionProposals(project, doc, node, attributeName, completions, startOffset, endOffset, prefix, (beanName) -> proposalPrefix + beanName + proposalPostfix);
}

private void computeProposalsForStringLiteral(IJavaProject project, ASTNode node, Collection<ICompletionProposal> completions, int offset, TextDocument doc) throws BadLocationException {
private void computeProposalsForStringLiteral(IJavaProject project, ASTNode node, String attributeName, Collection<ICompletionProposal> completions, int offset, TextDocument doc) throws BadLocationException {
int length = offset - (node.getStartPosition() + 1);

String prefix = identifyPropertyPrefix(doc.get(node.getStartPosition() + 1, length), length);
int startOffset = offset - prefix.length();
int endOffset = node.getStartPosition() + node.getLength() - 1;

createCompletionProposals(project, doc, node, completions, startOffset, endOffset, prefix, (beanName) -> beanName);
createCompletionProposals(project, doc, node, attributeName, completions, startOffset, endOffset, prefix, (beanName) -> beanName);
}

private void computeProposalsForArrayInitializr(IJavaProject project, ArrayInitializer node, Collection<ICompletionProposal> completions, int offset, TextDocument doc) {
createCompletionProposals(project, doc, node, completions, offset, offset, "", (beanName) -> "\"" + beanName + "\"");
private void computeProposalsForArrayInitializr(IJavaProject project, ArrayInitializer node, String attributeName, Collection<ICompletionProposal> completions, int offset, TextDocument doc) {
createCompletionProposals(project, doc, node, attributeName, completions, offset, offset, "", (beanName) -> "\"" + beanName + "\"");
}

private void computeProposalsForInsideArrayInitializer(IJavaProject project, ASTNode node, Collection<ICompletionProposal> completions, int offset, TextDocument doc) throws BadLocationException {
private void computeProposalsForInsideArrayInitializer(IJavaProject project, ASTNode node, String attributeName, Collection<ICompletionProposal> completions, int offset, TextDocument doc) throws BadLocationException {
int length = offset - (node.getStartPosition() + 1);
if (length >= 0) {
computeProposalsForStringLiteral(project, node, completions, offset, doc);
computeProposalsForStringLiteral(project, node, attributeName, completions, offset, doc);
}
else {
createCompletionProposals(project, doc, node, completions, offset, offset, "", (beanName) -> "\"" + beanName + "\",");
createCompletionProposals(project, doc, node, attributeName, completions, offset, offset, "", (beanName) -> "\"" + beanName + "\",");
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*******************************************************************************
* Copyright (c) 2024 Broadcom
* 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
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Broadcom - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.beans;

import java.util.Arrays;
import java.util.List;

import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
import org.springframework.ide.vscode.boot.java.annotations.AnnotationAttributeCompletionProvider;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.protocol.spring.Bean;

/**
* @author Martin Lippert
*/
public class ResourceCompletionProvider implements AnnotationAttributeCompletionProvider {

private final SpringMetamodelIndex springIndex;

public ResourceCompletionProvider(SpringMetamodelIndex springIndex) {
this.springIndex = springIndex;
}

@Override
public List<String> getCompletionCandidates(IJavaProject project) {

Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName());

return Arrays.stream(beans).map(bean -> bean.getName())
.distinct()
.toList();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*******************************************************************************
* Copyright (c) 2024 Broadcom
* 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
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Broadcom - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.beans;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.lsp4j.LocationLink;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.IJavaDefinitionProvider;
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.protocol.spring.Bean;

/**
* @author Martin Lippert
*/
public class ResourceDefinitionProvider implements IJavaDefinitionProvider {

private final SpringMetamodelIndex springIndex;

public ResourceDefinitionProvider(SpringMetamodelIndex springIndex) {
this.springIndex = springIndex;
}

@Override
public List<LocationLink> getDefinitions(CancelChecker cancelToken, IJavaProject project, CompilationUnit cu, ASTNode n) {
if (n instanceof StringLiteral) {
StringLiteral valueNode = (StringLiteral) n;

ASTNode parent = ASTUtils.getNearestAnnotationParent(valueNode);

if (parent != null && parent instanceof Annotation) {
Annotation a = (Annotation) parent;
IAnnotationBinding binding = a.resolveAnnotationBinding();
if (binding != null && binding.getAnnotationType() != null
&& (Annotations.RESOURCE_JAVAX.equals(binding.getAnnotationType().getQualifiedName())
|| Annotations.RESOURCE_JAKARTA.equals(binding.getAnnotationType().getQualifiedName()))) {
String beanName = valueNode.getLiteralValue();

if (beanName != null && beanName.length() > 0) {
return findBeansWithName(project, beanName);
}
}
}
}
return Collections.emptyList();
}

private List<LocationLink> findBeansWithName(IJavaProject project, String beanName) {
Bean[] beans = this.springIndex.getBeansWithName(project.getElementName(), beanName);

return Arrays.stream(beans)
.map(bean -> {
return new LocationLink(bean.getLocation().getUri(), bean.getLocation().getRange(), bean.getLocation().getRange());
})
.collect(Collectors.toList());
}

}
Loading

0 comments on commit bc5006f

Please sign in to comment.