diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/annotations/AnnotationHierarchyAwareLookup.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/annotations/AnnotationHierarchyAwareLookup.java index 2195af2f6..83e9843d5 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/annotations/AnnotationHierarchyAwareLookup.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/annotations/AnnotationHierarchyAwareLookup.java @@ -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 { return found.build(); } + public Collection getAll() { + ImmutableList.Builder found = ImmutableList.builder(); + Collection> values = bindings.values(); + values.forEach(binding -> found.add(binding.value)); + return found.build(); + } + private void findElements(ITypeBinding typeBinding, HashSet seen, Consumer requestor) { String qname = typeBinding.getQualifiedName(); if (seen.add(qname)) { diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeansSymbolProvider.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeansSymbolProvider.java index b60159be4..e30ca4d66 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeansSymbolProvider.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeansSymbolProvider.java @@ -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 getSymbols(Annotation node, ITypeBinding annotationType, Collection metaAnnotations, TextDocument doc) { boolean isFunction = isFunctionBean(node); + ImmutableList.Builder symbols = ImmutableList.builder(); String beanType = getBeanType(node); for (Tuple2 nameAndRegion : getBeanNames(node, doc)) { @@ -107,16 +72,65 @@ public class BeansSymbolProvider implements SymbolProvider { return symbols.build(); } - Collection> getBeanNames(Annotation node, TextDocument doc) { + @Override + public Collection getSymbols(TypeDeclaration typeDeclaration, TextDocument doc) { + Tuple3 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 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> getBeanNames(Annotation node, TextDocument doc) { Collection beanNameNodes = getBeanNameLiterals(node); - if (beanNameNodes!=null && !beanNameNodes.isEmpty()) { + + if (beanNameNodes != null && !beanNameNodes.isEmpty()) { ImmutableList.Builder> 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 getBeanNameLiterals(Annotation node) { ImmutableList.Builder 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; + } + + } diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentSymbolProvider.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentSymbolProvider.java index 657ad9de3..aa745f103 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentSymbolProvider.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentSymbolProvider.java @@ -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 getSymbols(TypeDeclaration typeDeclaration, TextDocument doc) { + // TODO Auto-generated method stub + return null; + } + } diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/SymbolProvider.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/SymbolProvider.java index 3c0ca62cf..ff4802e26 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/SymbolProvider.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/SymbolProvider.java @@ -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 getSymbols(Annotation node, ITypeBinding typeBinding, Collection metaAnnotations, TextDocument doc); + Collection getSymbols(TypeDeclaration typeDeclaration, TextDocument doc); } diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingSymbolProvider.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingSymbolProvider.java index f7ca9ae55..ec9d4d7a0 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingSymbolProvider.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingSymbolProvider.java @@ -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 getSymbols(TypeDeclaration typeDeclaration, TextDocument doc) { + return null; + } + } diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexer.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexer.java index fcac22a3d..8d4bc1038 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexer.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexer.java @@ -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 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 docRef, String content) throws Exception { + Collection providers = symbolProviders.getAll(); + if (!providers.isEmpty()) { + TextDocument doc = getTempTextDocument(docURI, docRef, content); + for (SymbolProvider provider : providers) { + Collection sbls = provider.getSymbols(typeDeclaration, doc); + if (sbls != null) { + sbls.forEach(symbol -> { + symbols.add(symbol); + symbolsByDoc.computeIfAbsent(docURI, s -> new ArrayList()).add(symbol); + }); + } + } + } + } + private void extractSymbolInformation(Annotation node, String docURI, AtomicReference docRef, String content) throws Exception { ITypeBinding typeBinding = node.resolveTypeBinding(); diff --git a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerBeansTest.java b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerBeansTest.java index a4a9acc90..2e5268eb3 100644 --- a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerBeansTest.java +++ b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerBeansTest.java @@ -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") - ); - } - @Test public void testScanSimpleComponentClass() throws Exception { SpringIndexerHarness indexer = new SpringIndexerHarness(harness.getServer(), projectFinder, symbolProviders); diff --git a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerFunctionBeansTest.java b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerFunctionBeansTest.java new file mode 100644 index 000000000..ac32c0579 --- /dev/null +++ b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerFunctionBeansTest.java @@ -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 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") + ); + } + + @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") + ); + } + + + //////////////////////////////// + // harness code + + private TestSymbolInfo symbol(String coveredText, String label) { + return new TestSymbolInfo(coveredText, label); + } +} diff --git a/headless-services/boot-java-language-server/src/test/resources/test-projects/test-annotation-indexing-beans/src/main/java/org/test/ScannedFunctionClass.java b/headless-services/boot-java-language-server/src/test/resources/test-projects/test-annotation-indexing-beans/src/main/java/org/test/ScannedFunctionClass.java new file mode 100644 index 000000000..838951e0f --- /dev/null +++ b/headless-services/boot-java-language-server/src/test/resources/test-projects/test-annotation-indexing-beans/src/main/java/org/test/ScannedFunctionClass.java @@ -0,0 +1,12 @@ +package org.test; + +import java.util.function.Function; + +public class ScannedFunctionClass implements Function { + + @Override + public String apply(String t) { + return t.toUpperCase(); + } + +}