GH-1344: refactored conditional on resource completion processor to be based on annotation attribute completion processor abstraction
This commit is contained in:
@@ -119,7 +119,7 @@ public class BootJavaCompletionEngineConfigurer {
|
||||
|
||||
providers.put(Annotations.VALUE, new ValueCompletionProcessor(javaProjectFinder, indexProvider, adHocProperties));
|
||||
providers.put(Annotations.CONTEXT_CONFIGURATION, new ContextConfigurationProcessor(javaProjectFinder));
|
||||
providers.put(Annotations.CONDITIONAL_ON_RESOURCE, new ConditionalOnResourceCompletionProcessor(javaProjectFinder));
|
||||
providers.put(Annotations.CONDITIONAL_ON_RESOURCE, new AnnotationAttributeCompletionProcessor(javaProjectFinder, Map.of("resources", new ConditionalOnResourceCompletionProcessor())));
|
||||
providers.put(Annotations.REPOSITORY, new DataRepositoryCompletionProcessor());
|
||||
|
||||
providers.put(Annotations.SCOPE, new AnnotationAttributeCompletionProcessor(javaProjectFinder, Map.of("value", new ScopeCompletionProcessor())));
|
||||
|
||||
@@ -24,6 +24,8 @@ import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.ArrayInitializer;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.MemberValuePair;
|
||||
import org.eclipse.jdt.core.dom.Name;
|
||||
import org.eclipse.jdt.core.dom.QualifiedName;
|
||||
import org.eclipse.jdt.core.dom.SimpleName;
|
||||
import org.eclipse.jdt.core.dom.StringLiteral;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider;
|
||||
@@ -59,17 +61,22 @@ public class AnnotationAttributeCompletionProcessor implements CompletionProvide
|
||||
|
||||
try {
|
||||
|
||||
// in case the node is embedded in an qualified name, e.g. "file.txt", use the fully qualified node instead just a part
|
||||
if (node instanceof Name && node.getParent() instanceof QualifiedName) {
|
||||
node = node.getParent();
|
||||
}
|
||||
|
||||
// case: @Qualifier(<*>)
|
||||
if (node == annotation && doc.get(offset - 1, 2).endsWith("()")) {
|
||||
createCompletionProposals(project, doc, node, "value", completions, offset, offset, "", (beanName) -> "\"" + beanName + "\"");
|
||||
}
|
||||
// case: @Qualifier(prefix<*>)
|
||||
else if (node instanceof SimpleName && node.getParent() instanceof Annotation
|
||||
else if (node instanceof Name && node.getParent() instanceof Annotation
|
||||
&& node != annotation.getTypeName()) {
|
||||
computeProposalsForSimpleName(project, node, "value", completions, offset, doc);
|
||||
}
|
||||
// case: @Qualifier(value=<*>)
|
||||
else if (node instanceof SimpleName && node.getParent() instanceof MemberValuePair
|
||||
else if (node instanceof Name && node.getParent() instanceof MemberValuePair
|
||||
&& completionProviders.containsKey(((MemberValuePair)node.getParent()).getName().toString())) {
|
||||
String attributeName = ((MemberValuePair)node.getParent()).getName().toString();
|
||||
computeProposalsForSimpleName(project, node, attributeName, completions, offset, doc);
|
||||
@@ -117,7 +124,8 @@ public class AnnotationAttributeCompletionProcessor implements CompletionProvide
|
||||
List<String> candidates = completionProvider.getCompletionCandidates(project);
|
||||
|
||||
List<String> filteredCandidates = candidates.stream()
|
||||
.filter(candidate -> candidate.toLowerCase().startsWith(filterPrefix.toLowerCase()))
|
||||
// .filter(candidate -> candidate.toLowerCase().startsWith(filterPrefix.toLowerCase()))
|
||||
.filter(candidate -> candidate.toLowerCase().contains(filterPrefix.toLowerCase()))
|
||||
.filter(candidate -> !alreadyMentionedValues.contains(candidate))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
|
||||
@@ -10,146 +10,21 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.conditionalonresource;
|
||||
|
||||
import static org.springframework.ide.vscode.commons.util.StringUtil.camelCaseToHyphens;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.MemberValuePair;
|
||||
import org.eclipse.jdt.core.dom.QualifiedName;
|
||||
import org.eclipse.jdt.core.dom.SimpleName;
|
||||
import org.eclipse.jdt.core.dom.StringLiteral;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.openrewrite.yaml.internal.grammar.JsonPathParser;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.boot.java.annotations.AnnotationAttributeCompletionProposal;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider;
|
||||
import org.springframework.ide.vscode.boot.metadata.ProjectBasedPropertyIndexProvider;
|
||||
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
|
||||
import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndexProvider;
|
||||
import org.springframework.ide.vscode.boot.java.annotations.AnnotationAttributeCompletionProvider;
|
||||
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.FuzzyMap;
|
||||
import org.springframework.ide.vscode.commons.util.FuzzyMap.Match;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
/**
|
||||
* @author Karthik Sankaranarayanan
|
||||
*/
|
||||
public class ConditionalOnResourceCompletionProcessor implements CompletionProvider {
|
||||
public class ConditionalOnResourceCompletionProcessor implements AnnotationAttributeCompletionProvider {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ConditionalOnResourceCompletionProcessor.class);
|
||||
|
||||
private final JavaProjectFinder projectFinder;
|
||||
|
||||
public ConditionalOnResourceCompletionProcessor(JavaProjectFinder projectFinder) {
|
||||
this.projectFinder = projectFinder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void provideCompletions(ASTNode node, Annotation annotation, ITypeBinding type,
|
||||
int offset, TextDocument doc, Collection<ICompletionProposal> completions) {
|
||||
|
||||
try {
|
||||
Optional<IJavaProject> optionalProject = this.projectFinder.find(doc.getId());
|
||||
if (optionalProject.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
IJavaProject project = optionalProject.get();
|
||||
|
||||
// case: @ConditionalOnResource(resources=<*>)
|
||||
if (node instanceof SimpleName && node.getParent() instanceof MemberValuePair
|
||||
&& ("resources".equals(((MemberValuePair)node.getParent()).getName().toString()))) {
|
||||
computeProposalsForSimpleName(project, node, completions, offset, doc);
|
||||
}
|
||||
// case: @ConditionalOnResource(resources=<*>)
|
||||
else if (node instanceof SimpleName && node.getParent() instanceof QualifiedName && node.getParent().getParent() instanceof MemberValuePair
|
||||
&& ("resources".equals(((MemberValuePair)node.getParent().getParent()).getName().toString()))) {
|
||||
computeProposalsForSimpleName(project, node.getParent(), completions, offset, doc);
|
||||
}
|
||||
// case:@ConditionalOnResource(resources="prefix<*>")
|
||||
else if (node instanceof StringLiteral && node.getParent() instanceof MemberValuePair
|
||||
&& ("resources".equals(((MemberValuePair)node.getParent()).getName().toString()))) {
|
||||
if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) {
|
||||
computeProposalsForStringLiteral(project, (StringLiteral) node, completions, offset, doc);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.error("problem while looking for ConditionalOnResource annotation proposals", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void addClasspathResourceProposals(IJavaProject project, TextDocument doc, int startOffset, int endOffset, String prefix, boolean includeQuotes, Collection<ICompletionProposal> completions) {
|
||||
String[] resources = findResources(project, prefix);
|
||||
|
||||
double score = resources.length + 1000;
|
||||
for (String resource : resources) {
|
||||
|
||||
DocumentEdits edits = new DocumentEdits(doc, false);
|
||||
|
||||
if (includeQuotes) {
|
||||
edits.replace(startOffset, endOffset, "\"classpath:" + resource + "\"");
|
||||
}
|
||||
else {
|
||||
edits.replace(startOffset, endOffset, "classpath:" + resource);
|
||||
}
|
||||
|
||||
String label = "classpath:" + resource;
|
||||
|
||||
ICompletionProposal proposal = new AnnotationAttributeCompletionProposal(edits, label, label, null, score--);
|
||||
completions.add(proposal);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void computeProposalsForSimpleName(IJavaProject project, ASTNode node, Collection<ICompletionProposal> completions, int offset, TextDocument doc) {
|
||||
int startOffset = node.getStartPosition();
|
||||
int endOffset = node.getStartPosition() + node.getLength();
|
||||
|
||||
String unfilteredPrefix = node.toString().substring(0, offset - node.getStartPosition());
|
||||
addClasspathResourceProposals(project, doc, startOffset, endOffset, unfilteredPrefix, true, completions);
|
||||
}
|
||||
|
||||
private void computeProposalsForStringLiteral(IJavaProject project, StringLiteral node, Collection<ICompletionProposal> completions, int offset, TextDocument doc) throws BadLocationException {
|
||||
String prefix = identifyPropertyPrefix(doc.get(node.getStartPosition() + 1, offset - (node.getStartPosition() + 1)), offset - (node.getStartPosition() + 1));
|
||||
|
||||
int startOffset = offset - prefix.length();
|
||||
int endOffset = offset;
|
||||
|
||||
String unfilteredPrefix = node.getLiteralValue().substring(0, offset - (node.getStartPosition() + 1));
|
||||
addClasspathResourceProposals(project, doc, startOffset, endOffset, unfilteredPrefix, false, completions);
|
||||
}
|
||||
|
||||
public String identifyPropertyPrefix(String nodeContent, int offset) {
|
||||
String result = nodeContent.substring(0, offset);
|
||||
|
||||
int i = offset - 1;
|
||||
while (i >= 0) {
|
||||
char c = nodeContent.charAt(i);
|
||||
if (c == '}' || c == '{' || c == '$' || c == '#') {
|
||||
result = result.substring(i + 1, offset);
|
||||
break;
|
||||
}
|
||||
i--;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private String[] findResources(IJavaProject project, String prefix) {
|
||||
String[] resources = IClasspathUtil.getClasspathResources(project.getClasspath()).stream()
|
||||
private List<String> findResources(IJavaProject project) {
|
||||
List<String> resources = IClasspathUtil.getClasspathResources(project.getClasspath()).stream()
|
||||
.distinct()
|
||||
.sorted(new Comparator<String>() {
|
||||
@Override
|
||||
@@ -158,10 +33,15 @@ public class ConditionalOnResourceCompletionProcessor implements CompletionProvi
|
||||
}
|
||||
})
|
||||
.map(r -> r.replaceAll("\\\\", "/"))
|
||||
.filter(r -> ("classpath:" + r).contains(prefix))
|
||||
.toArray(String[]::new);
|
||||
.map(r -> "classpath:" + r)
|
||||
.toList();
|
||||
|
||||
return resources;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getCompletionCandidates(IJavaProject project) {
|
||||
return findResources(project);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -103,6 +103,11 @@ public class DependsOnCompletionProviderTest {
|
||||
assertCompletions("@DependsOn(be<*>)", 2, "@DependsOn(\"bean1\"<*>)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDependsOnCompletionWithoutQuotesWithNotExactPrefix() throws Exception {
|
||||
assertCompletions("@DependsOn(ea<*>)", 2, "@DependsOn(\"bean1\"<*>)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDependsOnCompletionWithoutQuotesWithAttributeName() throws Exception {
|
||||
assertCompletions("@DependsOn(value=<*>)", 2, "@DependsOn(value=\"bean1\"<*>)");
|
||||
|
||||
@@ -181,7 +181,15 @@ public class ConditionalOnResourceCompletionTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testComplexResourceNameInPrefixWithParamNameCompletion() throws Exception {
|
||||
void testResourceNameInPrefixWithParamNameCompletion() throws Exception {
|
||||
prepareCase("@ConditionalOnResource(resources=\"onClass\")", "@ConditionalOnResource(resources=root<*>)");
|
||||
|
||||
assertClasspathCompletions(
|
||||
"@ConditionalOnResource(resources=\"classpath:a-random-resource-root.md\"<*>)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testQualifiedResourceNameInPrefixWithParamNameCompletion() throws Exception {
|
||||
prepareCase("@ConditionalOnResource(resources=\"onClass\")", "@ConditionalOnResource(resources=root.md<*>)");
|
||||
|
||||
assertClasspathCompletions(
|
||||
@@ -210,10 +218,6 @@ public class ConditionalOnResourceCompletionTest {
|
||||
assertClasspathCompletions();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private void prepareCase(String selectedAnnotation, String annotationStatementBeforeTest) throws Exception {
|
||||
InputStream resource = this.getClass().getResourceAsStream("/test-projects/test-annotation-conditionalonresource/src/main/java/org/test/TestConditionalOnResourceCompletion.java");
|
||||
String content = IOUtils.toString(resource, Charset.defaultCharset());
|
||||
|
||||
Reference in New Issue
Block a user