Skip to content

Commit 565904d

Browse files
committed
GH-1344: refactored conditional on resource completion processor to be based on annotation attribute completion processor abstraction
1 parent c1970b7 commit 565904d

File tree

5 files changed

+39
-142
lines changed

5 files changed

+39
-142
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ BootJavaCompletionEngine javaCompletionEngine(
119119

120120
providers.put(Annotations.VALUE, new ValueCompletionProcessor(javaProjectFinder, indexProvider, adHocProperties));
121121
providers.put(Annotations.CONTEXT_CONFIGURATION, new ContextConfigurationProcessor(javaProjectFinder));
122-
providers.put(Annotations.CONDITIONAL_ON_RESOURCE, new ConditionalOnResourceCompletionProcessor(javaProjectFinder));
122+
providers.put(Annotations.CONDITIONAL_ON_RESOURCE, new AnnotationAttributeCompletionProcessor(javaProjectFinder, Map.of("resources", new ConditionalOnResourceCompletionProcessor())));
123123
providers.put(Annotations.REPOSITORY, new DataRepositoryCompletionProcessor());
124124

125125
providers.put(Annotations.SCOPE, new AnnotationAttributeCompletionProcessor(javaProjectFinder, Map.of("value", new ScopeCompletionProcessor())));

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

+11-3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import org.eclipse.jdt.core.dom.ArrayInitializer;
2525
import org.eclipse.jdt.core.dom.ITypeBinding;
2626
import org.eclipse.jdt.core.dom.MemberValuePair;
27+
import org.eclipse.jdt.core.dom.Name;
28+
import org.eclipse.jdt.core.dom.QualifiedName;
2729
import org.eclipse.jdt.core.dom.SimpleName;
2830
import org.eclipse.jdt.core.dom.StringLiteral;
2931
import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider;
@@ -59,17 +61,22 @@ public void provideCompletions(ASTNode node, Annotation annotation, ITypeBinding
5961

6062
try {
6163

64+
// in case the node is embedded in an qualified name, e.g. "file.txt", use the fully qualified node instead just a part
65+
if (node instanceof Name && node.getParent() instanceof QualifiedName) {
66+
node = node.getParent();
67+
}
68+
6269
// case: @Qualifier(<*>)
6370
if (node == annotation && doc.get(offset - 1, 2).endsWith("()")) {
6471
createCompletionProposals(project, doc, node, "value", completions, offset, offset, "", (beanName) -> "\"" + beanName + "\"");
6572
}
6673
// case: @Qualifier(prefix<*>)
67-
else if (node instanceof SimpleName && node.getParent() instanceof Annotation
74+
else if (node instanceof Name && node.getParent() instanceof Annotation
6875
&& node != annotation.getTypeName()) {
6976
computeProposalsForSimpleName(project, node, "value", completions, offset, doc);
7077
}
7178
// case: @Qualifier(value=<*>)
72-
else if (node instanceof SimpleName && node.getParent() instanceof MemberValuePair
79+
else if (node instanceof Name && node.getParent() instanceof MemberValuePair
7380
&& completionProviders.containsKey(((MemberValuePair)node.getParent()).getName().toString())) {
7481
String attributeName = ((MemberValuePair)node.getParent()).getName().toString();
7582
computeProposalsForSimpleName(project, node, attributeName, completions, offset, doc);
@@ -117,7 +124,8 @@ private void createCompletionProposals(IJavaProject project, TextDocument doc, A
117124
List<String> candidates = completionProvider.getCompletionCandidates(project);
118125

119126
List<String> filteredCandidates = candidates.stream()
120-
.filter(candidate -> candidate.toLowerCase().startsWith(filterPrefix.toLowerCase()))
127+
// .filter(candidate -> candidate.toLowerCase().startsWith(filterPrefix.toLowerCase()))
128+
.filter(candidate -> candidate.toLowerCase().contains(filterPrefix.toLowerCase()))
121129
.filter(candidate -> !alreadyMentionedValues.contains(candidate))
122130
.collect(Collectors.toList());
123131

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

+13-133
Original file line numberDiff line numberDiff line change
@@ -10,146 +10,21 @@
1010
*******************************************************************************/
1111
package org.springframework.ide.vscode.boot.java.conditionalonresource;
1212

13-
import static org.springframework.ide.vscode.commons.util.StringUtil.camelCaseToHyphens;
14-
1513
import java.nio.file.Paths;
16-
import java.util.*;
17-
import java.util.stream.Collectors;
14+
import java.util.Comparator;
15+
import java.util.List;
1816

19-
import org.eclipse.jdt.core.dom.ASTNode;
20-
import org.eclipse.jdt.core.dom.Annotation;
21-
import org.eclipse.jdt.core.dom.ITypeBinding;
22-
import org.eclipse.jdt.core.dom.MemberValuePair;
23-
import org.eclipse.jdt.core.dom.QualifiedName;
24-
import org.eclipse.jdt.core.dom.SimpleName;
25-
import org.eclipse.jdt.core.dom.StringLiteral;
26-
import org.eclipse.lsp4j.TextDocumentIdentifier;
27-
import org.openrewrite.yaml.internal.grammar.JsonPathParser;
28-
import org.slf4j.Logger;
29-
import org.slf4j.LoggerFactory;
30-
import org.springframework.ide.vscode.boot.java.annotations.AnnotationAttributeCompletionProposal;
31-
import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider;
32-
import org.springframework.ide.vscode.boot.metadata.ProjectBasedPropertyIndexProvider;
33-
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
34-
import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndexProvider;
17+
import org.springframework.ide.vscode.boot.java.annotations.AnnotationAttributeCompletionProvider;
3518
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
3619
import org.springframework.ide.vscode.commons.java.IJavaProject;
37-
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
38-
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
39-
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
40-
import org.springframework.ide.vscode.commons.util.BadLocationException;
41-
import org.springframework.ide.vscode.commons.util.FuzzyMap;
42-
import org.springframework.ide.vscode.commons.util.FuzzyMap.Match;
43-
import org.springframework.ide.vscode.commons.util.text.IDocument;
44-
import org.springframework.ide.vscode.commons.util.text.TextDocument;
4520

4621
/**
4722
* @author Karthik Sankaranarayanan
4823
*/
49-
public class ConditionalOnResourceCompletionProcessor implements CompletionProvider {
50-
51-
private static final Logger log = LoggerFactory.getLogger(ConditionalOnResourceCompletionProcessor.class);
52-
53-
private final JavaProjectFinder projectFinder;
54-
55-
public ConditionalOnResourceCompletionProcessor(JavaProjectFinder projectFinder) {
56-
this.projectFinder = projectFinder;
57-
}
58-
59-
@Override
60-
public void provideCompletions(ASTNode node, Annotation annotation, ITypeBinding type,
61-
int offset, TextDocument doc, Collection<ICompletionProposal> completions) {
62-
63-
try {
64-
Optional<IJavaProject> optionalProject = this.projectFinder.find(doc.getId());
65-
if (optionalProject.isEmpty()) {
66-
return;
67-
}
68-
69-
IJavaProject project = optionalProject.get();
70-
71-
// case: @ConditionalOnResource(resources=<*>)
72-
if (node instanceof SimpleName && node.getParent() instanceof MemberValuePair
73-
&& ("resources".equals(((MemberValuePair)node.getParent()).getName().toString()))) {
74-
computeProposalsForSimpleName(project, node, completions, offset, doc);
75-
}
76-
// case: @ConditionalOnResource(resources=<*>)
77-
else if (node instanceof SimpleName && node.getParent() instanceof QualifiedName && node.getParent().getParent() instanceof MemberValuePair
78-
&& ("resources".equals(((MemberValuePair)node.getParent().getParent()).getName().toString()))) {
79-
computeProposalsForSimpleName(project, node.getParent(), completions, offset, doc);
80-
}
81-
// case:@ConditionalOnResource(resources="prefix<*>")
82-
else if (node instanceof StringLiteral && node.getParent() instanceof MemberValuePair
83-
&& ("resources".equals(((MemberValuePair)node.getParent()).getName().toString()))) {
84-
if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) {
85-
computeProposalsForStringLiteral(project, (StringLiteral) node, completions, offset, doc);
86-
}
87-
}
88-
}
89-
catch (Exception e) {
90-
log.error("problem while looking for ConditionalOnResource annotation proposals", e);
91-
}
92-
}
93-
94-
private void addClasspathResourceProposals(IJavaProject project, TextDocument doc, int startOffset, int endOffset, String prefix, boolean includeQuotes, Collection<ICompletionProposal> completions) {
95-
String[] resources = findResources(project, prefix);
96-
97-
double score = resources.length + 1000;
98-
for (String resource : resources) {
99-
100-
DocumentEdits edits = new DocumentEdits(doc, false);
101-
102-
if (includeQuotes) {
103-
edits.replace(startOffset, endOffset, "\"classpath:" + resource + "\"");
104-
}
105-
else {
106-
edits.replace(startOffset, endOffset, "classpath:" + resource);
107-
}
108-
109-
String label = "classpath:" + resource;
24+
public class ConditionalOnResourceCompletionProcessor implements AnnotationAttributeCompletionProvider {
11025

111-
ICompletionProposal proposal = new AnnotationAttributeCompletionProposal(edits, label, label, null, score--);
112-
completions.add(proposal);
113-
}
114-
115-
}
116-
117-
private void computeProposalsForSimpleName(IJavaProject project, ASTNode node, Collection<ICompletionProposal> completions, int offset, TextDocument doc) {
118-
int startOffset = node.getStartPosition();
119-
int endOffset = node.getStartPosition() + node.getLength();
120-
121-
String unfilteredPrefix = node.toString().substring(0, offset - node.getStartPosition());
122-
addClasspathResourceProposals(project, doc, startOffset, endOffset, unfilteredPrefix, true, completions);
123-
}
124-
125-
private void computeProposalsForStringLiteral(IJavaProject project, StringLiteral node, Collection<ICompletionProposal> completions, int offset, TextDocument doc) throws BadLocationException {
126-
String prefix = identifyPropertyPrefix(doc.get(node.getStartPosition() + 1, offset - (node.getStartPosition() + 1)), offset - (node.getStartPosition() + 1));
127-
128-
int startOffset = offset - prefix.length();
129-
int endOffset = offset;
130-
131-
String unfilteredPrefix = node.getLiteralValue().substring(0, offset - (node.getStartPosition() + 1));
132-
addClasspathResourceProposals(project, doc, startOffset, endOffset, unfilteredPrefix, false, completions);
133-
}
134-
135-
public String identifyPropertyPrefix(String nodeContent, int offset) {
136-
String result = nodeContent.substring(0, offset);
137-
138-
int i = offset - 1;
139-
while (i >= 0) {
140-
char c = nodeContent.charAt(i);
141-
if (c == '}' || c == '{' || c == '$' || c == '#') {
142-
result = result.substring(i + 1, offset);
143-
break;
144-
}
145-
i--;
146-
}
147-
148-
return result;
149-
}
150-
151-
private String[] findResources(IJavaProject project, String prefix) {
152-
String[] resources = IClasspathUtil.getClasspathResources(project.getClasspath()).stream()
26+
private List<String> findResources(IJavaProject project) {
27+
List<String> resources = IClasspathUtil.getClasspathResources(project.getClasspath()).stream()
15328
.distinct()
15429
.sorted(new Comparator<String>() {
15530
@Override
@@ -158,10 +33,15 @@ public int compare(String o1, String o2) {
15833
}
15934
})
16035
.map(r -> r.replaceAll("\\\\", "/"))
161-
.filter(r -> ("classpath:" + r).contains(prefix))
162-
.toArray(String[]::new);
36+
.map(r -> "classpath:" + r)
37+
.toList();
16338

16439
return resources;
16540
}
16641

42+
@Override
43+
public List<String> getCompletionCandidates(IJavaProject project) {
44+
return findResources(project);
45+
}
46+
16747
}

headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/DependsOnCompletionProviderTest.java

+5
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,11 @@ public void testDependsOnCompletionWithoutQuotesWithPrefix() throws Exception {
103103
assertCompletions("@DependsOn(be<*>)", 2, "@DependsOn(\"bean1\"<*>)");
104104
}
105105

106+
@Test
107+
public void testDependsOnCompletionWithoutQuotesWithNotExactPrefix() throws Exception {
108+
assertCompletions("@DependsOn(ea<*>)", 2, "@DependsOn(\"bean1\"<*>)");
109+
}
110+
106111
@Test
107112
public void testDependsOnCompletionWithoutQuotesWithAttributeName() throws Exception {
108113
assertCompletions("@DependsOn(value=<*>)", 2, "@DependsOn(value=\"bean1\"<*>)");

headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/conditionalonresource/test/ConditionalOnResourceCompletionTest.java

+9-5
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,15 @@ void testEmptyStringLiteralCompletionWithoutMandatoryResourcesParam() throws Exc
181181
}
182182

183183
@Test
184-
void testComplexResourceNameInPrefixWithParamNameCompletion() throws Exception {
184+
void testResourceNameInPrefixWithParamNameCompletion() throws Exception {
185+
prepareCase("@ConditionalOnResource(resources=\"onClass\")", "@ConditionalOnResource(resources=root<*>)");
186+
187+
assertClasspathCompletions(
188+
"@ConditionalOnResource(resources=\"classpath:a-random-resource-root.md\"<*>)");
189+
}
190+
191+
@Test
192+
void testQualifiedResourceNameInPrefixWithParamNameCompletion() throws Exception {
185193
prepareCase("@ConditionalOnResource(resources=\"onClass\")", "@ConditionalOnResource(resources=root.md<*>)");
186194

187195
assertClasspathCompletions(
@@ -210,10 +218,6 @@ void testComplexResourceNameWithSlashPrefixAndWithResourcesAndParamNameCompletio
210218
assertClasspathCompletions();
211219
}
212220

213-
214-
215-
216-
217221
private void prepareCase(String selectedAnnotation, String annotationStatementBeforeTest) throws Exception {
218222
InputStream resource = this.getClass().getResourceAsStream("/test-projects/test-annotation-conditionalonresource/src/main/java/org/test/TestConditionalOnResourceCompletion.java");
219223
String content = IOUtils.toString(resource, Charset.defaultCharset());

0 commit comments

Comments
 (0)