Use annotation attributes in creation of @Bean symbols

This commit is contained in:
Kris De Volder
2017-11-10 13:54:30 -08:00
parent 8877c2a0d5
commit 09407cb3ad
8 changed files with 308 additions and 25 deletions

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}