Skip to content

Commit bc5006f

Browse files
committed
GH-1298: added bean name code completion and navigation support for name attribute of resource annotation
1 parent 6b0bd5d commit bc5006f

File tree

7 files changed

+478
-20
lines changed

7 files changed

+478
-20
lines changed

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaCompletionEngineConfigurer.java

+4
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.springframework.ide.vscode.boot.java.beans.DependsOnCompletionProcessor;
2929
import org.springframework.ide.vscode.boot.java.beans.ProfileCompletionProvider;
3030
import org.springframework.ide.vscode.boot.java.beans.QualifierCompletionProvider;
31+
import org.springframework.ide.vscode.boot.java.beans.ResourceCompletionProvider;
3132
import org.springframework.ide.vscode.boot.java.data.DataRepositoryCompletionProcessor;
3233
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCompletionEngine;
3334
import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider;
@@ -121,6 +122,9 @@ BootJavaCompletionEngine javaCompletionEngine(
121122
providers.put(Annotations.QUALIFIER, new AnnotationAttributeCompletionProcessor(javaProjectFinder, Map.of("value", new QualifierCompletionProvider(springIndex))));
122123
providers.put(Annotations.PROFILE, new AnnotationAttributeCompletionProcessor(javaProjectFinder, Map.of("value", new ProfileCompletionProvider(springIndex))));
123124

125+
providers.put(Annotations.RESOURCE_JAVAX, new AnnotationAttributeCompletionProcessor(javaProjectFinder, Map.of("name", new ResourceCompletionProvider(springIndex))));
126+
providers.put(Annotations.RESOURCE_JAKARTA, new AnnotationAttributeCompletionProcessor(javaProjectFinder, Map.of("name", new ResourceCompletionProvider(springIndex))));
127+
124128
return new BootJavaCompletionEngine(cuCache, providers, snippetManager);
125129
}
126130

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java

+2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import org.springframework.ide.vscode.boot.java.JavaDefinitionHandler;
4848
import org.springframework.ide.vscode.boot.java.beans.DependsOnDefinitionProvider;
4949
import org.springframework.ide.vscode.boot.java.beans.QualifierDefinitionProvider;
50+
import org.springframework.ide.vscode.boot.java.beans.ResourceDefinitionProvider;
5051
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeActionProvider;
5152
import org.springframework.ide.vscode.boot.java.handlers.BootJavaReconcileEngine;
5253
import org.springframework.ide.vscode.boot.java.handlers.JavaCodeActionHandler;
@@ -397,6 +398,7 @@ JavaDefinitionHandler javaDefinitionHandler(CompilationUnitCache cuCache, JavaPr
397398
return new JavaDefinitionHandler(cuCache, projectFinder, List.of(
398399
new ValueDefinitionProvider(),
399400
new DependsOnDefinitionProvider(springIndex),
401+
new ResourceDefinitionProvider(springIndex),
400402
new QualifierDefinitionProvider(springIndex)));
401403
}
402404

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/annotations/AnnotationAttributeCompletionProcessor.java

+22-20
Original file line numberDiff line numberDiff line change
@@ -61,39 +61,41 @@ public void provideCompletions(ASTNode node, Annotation annotation, ITypeBinding
6161

6262
// case: @Qualifier(<*>)
6363
if (node == annotation && doc.get(offset - 1, 2).endsWith("()")) {
64-
createCompletionProposals(project, doc, node, completions, offset, offset, "", (beanName) -> "\"" + beanName + "\"");
64+
createCompletionProposals(project, doc, node, "value", completions, offset, offset, "", (beanName) -> "\"" + beanName + "\"");
6565
}
6666
// case: @Qualifier(prefix<*>)
6767
else if (node instanceof SimpleName && node.getParent() instanceof Annotation) {
68-
computeProposalsForSimpleName(project, node, completions, offset, doc);
68+
computeProposalsForSimpleName(project, node, "value", completions, offset, doc);
6969
}
7070
// case: @Qualifier(value=<*>)
7171
else if (node instanceof SimpleName && node.getParent() instanceof MemberValuePair
72-
&& "value".equals(((MemberValuePair)node.getParent()).getName().toString())) {
73-
computeProposalsForSimpleName(project, node, completions, offset, doc);
72+
&& completionProviders.containsKey(((MemberValuePair)node.getParent()).getName().toString())) {
73+
String attributeName = ((MemberValuePair)node.getParent()).getName().toString();
74+
computeProposalsForSimpleName(project, node, attributeName, completions, offset, doc);
7475
}
7576
// case: @Qualifier("prefix<*>")
7677
else if (node instanceof StringLiteral && node.getParent() instanceof Annotation) {
7778
if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) {
78-
computeProposalsForStringLiteral(project, node, completions, offset, doc);
79+
computeProposalsForStringLiteral(project, node, "value", completions, offset, doc);
7980
}
8081
}
8182
// case: @Qualifier({"prefix<*>"})
8283
else if (node instanceof StringLiteral && node.getParent() instanceof ArrayInitializer) {
8384
if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) {
84-
computeProposalsForInsideArrayInitializer(project, node, completions, offset, doc);
85+
computeProposalsForInsideArrayInitializer(project, node, "value", completions, offset, doc);
8586
}
8687
}
8788
// case: @Qualifier(value="prefix<*>")
8889
else if (node instanceof StringLiteral && node.getParent() instanceof MemberValuePair
89-
&& "value".equals(((MemberValuePair)node.getParent()).getName().toString())) {
90+
&& completionProviders.containsKey(((MemberValuePair)node.getParent()).getName().toString())) {
9091
if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) {
91-
computeProposalsForStringLiteral(project, node, completions, offset, doc);
92+
String attributeName = ((MemberValuePair)node.getParent()).getName().toString();
93+
computeProposalsForStringLiteral(project, node, attributeName, completions, offset, doc);
9294
}
9395
}
9496
// case: @Qualifier({<*>})
9597
else if (node instanceof ArrayInitializer && node.getParent() instanceof Annotation) {
96-
computeProposalsForArrayInitializr(project, (ArrayInitializer) node, completions, offset, doc);
98+
computeProposalsForArrayInitializr(project, (ArrayInitializer) node, "value", completions, offset, doc);
9799
}
98100
}
99101
catch (Exception e) {
@@ -104,12 +106,12 @@ else if (node instanceof ArrayInitializer && node.getParent() instanceof Annotat
104106
/**
105107
* create the concrete completion proposal
106108
*/
107-
private void createCompletionProposals(IJavaProject project, TextDocument doc, ASTNode node, Collection<ICompletionProposal> completions, int startOffset, int endOffset,
109+
private void createCompletionProposals(IJavaProject project, TextDocument doc, ASTNode node, String attributeName, Collection<ICompletionProposal> completions, int startOffset, int endOffset,
108110
String filterPrefix, Function<String, String> createReplacementText) {
109111

110112
Set<String> alreadyMentionedValues = alreadyMentionedValues(node);
111113

112-
AnnotationAttributeCompletionProvider completionProvider = this.completionProviders.get("value");
114+
AnnotationAttributeCompletionProvider completionProvider = this.completionProviders.get(attributeName);
113115
if (completionProvider != null) {
114116
List<String> candidates = completionProvider.getCompletionCandidates(project);
115117

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

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

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

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

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

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

157-
createCompletionProposals(project, doc, node, completions, startOffset, endOffset, prefix, (beanName) -> beanName);
159+
createCompletionProposals(project, doc, node, attributeName, completions, startOffset, endOffset, prefix, (beanName) -> beanName);
158160
}
159161

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

164-
private void computeProposalsForInsideArrayInitializer(IJavaProject project, ASTNode node, Collection<ICompletionProposal> completions, int offset, TextDocument doc) throws BadLocationException {
166+
private void computeProposalsForInsideArrayInitializer(IJavaProject project, ASTNode node, String attributeName, Collection<ICompletionProposal> completions, int offset, TextDocument doc) throws BadLocationException {
165167
int length = offset - (node.getStartPosition() + 1);
166168
if (length >= 0) {
167-
computeProposalsForStringLiteral(project, node, completions, offset, doc);
169+
computeProposalsForStringLiteral(project, node, attributeName, completions, offset, doc);
168170
}
169171
else {
170-
createCompletionProposals(project, doc, node, completions, offset, offset, "", (beanName) -> "\"" + beanName + "\",");
172+
createCompletionProposals(project, doc, node, attributeName, completions, offset, offset, "", (beanName) -> "\"" + beanName + "\",");
171173
}
172174
}
173175

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2024 Broadcom
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Broadcom - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.boot.java.beans;
12+
13+
import java.util.Arrays;
14+
import java.util.List;
15+
16+
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
17+
import org.springframework.ide.vscode.boot.java.annotations.AnnotationAttributeCompletionProvider;
18+
import org.springframework.ide.vscode.commons.java.IJavaProject;
19+
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
20+
21+
/**
22+
* @author Martin Lippert
23+
*/
24+
public class ResourceCompletionProvider implements AnnotationAttributeCompletionProvider {
25+
26+
private final SpringMetamodelIndex springIndex;
27+
28+
public ResourceCompletionProvider(SpringMetamodelIndex springIndex) {
29+
this.springIndex = springIndex;
30+
}
31+
32+
@Override
33+
public List<String> getCompletionCandidates(IJavaProject project) {
34+
35+
Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName());
36+
37+
return Arrays.stream(beans).map(bean -> bean.getName())
38+
.distinct()
39+
.toList();
40+
}
41+
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2024 Broadcom
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Broadcom - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.boot.java.beans;
12+
13+
import java.util.Arrays;
14+
import java.util.Collections;
15+
import java.util.List;
16+
import java.util.stream.Collectors;
17+
18+
import org.eclipse.jdt.core.dom.ASTNode;
19+
import org.eclipse.jdt.core.dom.Annotation;
20+
import org.eclipse.jdt.core.dom.CompilationUnit;
21+
import org.eclipse.jdt.core.dom.IAnnotationBinding;
22+
import org.eclipse.jdt.core.dom.StringLiteral;
23+
import org.eclipse.lsp4j.LocationLink;
24+
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
25+
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
26+
import org.springframework.ide.vscode.boot.java.Annotations;
27+
import org.springframework.ide.vscode.boot.java.IJavaDefinitionProvider;
28+
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
29+
import org.springframework.ide.vscode.commons.java.IJavaProject;
30+
import org.springframework.ide.vscode.commons.protocol.spring.Bean;
31+
32+
/**
33+
* @author Martin Lippert
34+
*/
35+
public class ResourceDefinitionProvider implements IJavaDefinitionProvider {
36+
37+
private final SpringMetamodelIndex springIndex;
38+
39+
public ResourceDefinitionProvider(SpringMetamodelIndex springIndex) {
40+
this.springIndex = springIndex;
41+
}
42+
43+
@Override
44+
public List<LocationLink> getDefinitions(CancelChecker cancelToken, IJavaProject project, CompilationUnit cu, ASTNode n) {
45+
if (n instanceof StringLiteral) {
46+
StringLiteral valueNode = (StringLiteral) n;
47+
48+
ASTNode parent = ASTUtils.getNearestAnnotationParent(valueNode);
49+
50+
if (parent != null && parent instanceof Annotation) {
51+
Annotation a = (Annotation) parent;
52+
IAnnotationBinding binding = a.resolveAnnotationBinding();
53+
if (binding != null && binding.getAnnotationType() != null
54+
&& (Annotations.RESOURCE_JAVAX.equals(binding.getAnnotationType().getQualifiedName())
55+
|| Annotations.RESOURCE_JAKARTA.equals(binding.getAnnotationType().getQualifiedName()))) {
56+
String beanName = valueNode.getLiteralValue();
57+
58+
if (beanName != null && beanName.length() > 0) {
59+
return findBeansWithName(project, beanName);
60+
}
61+
}
62+
}
63+
}
64+
return Collections.emptyList();
65+
}
66+
67+
private List<LocationLink> findBeansWithName(IJavaProject project, String beanName) {
68+
Bean[] beans = this.springIndex.getBeansWithName(project.getElementName(), beanName);
69+
70+
return Arrays.stream(beans)
71+
.map(bean -> {
72+
return new LocationLink(bean.getLocation().getUri(), bean.getLocation().getRange(), bean.getLocation().getRange());
73+
})
74+
.collect(Collectors.toList());
75+
}
76+
77+
}

0 commit comments

Comments
 (0)