first cut for providing symbols for function beans from ckasses without annotation

This commit is contained in:
Martin Lippert
2018-01-01 16:58:58 +01:00
parent 16888fe1fe
commit 13fc1ba6fa
9 changed files with 236 additions and 63 deletions

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017 Pivotal, Inc.
* Copyright (c) 2017, 2018 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
@@ -93,6 +93,13 @@ public class AnnotationHierarchyAwareLookup<T> {
return found.build();
}
public Collection<T> getAll() {
ImmutableList.Builder<T> found = ImmutableList.builder();
Collection<Binding<T>> values = bindings.values();
values.forEach(binding -> found.add(binding.value));
return found.build();
}
private void findElements(ITypeBinding typeBinding, HashSet<String> seen, Consumer<T> requestor) {
String qname = typeBinding.getQualifiedName();
if (seen.add(qname)) {

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017 Pivotal, Inc.
* Copyright (c) 2017, 2018 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
@@ -11,6 +11,7 @@
package org.springframework.ide.vscode.boot.java.beans;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -22,6 +23,7 @@ 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.jdt.core.dom.TypeDeclaration;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.SymbolKind;
@@ -35,6 +37,7 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument;
import com.google.common.collect.ImmutableList;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuple3;
import reactor.util.function.Tuples;
/**
@@ -46,51 +49,13 @@ 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"};
private boolean isFunctionBean(Annotation node) {
ASTNode parent = node.getParent();
if (parent instanceof MethodDeclaration) {
MethodDeclaration method = (MethodDeclaration) parent;
String returnType = null;
if (method.getReturnType2().isParameterizedType()) {
ParameterizedType paramType = (ParameterizedType) method.getReturnType2();
Type type = paramType.getType();
ITypeBinding typeBinding = type.resolveBinding();
returnType = typeBinding.getBinaryName();
}
else {
returnType = method.getReturnType2().resolveBinding().getQualifiedName();
}
return FUNCTION_FUNCTION_TYPE.equals(returnType) || FUNCTION_CONSUMER_TYPE.equals(returnType)
|| FUNCTION_SUPPLIER_TYPE.equals(returnType);
}
return false;
}
// private SymbolInformation createFunctionSymbol(Annotation node, TextDocument doc) throws BadLocationException {
// 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;
// }
@Override
public Collection<SymbolInformation> getSymbols(Annotation node, ITypeBinding annotationType, Collection<ITypeBinding> metaAnnotations, TextDocument doc) {
boolean isFunction = isFunctionBean(node);
ImmutableList.Builder<SymbolInformation> symbols = ImmutableList.builder();
String beanType = getBeanType(node);
for (Tuple2<String, DocumentRegion> nameAndRegion : getBeanNames(node, doc)) {
@@ -107,16 +72,65 @@ public class BeansSymbolProvider implements SymbolProvider {
return symbols.build();
}
Collection<Tuple2<String, DocumentRegion>> getBeanNames(Annotation node, TextDocument doc) {
@Override
public Collection<SymbolInformation> getSymbols(TypeDeclaration typeDeclaration, TextDocument doc) {
Tuple3<String, String, DocumentRegion> functionBean = getFunctionBean(typeDeclaration, doc);
if (functionBean != null) {
try {
SymbolInformation symbol = new SymbolInformation(
beanLabel(true, functionBean.getT1(), functionBean.getT2()),
SymbolKind.Interface,
new Location(doc.getUri(), doc.toRange(functionBean.getT3())));
return ImmutableList.of(symbol);
} catch (BadLocationException e) {
Log.log(e);
}
}
return ImmutableList.of();
}
protected Tuple3<String, String, DocumentRegion> getFunctionBean(TypeDeclaration typeDeclaration, TextDocument doc) {
List<?> interfaceTypes = typeDeclaration.superInterfaceTypes();
if (interfaceTypes != null && interfaceTypes.size() > 0) {
for (Object interfaceType : interfaceTypes) {
Type type = (Type) interfaceType;
String simplifiedType = null;
if (type.isParameterizedType()) {
ParameterizedType paramType = (ParameterizedType) type;
Type simpleType = paramType.getType();
ITypeBinding typeBinding = simpleType.resolveBinding();
simplifiedType = typeBinding.getBinaryName();
}
else {
simplifiedType = type.resolveBinding().getQualifiedName();
}
if (FUNCTION_FUNCTION_TYPE.equals(simplifiedType) || FUNCTION_CONSUMER_TYPE.equals(simplifiedType)
|| FUNCTION_SUPPLIER_TYPE.equals(simplifiedType)) {
String beanName = getBeanName(typeDeclaration);
String beanType = type.resolveBinding().getName();
DocumentRegion region = ASTUtils.nodeRegion(doc, typeDeclaration.getName());
return Tuples.of(beanName, beanType, region);
}
}
}
return null;
}
protected Collection<Tuple2<String, DocumentRegion>> getBeanNames(Annotation node, TextDocument doc) {
Collection<StringLiteral> beanNameNodes = getBeanNameLiterals(node);
if (beanNameNodes!=null && !beanNameNodes.isEmpty()) {
if (beanNameNodes != null && !beanNameNodes.isEmpty()) {
ImmutableList.Builder<Tuple2<String,DocumentRegion>> namesAndRegions = ImmutableList.builder();
for (StringLiteral nameNode : beanNameNodes) {
String name = ASTUtils.getLiteralValue(nameNode);
namesAndRegions.add(Tuples.of(name, ASTUtils.stringRegion(doc, nameNode)));
}
return namesAndRegions.build();
} else {
}
else {
ASTNode parent = node.getParent();
if (parent instanceof MethodDeclaration) {
MethodDeclaration method = (MethodDeclaration) parent;
@@ -141,6 +155,7 @@ public class BeansSymbolProvider implements SymbolProvider {
symbolLabel.append(beanType);
return symbolLabel.toString();
}
protected Collection<StringLiteral> getBeanNameLiterals(Annotation node) {
ImmutableList.Builder<StringLiteral> literals = ImmutableList.builder();
for (String attrib : NAME_ATTRIBUTES) {
@@ -160,4 +175,36 @@ public class BeansSymbolProvider implements SymbolProvider {
}
return null;
}
protected String getBeanName(TypeDeclaration typeDeclaration) {
String beanName = typeDeclaration.getName().toString();
if (beanName.length() > 0 && Character.isUpperCase(beanName.charAt(0))) {
beanName = Character.toLowerCase(beanName.charAt(0)) + beanName.substring(1);
}
return beanName;
}
private boolean isFunctionBean(Annotation node) {
ASTNode parent = node.getParent();
if (parent instanceof MethodDeclaration) {
MethodDeclaration method = (MethodDeclaration) parent;
String returnType = null;
if (method.getReturnType2().isParameterizedType()) {
ParameterizedType paramType = (ParameterizedType) method.getReturnType2();
Type type = paramType.getType();
ITypeBinding typeBinding = type.resolveBinding();
returnType = typeBinding.getBinaryName();
}
else {
returnType = method.getReturnType2().resolveBinding().getQualifiedName();
}
return FUNCTION_FUNCTION_TYPE.equals(returnType) || FUNCTION_CONSUMER_TYPE.equals(returnType)
|| FUNCTION_SUPPLIER_TYPE.equals(returnType);
}
return false;
}
}

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017 Pivotal, Inc.
* Copyright (c) 2017, 2018 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
@@ -111,4 +111,10 @@ public class ComponentSymbolProvider implements SymbolProvider {
return null;
}
@Override
public Collection<SymbolInformation> getSymbols(TypeDeclaration typeDeclaration, TextDocument doc) {
// TODO Auto-generated method stub
return null;
}
}

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017 Pivotal, Inc.
* Copyright (c) 2017, 2018 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
@@ -14,6 +14,7 @@ import java.util.Collection;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.lsp4j.SymbolInformation;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
@@ -24,5 +25,6 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument;
public interface SymbolProvider {
Collection<SymbolInformation> getSymbols(Annotation node, ITypeBinding typeBinding, Collection<ITypeBinding> metaAnnotations, TextDocument doc);
Collection<SymbolInformation> getSymbols(TypeDeclaration typeDeclaration, TextDocument doc);
}

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017 Pivotal, Inc.
* Copyright (c) 2017, 2018 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
@@ -176,4 +176,9 @@ public class RequestMappingSymbolProvider implements SymbolProvider {
return null;
}
@Override
public Collection<SymbolInformation> getSymbols(TypeDeclaration typeDeclaration, TextDocument doc) {
return null;
}
}

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017 Pivotal, Inc.
* Copyright (c) 2017, 2018 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
@@ -43,6 +43,7 @@ import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MarkerAnnotation;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.SymbolKind;
@@ -342,6 +343,17 @@ public class SpringIndexer {
private void scanAST(final CompilationUnit cu, final String docURI, AtomicReference<TextDocument> docRef, final String content) {
cu.accept(new ASTVisitor() {
@Override
public boolean visit(TypeDeclaration node) {
try {
extractSymbolInformation(node, docURI, docRef, content);
}
catch (Exception e) {
e.printStackTrace();
}
return super.visit(node);
}
@Override
public boolean visit(SingleMemberAnnotation node) {
try {
@@ -380,6 +392,22 @@ public class SpringIndexer {
});
}
private void extractSymbolInformation(TypeDeclaration typeDeclaration, String docURI, AtomicReference<TextDocument> docRef, String content) throws Exception {
Collection<SymbolProvider> providers = symbolProviders.getAll();
if (!providers.isEmpty()) {
TextDocument doc = getTempTextDocument(docURI, docRef, content);
for (SymbolProvider provider : providers) {
Collection<SymbolInformation> sbls = provider.getSymbols(typeDeclaration, doc);
if (sbls != null) {
sbls.forEach(symbol -> {
symbols.add(symbol);
symbolsByDoc.computeIfAbsent(docURI, s -> new ArrayList<SymbolInformation>()).add(symbol);
});
}
}
}
}
private void extractSymbolInformation(Annotation node, String docURI, AtomicReference<TextDocument> docRef, String content) throws Exception {
ITypeBinding typeBinding = node.resolveTypeBinding();

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017 Pivotal, Inc.
* Copyright (c) 2017, 2018 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
@@ -86,19 +86,6 @@ public class SpringIndexerBeansTest {
);
}
@Test
public void testScanSimpleFunctionBean() 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(indexer.wsFolder(directory));
String uriPrefix = "file://" + directory.getAbsolutePath();
indexer.assertDocumentSymbols(uriPrefix + "/src/main/java/org/test/FunctionClass.java",
symbol("@Configuration", "@+ 'functionClass' (@Configuration <: @Component) FunctionClass"),
symbol("@Bean", "@> 'uppercase' (@Bean) Function<String,String>")
);
}
@Test
public void testScanSimpleComponentClass() throws Exception {
SpringIndexerHarness indexer = new SpringIndexerHarness(harness.getServer(), projectFinder, symbolProviders);

View File

@@ -0,0 +1,79 @@
/*******************************************************************************
* Copyright (c) 2017, 2018 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 java.io.File;
import org.junit.Before;
import org.junit.Test;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchyAwareLookup;
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.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness;
import org.springframework.ide.vscode.project.harness.ProjectsHarness;
/**
* @author Martin Lippert
*/
public class SpringIndexerFunctionBeansTest {
private AnnotationHierarchyAwareLookup<SymbolProvider> symbolProviders;
private BootLanguageServerHarness harness;
private JavaProjectFinder projectFinder;
@Before
public void setup() throws Exception {
symbolProviders = new AnnotationHierarchyAwareLookup<>();
symbolProviders.put(Annotations.BEAN, new BeansSymbolProvider());
symbolProviders.put(Annotations.COMPONENT, new ComponentSymbolProvider());
harness = BootLanguageServerHarness.builder().build();
projectFinder = harness.getProjectFinder();
harness.intialize(new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-beans/").toURI()));
}
@Test
public void testScanSimpleFunctionBean() 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(indexer.wsFolder(directory));
String uriPrefix = "file://" + directory.getAbsolutePath();
indexer.assertDocumentSymbols(uriPrefix + "/src/main/java/org/test/FunctionClass.java",
symbol("@Configuration", "@+ 'functionClass' (@Configuration <: @Component) FunctionClass"),
symbol("@Bean", "@> 'uppercase' (@Bean) Function<String,String>")
);
}
@Test
public void testScanSimpleFunctionClass() 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(indexer.wsFolder(directory));
String uriPrefix = "file://" + directory.getAbsolutePath();
indexer.assertDocumentSymbols(uriPrefix + "/src/main/java/org/test/ScannedFunctionClass.java",
symbol("ScannedFunctionClass", "@> 'scannedFunctionClass' (@Bean) Function<String,String>")
);
}
////////////////////////////////
// harness code
private TestSymbolInfo symbol(String coveredText, String label) {
return new TestSymbolInfo(coveredText, label);
}
}

View File

@@ -0,0 +1,12 @@
package org.test;
import java.util.function.Function;
public class ScannedFunctionClass implements Function<String, String> {
@Override
public String apply(String t) {
return t.toUpperCase();
}
}