Use annotation attributes in creation of @Bean symbols
This commit is contained in:
@@ -21,14 +21,18 @@ import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||
import org.eclipse.jdt.core.dom.ParameterizedType;
|
||||
import org.eclipse.jdt.core.dom.StringLiteral;
|
||||
import org.eclipse.jdt.core.dom.Type;
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.SymbolInformation;
|
||||
import org.eclipse.lsp4j.SymbolKind;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider;
|
||||
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
@@ -37,6 +41,7 @@ public class BeansSymbolProvider implements SymbolProvider {
|
||||
private static final String FUNCTION_FUNCTION_TYPE = Function.class.getName();
|
||||
private static final String FUNCTION_CONSUMER_TYPE = Consumer.class.getName();
|
||||
private static final String FUNCTION_SUPPLIER_TYPE = Supplier.class.getName();
|
||||
private static final String[] NAME_ATTRIBUTES = {"value", "name"};
|
||||
|
||||
@Override
|
||||
public Collection<SymbolInformation> getSymbols(Annotation node, TextDocument doc) {
|
||||
@@ -45,13 +50,13 @@ public class BeansSymbolProvider implements SymbolProvider {
|
||||
return Collections.singleton(createFunctionSymbol(node, doc));
|
||||
}
|
||||
else {
|
||||
return Collections.singleton(createRegularBeanSymbol(node, doc));
|
||||
return createRegularBeanSymbols(node, doc);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
private boolean isFunctionBean(Annotation node) {
|
||||
@@ -94,25 +99,51 @@ public class BeansSymbolProvider implements SymbolProvider {
|
||||
return symbol;
|
||||
}
|
||||
|
||||
private SymbolInformation createRegularBeanSymbol(Annotation node, TextDocument doc) throws BadLocationException {
|
||||
private Collection<SymbolInformation> createRegularBeanSymbols(Annotation node, TextDocument doc) throws BadLocationException {
|
||||
|
||||
Collection<StringLiteral> beanNameNodes = getBeanNameLiterals(node);
|
||||
if (beanNameNodes!=null && !beanNameNodes.isEmpty()) {
|
||||
ImmutableList.Builder<SymbolInformation> symbols = ImmutableList.builder();
|
||||
for (StringLiteral nameNode : beanNameNodes) {
|
||||
String name = ASTUtils.getLiteralValue(nameNode);
|
||||
String type = getBeanType(node);
|
||||
symbols.add(new SymbolInformation(
|
||||
beanLabel(name, type),
|
||||
SymbolKind.Interface,
|
||||
new Location(doc.getUri(), doc.toRange(ASTUtils.stringRegion(doc, nameNode)))
|
||||
));
|
||||
}
|
||||
return symbols.build();
|
||||
} else {
|
||||
return ImmutableList.of(new SymbolInformation(
|
||||
beanLabel(getBeanName(node), getBeanType(node)),
|
||||
SymbolKind.Interface,
|
||||
new Location(doc.getUri(), doc.toRange(ASTUtils.nameRegion(doc, node)))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
protected String beanLabel(String beanName, String beanType) {
|
||||
StringBuilder symbolLabel = new StringBuilder();
|
||||
symbolLabel.append("@+ ");
|
||||
|
||||
String beanName = getBeanName(node);
|
||||
String beanType = getBeanType(node);
|
||||
|
||||
symbolLabel.append('\'');
|
||||
symbolLabel.append(beanName);
|
||||
symbolLabel.append('\'');
|
||||
symbolLabel.append(" (@Bean) ");
|
||||
symbolLabel.append(beanType);
|
||||
|
||||
SymbolInformation symbol = new SymbolInformation(symbolLabel.toString(), SymbolKind.Interface,
|
||||
new Location(doc.getUri(), doc.toRange(node.getStartPosition(), node.getLength())));
|
||||
return symbol;
|
||||
return symbolLabel.toString();
|
||||
}
|
||||
protected Collection<StringLiteral> getBeanNameLiterals(Annotation node) {
|
||||
ImmutableList.Builder<StringLiteral> literals = ImmutableList.builder();
|
||||
for (String attrib : NAME_ATTRIBUTES) {
|
||||
ASTUtils.getAttribute(node, attrib).ifPresent((valueExp) -> {
|
||||
literals.addAll(ASTUtils.getExpressionValueAsListOfLiterals(valueExp));
|
||||
});
|
||||
}
|
||||
return literals.build();
|
||||
}
|
||||
|
||||
private String getBeanName(Annotation node) {
|
||||
protected String getBeanName(Annotation node) {
|
||||
ASTNode parent = node.getParent();
|
||||
if (parent instanceof MethodDeclaration) {
|
||||
MethodDeclaration method = (MethodDeclaration) parent;
|
||||
@@ -121,7 +152,7 @@ public class BeansSymbolProvider implements SymbolProvider {
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getBeanType(Annotation node) {
|
||||
protected String getBeanType(Annotation node) {
|
||||
ASTNode parent = node.getParent();
|
||||
if (parent instanceof MethodDeclaration) {
|
||||
MethodDeclaration method = (MethodDeclaration) parent;
|
||||
|
||||
@@ -15,6 +15,7 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
@@ -31,6 +32,7 @@ import org.eclipse.jdt.core.dom.StringLiteral;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion;
|
||||
import org.springframework.ide.vscode.commons.util.CollectorUtil;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
@@ -47,7 +49,6 @@ public class ASTUtils {
|
||||
return new DocumentRegion(doc, start, end);
|
||||
}
|
||||
|
||||
|
||||
public static Optional<Range> nameRange(TextDocument doc, Annotation annotation) {
|
||||
try {
|
||||
return Optional.of(nameRegion(doc, annotation).asRange());
|
||||
@@ -57,6 +58,24 @@ public class ASTUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static DocumentRegion stringRegion(TextDocument doc, StringLiteral node) {
|
||||
DocumentRegion nodeRegion = nodeRegion(doc, node);
|
||||
if (nodeRegion.startsWith("\"")) {
|
||||
nodeRegion = nodeRegion.subSequence(1);
|
||||
}
|
||||
if (nodeRegion.endsWith("\"")) {
|
||||
nodeRegion = nodeRegion.subSequence(0, nodeRegion.getLength()-1);
|
||||
}
|
||||
return nodeRegion;
|
||||
}
|
||||
|
||||
|
||||
public static DocumentRegion nodeRegion(TextDocument doc, ASTNode node) {
|
||||
int start = node.getStartPosition();
|
||||
int end = start + node.getLength();
|
||||
return new DocumentRegion(doc, start, end);
|
||||
}
|
||||
|
||||
public static Optional<Expression> getAttribute(Annotation annotation, String name) {
|
||||
try {
|
||||
if (annotation.isSingleMemberAnnotation() && name.equals("value")) {
|
||||
@@ -179,6 +198,22 @@ public class ASTUtils {
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static List<StringLiteral> getExpressionValueAsListOfLiterals(Expression exp) {
|
||||
if (exp instanceof ArrayInitializer) {
|
||||
ArrayInitializer array = (ArrayInitializer) exp;
|
||||
return ((List<Expression>) array.expressions()).stream()
|
||||
.flatMap(e -> e instanceof StringLiteral
|
||||
? Stream.of((StringLiteral)e)
|
||||
: Stream.empty()
|
||||
)
|
||||
.collect(CollectorUtil.toImmutableList());
|
||||
} else if (exp instanceof StringLiteral){
|
||||
return ImmutableList.of((StringLiteral)exp);
|
||||
}
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
|
||||
public static Collection<Annotation> getAnnotations(TypeDeclaration declaringType) {
|
||||
Object modifiersObj = declaringType.getStructuralProperty(TypeDeclaration.MODIFIERS2_PROPERTY);
|
||||
@@ -203,4 +238,6 @@ public class ASTUtils {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.junit.Test;
|
||||
import org.springframework.ide.vscode.boot.java.Annotations;
|
||||
import org.springframework.ide.vscode.boot.java.beans.BeansSymbolProvider;
|
||||
import org.springframework.ide.vscode.boot.java.beans.ComponentSymbolProvider;
|
||||
import org.springframework.ide.vscode.boot.java.beans.test.SpringIndexerHarness.TestSymbolInfo;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider;
|
||||
import org.springframework.ide.vscode.boot.java.utils.SpringIndexer;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
@@ -53,14 +54,45 @@ public class SpringIndexerBeansTest {
|
||||
|
||||
@Test
|
||||
public void testScanSimpleConfigurationClass() throws Exception {
|
||||
SpringIndexer indexer = new SpringIndexer(harness.getServer(), projectFinder, symbolProviders);
|
||||
SpringIndexerHarness indexer = new SpringIndexerHarness(harness.getServer(), projectFinder, symbolProviders);
|
||||
File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-beans/").toURI());
|
||||
indexer.initialize(directory.toPath());
|
||||
|
||||
String uriPrefix = "file://" + directory.getAbsolutePath();
|
||||
List<? extends SymbolInformation> symbols = indexer.getSymbols(uriPrefix + "/src/main/java/org/test/SimpleConfiguration.java");
|
||||
assertEquals(2, symbols.size());
|
||||
assertTrue(containsSymbol(symbols, "@+ 'simpleBean' (@Bean) BeanClass", uriPrefix + "/src/main/java/org/test/SimpleConfiguration.java", 8, 1, 8, 8));
|
||||
indexer.assertDocumentSymbols(uriPrefix + "/src/main/java/org/test/SimpleConfiguration.java",
|
||||
symbol("@Configuration", "@Configuration"),
|
||||
symbol("@Bean", "@+ 'simpleBean' (@Bean) BeanClass")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testScanSpecialConfigurationClass() throws Exception {
|
||||
SpringIndexerHarness indexer = new SpringIndexerHarness(harness.getServer(), projectFinder, symbolProviders);
|
||||
File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-beans/").toURI());
|
||||
indexer.initialize(directory.toPath());
|
||||
|
||||
String uriPrefix = "file://" + directory.getAbsolutePath();
|
||||
String docUri = uriPrefix + "/src/main/java/org/test/SpecialConfiguration.java";
|
||||
indexer.assertDocumentSymbols(docUri,
|
||||
symbol("@Configuration", "@Configuration"),
|
||||
|
||||
// @Bean("implicitNamedBean")
|
||||
symbol("implicitNamedBean", "@+ 'implicitNamedBean' (@Bean) BeanClass"),
|
||||
|
||||
// @Bean(value="valueBean")
|
||||
symbol("valueBean", "@+ 'valueBean' (@Bean) BeanClass"),
|
||||
|
||||
// @Bean(value= {"valueBean1", "valueBean2"})
|
||||
symbol("valueBean1", "@+ 'valueBean1' (@Bean) BeanClass"),
|
||||
symbol("valueBean2", "@+ 'valueBean2' (@Bean) BeanClass"),
|
||||
|
||||
// @Bean(name="namedBean")
|
||||
symbol("namedBean", "@+ 'namedBean' (@Bean) BeanClass"),
|
||||
|
||||
// @Bean(name= {"namedBean1", "namedBean2"})
|
||||
symbol("namedBean1", "@+ 'namedBean1' (@Bean) BeanClass"),
|
||||
symbol("namedBean2", "@+ 'namedBean2' (@Bean) BeanClass")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -87,6 +119,9 @@ public class SpringIndexerBeansTest {
|
||||
assertTrue(containsSymbol(symbols, "@+ 'simpleComponent' (@Component) SimpleComponent", uriPrefix + "/src/main/java/org/test/SimpleComponent.java", 4, 0, 4, 10));
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// harness code
|
||||
|
||||
private boolean containsSymbol(List<? extends SymbolInformation> symbols, String name, String uri, int startLine, int startCHaracter, int endLine, int endCharacter) {
|
||||
for (Iterator<? extends SymbolInformation> iterator = symbols.iterator(); iterator.hasNext();) {
|
||||
SymbolInformation symbol = iterator.next();
|
||||
@@ -104,4 +139,7 @@ public class SpringIndexerBeansTest {
|
||||
return false;
|
||||
}
|
||||
|
||||
private TestSymbolInfo symbol(String coveredText, String label) {
|
||||
return new TestSymbolInfo(coveredText, label);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Pivotal, Inc.
|
||||
* 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
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.beans.test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.SymbolInformation;
|
||||
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServer;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider;
|
||||
import org.springframework.ide.vscode.boot.java.utils.SpringIndexer;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.util.text.LanguageId;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
import org.springframework.ide.vscode.languageserver.testharness.Editor;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
public class SpringIndexerHarness {
|
||||
|
||||
public static class TestSymbolInfo {
|
||||
|
||||
private String coveredText;
|
||||
private String label;
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((coveredText == null) ? 0 : coveredText.hashCode());
|
||||
result = prime * result + ((label == null) ? 0 : label.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
TestSymbolInfo other = (TestSymbolInfo) obj;
|
||||
if (coveredText == null) {
|
||||
if (other.coveredText != null)
|
||||
return false;
|
||||
} else if (!coveredText.equals(other.coveredText))
|
||||
return false;
|
||||
if (label == null) {
|
||||
if (other.label != null)
|
||||
return false;
|
||||
} else if (!label.equals(other.label))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "["+coveredText+"] => " + label;
|
||||
}
|
||||
|
||||
public TestSymbolInfo(String coveredText, String label) {
|
||||
this.coveredText = coveredText;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final Comparator<Range> RANGE_COMPARATOR = Editor.RANGE_COMPARATOR;
|
||||
private static final Comparator<SymbolInformation> SYMBOL_COMPARATOR = new Comparator<SymbolInformation>() {
|
||||
|
||||
@Override
|
||||
public int compare(SymbolInformation o1, SymbolInformation o2) {
|
||||
int r = o1.getLocation().getUri().compareTo(o2.getLocation().getUri());
|
||||
if (r!=0) return r;
|
||||
|
||||
r = RANGE_COMPARATOR.compare(o1.getLocation().getRange(), o2.getLocation().getRange());
|
||||
if (r!=0) return r;
|
||||
|
||||
return o1.getName().compareTo(o2.getName());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
private SpringIndexer indexer;
|
||||
|
||||
public SpringIndexerHarness(BootJavaLanguageServer server, JavaProjectFinder projectFinder, Map<String, SymbolProvider> symbolProviders) {
|
||||
this.indexer = new SpringIndexer(server, projectFinder, symbolProviders);
|
||||
}
|
||||
|
||||
public void assertDocumentSymbols(String documentUri, TestSymbolInfo... expectedSymbols) throws Exception {
|
||||
List<TestSymbolInfo> actualSymbols = getSymbolsInFile(documentUri);
|
||||
assertEquals(symbolsString(Arrays.asList(expectedSymbols)), symbolsString(actualSymbols));
|
||||
}
|
||||
|
||||
private String symbolsString(List<TestSymbolInfo> symbols) {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
for (TestSymbolInfo s : symbols) {
|
||||
buf.append(s+"\n");
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public List<TestSymbolInfo> getSymbolsInFile(String docURI) throws Exception {
|
||||
List<? extends SymbolInformation> symbols = indexer.getSymbols(docURI);
|
||||
if (symbols!=null) {
|
||||
symbols = new ArrayList<>(symbols);
|
||||
Collections.sort(symbols, SYMBOL_COMPARATOR);
|
||||
TextDocument doc = new TextDocument(docURI, LanguageId.JAVA, 1, IOUtils.toString(new URI(docURI)));
|
||||
ImmutableList.Builder<TestSymbolInfo> symbolInfos = ImmutableList.builder();
|
||||
for (SymbolInformation s : symbols) {
|
||||
int start = doc.toOffset(s.getLocation().getRange().getStart());
|
||||
int end = doc.toOffset(s.getLocation().getRange().getEnd());
|
||||
symbolInfos.add(new TestSymbolInfo(doc.textBetween(start, end), s.getName()));
|
||||
}
|
||||
return symbolInfos.build();
|
||||
}
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
public void initialize(Path wsRoot) {
|
||||
indexer.initialize(wsRoot);
|
||||
}
|
||||
}
|
||||
@@ -6,16 +6,29 @@ import org.springframework.context.annotation.Configuration;
|
||||
@Configuration
|
||||
public class SpecialConfiguration {
|
||||
|
||||
@Bean("namedBean")
|
||||
@Bean("implicitNamedBean")
|
||||
public BeanClass specialBean() {
|
||||
return new BeanClass();
|
||||
}
|
||||
|
||||
@Bean(name= {"beanName1","beanName2"})
|
||||
public BeanClass beanWithAlias() {
|
||||
|
||||
@Bean(value="valueBean")
|
||||
public BeanClass specialBean2() {
|
||||
return new BeanClass();
|
||||
}
|
||||
|
||||
@Bean(value= {"valueBean1", "valueBean2"})
|
||||
public BeanClass specialBean3() {
|
||||
return new BeanClass();
|
||||
}
|
||||
|
||||
@Bean(name="namedBean")
|
||||
public BeanClass specialBean4() {
|
||||
return new BeanClass();
|
||||
}
|
||||
|
||||
@Bean(name= {"namedBean1", "namedBean2"})
|
||||
public BeanClass specialBean5() {
|
||||
return new BeanClass();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user