From 733e59afd1002253d32773ccda79b91474d141e1 Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Mon, 20 Nov 2017 15:10:49 -0800 Subject: [PATCH] Rework symbol provider inheritance to create fewer symbols with more info --- .../boot/java/BootJavaLanguageServer.java | 6 +- .../annotations/AnnotationHierarchies.java | 29 ++++++-- ...nnotationHierarchyAwareFactoryManager.java | 66 ------------------- .../AnnotationHierarchyAwareLookup.java | 40 +++++------ .../annotations/SymbolProviderLookup.java | 51 ++++++++++++++ .../boot/java/beans/BeansSymbolProvider.java | 2 +- .../java/beans/ComponentSymbolProvider.java | 54 +++++++-------- .../boot/java/handlers/SymbolProvider.java | 2 +- .../RequestMappingSymbolProvider.java | 2 +- .../vscode/boot/java/utils/SpringIndexer.java | 10 +-- .../beans/test/SpringIndexerBeansTest.java | 41 +++--------- .../java/beans/test/SpringIndexerHarness.java | 4 +- .../java/utils/test/SpringIndexerTest.java | 29 ++++---- 13 files changed, 150 insertions(+), 186 deletions(-) delete mode 100644 headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/annotations/AnnotationHierarchyAwareFactoryManager.java create mode 100644 headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/annotations/SymbolProviderLookup.java diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java index acc1a9e2b..1970ca546 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java @@ -20,7 +20,7 @@ import org.eclipse.lsp4j.InitializeParams; import org.eclipse.lsp4j.InitializeResult; import org.eclipse.lsp4j.Registration; import org.eclipse.lsp4j.RegistrationParams; -import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchyAwareFactoryManager; +import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchyAwareLookup; import org.springframework.ide.vscode.boot.java.autowired.AutowiredHoverProvider; import org.springframework.ide.vscode.boot.java.beans.BeansSymbolProvider; import org.springframework.ide.vscode.boot.java.beans.ComponentSymbolProvider; @@ -313,7 +313,7 @@ public class BootJavaLanguageServer extends SimpleLanguageServer { } protected SpringIndexer createAnnotationIndexer(SimpleLanguageServer server, JavaProjectFinder projectFinder) { - AnnotationHierarchyAwareFactoryManager providers = new AnnotationHierarchyAwareFactoryManager<>(); + AnnotationHierarchyAwareLookup providers = new AnnotationHierarchyAwareLookup<>(); providers.put(Annotations.SPRING_REQUEST_MAPPING, new RequestMappingSymbolProvider()); providers.put(Annotations.SPRING_GET_MAPPING, new RequestMappingSymbolProvider()); @@ -327,7 +327,7 @@ public class BootJavaLanguageServer extends SimpleLanguageServer { new RequestMappingSymbolProvider()); providers.put(Annotations.BEAN, new BeansSymbolProvider()); - providers.putFactory(Annotations.COMPONENT, ComponentSymbolProvider::new); + providers.put(Annotations.COMPONENT, new ComponentSymbolProvider()); return new SpringIndexer(this, projectFinder, providers); } diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/annotations/AnnotationHierarchies.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/annotations/AnnotationHierarchies.java index f5f480738..19d9e8274 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/annotations/AnnotationHierarchies.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/annotations/AnnotationHierarchies.java @@ -11,19 +11,23 @@ package org.springframework.ide.vscode.boot.java.annotations; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; import java.util.stream.Stream; import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.IAnnotationBinding; import org.eclipse.jdt.core.dom.ITypeBinding; +import org.gradle.internal.io.SkipFirstTextStream; +import org.springframework.ide.vscode.commons.util.CollectorUtil; import org.springframework.ide.vscode.commons.util.Log; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; import com.google.common.collect.ImmutableList; /** @@ -67,11 +71,11 @@ public abstract class AnnotationHierarchies { return seen; } - public static Stream findTransitiveSupers(ITypeBinding typeBinding, Set seen) { + public static Stream findTransitiveSupers(ITypeBinding typeBinding, Set seen) { String qname = typeBinding.getQualifiedName(); if (seen.add(qname)) { return Stream.concat( - Stream.of(qname), + Stream.of(typeBinding), getDirectSuperAnnotations(typeBinding).stream().flatMap(superBinding -> findTransitiveSupers(superBinding, seen) ) @@ -84,9 +88,22 @@ public abstract class AnnotationHierarchies { ITypeBinding annotationType = annotation.resolveTypeBinding(); if (annotationType!=null) { return findTransitiveSupers(annotationType, new HashSet<>()) - .anyMatch(superType -> superType.equals(fqAnnotationTypeName)); + .anyMatch(superType -> superType.getQualifiedName().equals(fqAnnotationTypeName)); } return false; } + public static Collection getMetaAnnotations(ITypeBinding actualAnnotation, Predicate isKeyAnnotationName) { + Stream allSupers = findTransitiveSupers(actualAnnotation, new HashSet<>()) + .skip(1); //Don't include 'actualAnnotation' itself. + return allSupers + .filter(candidate -> isMetaAnnotation(candidate, isKeyAnnotationName)) + .collect(CollectorUtil.toImmutableList()); + } + + private static boolean isMetaAnnotation(ITypeBinding candidate, Predicate isKeyAnnotationName) { + return findTransitiveSupers(candidate, new HashSet<>()) + .anyMatch(sa -> isKeyAnnotationName.test(sa.getQualifiedName())); + } + } diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/annotations/AnnotationHierarchyAwareFactoryManager.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/annotations/AnnotationHierarchyAwareFactoryManager.java deleted file mode 100644 index f630ff868..000000000 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/annotations/AnnotationHierarchyAwareFactoryManager.java +++ /dev/null @@ -1,66 +0,0 @@ -/******************************************************************************* - * 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.annotations; - -import java.util.Collection; - -import org.eclipse.jdt.core.dom.ITypeBinding; - -import com.google.common.collect.ImmutableList; - -import reactor.util.function.Tuple2; - -/** - * @author Kris De Volder - */ -public class AnnotationHierarchyAwareFactoryManager { - - @FunctionalInterface - public interface Factory { - T create(String fqAnnotationType); - } - - private AnnotationHierarchyAwareLookup> factories = new AnnotationHierarchyAwareLookup>(); - - /** - * Deprecated, for proper handling of annotation inheritance, use putFactory method instead. - */ - @Deprecated - public void put(String fqAnnotationType, T value) { - factories.put(fqAnnotationType, true, (actualAnnotationType) -> - actualAnnotationType.equals(fqAnnotationType) ? value : null - ); - } - - /** - * Add a 'base' factory which creates a `T` for a given type of annotation. - * The factory will be passed the fq name of the annotation 'base' annotation. - * It is acceptable for the Factory to return null. Null values will simply - * be ignored. - */ - public void putFactory(String fqAnnotationType, Factory factory) { - factories.put(fqAnnotationType, false, factory); - } - - public Collection get(ITypeBinding typeBinding) { - ImmutableList.Builder builder = ImmutableList.builder(); - for (Tuple2> entry : factories.get(typeBinding)) { - String superAnnotationName = entry.getT1(); - Factory factory = entry.getT2(); - T element = factory.create(superAnnotationName); - if (element!=null) { - builder.add(element); - } - } - return builder.build(); - } - -} 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 caa878931..2f7729a86 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 @@ -13,13 +13,16 @@ package org.springframework.ide.vscode.boot.java.annotations; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.Map; import java.util.function.Consumer; import org.eclipse.jdt.core.dom.ITypeBinding; +import org.springframework.ide.vscode.boot.java.requestmapping.RequestMappingSymbolProvider; import org.springframework.ide.vscode.commons.util.Assert; import org.springframework.ide.vscode.commons.util.StringUtil; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import reactor.util.function.Tuple2; @@ -90,42 +93,35 @@ public class AnnotationHierarchyAwareLookup { * a symbol provider for Components should be asked to produce symbols for Component, Controller and RestController, * so should result in 3 separate calls to the symbols provider. */ - public Collection> get(ITypeBinding annotationType) { - ImmutableSet.Builder> associations = ImmutableSet.builder(); - findElements(annotationType, new HashSet<>(), associations::add); - return associations.build(); + public Collection get(ITypeBinding annotationType) { + ImmutableList.Builder found = ImmutableList.builder(); + findElements(annotationType, new LinkedHashSet<>(), found::add); + return found.build(); } - private void findElements(ITypeBinding typeBinding, HashSet seen, Consumer> requestor) { - //Note: the 'seen' hashset is unneceassary if meta annotations do not annotate eachother - //in such a way as to create a cycle. Intuitively, you might expect that inheritance graphs - //do not contain cycles, but since these annotations can be coming from anywhere on - //a random project's classpath, we don't really know this for sure, so we must play it safe - //and guard the lookup against infinite looping. + private void findElements(ITypeBinding typeBinding, HashSet seen, Consumer requestor) { String qname = typeBinding.getQualifiedName(); -// int debugIndent = debug_in("findElements "+StringUtil.simpleName(qname)); -// Consumer> requestor = (e) -> { -// debug(debugIndent, "<== "+StringUtil.simpleName(e.getT1())+" from "+StringUtil.simpleName(qname)); -// _requestor.accept(e); -// }; - if (seen.add(qname)) { Binding binding = bindings.get(qname); boolean isOverriding = false; if (binding!=null) { - requestor.accept(Tuples.of(qname, binding.value)); + requestor.accept(binding.value); isOverriding = binding.isOverriding; } if (!isOverriding) { for (ITypeBinding superAnnotation : AnnotationHierarchies.getDirectSuperAnnotations(typeBinding)) { - findElements(superAnnotation, seen, superResult -> { - requestor.accept(superResult); - requestor.accept(Tuples.of(qname, superResult.getT2())); - }); + findElements(superAnnotation, seen, requestor); } } } -// debug_out("findElements "+qname); + } + + public void put(String annotationName, T value) { + put(annotationName, true, value); + } + + public boolean containsKey(String fqName) { + return bindings.containsKey(fqName); } // private static int indent = 0; diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/annotations/SymbolProviderLookup.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/annotations/SymbolProviderLookup.java new file mode 100644 index 000000000..419fc8cae --- /dev/null +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/annotations/SymbolProviderLookup.java @@ -0,0 +1,51 @@ +///******************************************************************************* +// * 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.annotations; +// +//import java.util.Collection; +// +//import org.eclipse.jdt.core.dom.ITypeBinding; +//import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider; +// +//import com.google.common.collect.ImmutableList; +// +//import reactor.util.function.Tuple2; +// +///** +// * @author Kris De Volder +// */ +//public class SymbolProviderLookup { +// +// @FunctionalInterface +// public interface Factory { +// T create(String fqAnnotationType); +// } +// +// private AnnotationHierarchyAwareLookup providers = new AnnotationHierarchyAwareLookup<>(); +// +// public void put(String fqAnnotationType, SymbolProvider value) { +// providers.put(fqAnnotationType, true, value); +// } +// +// public Collection get(ITypeBinding typeBinding) { +// ImmutableList.Builder builder = ImmutableList.builder(); +// for (Tuple2> entry : factories.get(typeBinding)) { +// String superAnnotationName = entry.getT1(); +// Factory factory = entry.getT2(); +// T element = factory.create(superAnnotationName); +// if (element!=null) { +// builder.add(element); +// } +// } +// return builder.build(); +// } +// +//} 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 5128ae8df..b60159be4 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 @@ -89,7 +89,7 @@ public class BeansSymbolProvider implements SymbolProvider { // } @Override - public Collection getSymbols(Annotation node, ITypeBinding annotationType, TextDocument doc) { + public Collection getSymbols(Annotation node, ITypeBinding annotationType, Collection metaAnnotations, TextDocument doc) { boolean isFunction = isFunctionBean(node); ImmutableList.Builder symbols = ImmutableList.builder(); String beanType = getBeanType(node); 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 fc092a683..92ed764c1 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 @@ -11,6 +11,7 @@ package org.springframework.ide.vscode.boot.java.beans; import java.util.Collection; +import java.util.stream.Collectors; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.Annotation; @@ -33,23 +34,12 @@ import com.google.common.collect.ImmutableList; */ public class ComponentSymbolProvider implements SymbolProvider { - private String annotationName; - private String simpleAnnotationName; - - public ComponentSymbolProvider(String annotationName) { - this.annotationName = annotationName; - this.simpleAnnotationName = StringUtil.simpleName(annotationName); - } - @Override - public Collection getSymbols(Annotation node, ITypeBinding annotationType, TextDocument doc) { + public Collection getSymbols(Annotation node, ITypeBinding annotationType, Collection metaAnnotations, TextDocument doc) { try { - ImmutableList.Builder symbols = ImmutableList.builder(); - symbols.add(createSymbol(node, doc, false)); - if (isExactAnnotationType(annotationType)) { - symbols.add(createSymbol(node, doc, true)); - } - return symbols.build(); + return ImmutableList.of( + createSymbol(node, annotationType, metaAnnotations, doc) + ); } catch (Exception e) { Log.log(e); @@ -57,24 +47,21 @@ public class ComponentSymbolProvider implements SymbolProvider { return ImmutableList.of(); } - protected SymbolInformation createSymbol(Annotation node, TextDocument doc, boolean isExactAnnotationType) throws BadLocationException { - String annotationTypeName = isExactAnnotationType - ? simpleAnnotationName - : '+' + simpleAnnotationName; + protected SymbolInformation createSymbol(Annotation node, ITypeBinding annotationType, Collection metaAnnotations, TextDocument doc) throws BadLocationException { + String annotationTypeName = annotationType.getName(); + Collection metaAnnotationNames = metaAnnotations.stream() + .map(ITypeBinding::getName) + .collect(Collectors.toList()); String beanName = getBeanName(node); String beanType = getBeanType(node); SymbolInformation symbol = new SymbolInformation( - beanLabel("+", annotationTypeName, beanName, beanType), SymbolKind.Interface, + beanLabel("+", annotationTypeName, metaAnnotationNames, beanName, beanType), SymbolKind.Interface, new Location(doc.getUri(), doc.toRange(node.getStartPosition(), node.getLength()))); return symbol; } - protected boolean isExactAnnotationType(ITypeBinding actualAnnotationType) { - return actualAnnotationType.getQualifiedName().equals(annotationName); - } - - protected String beanLabel(String searchPrefix, String annotationTypeName, String beanName, String beanType) { + protected String beanLabel(String searchPrefix, String annotationTypeName, Collection metaAnnotationNames, String beanName, String beanType) { StringBuilder symbolLabel = new StringBuilder(); symbolLabel.append("@"); symbolLabel.append(searchPrefix); @@ -84,6 +71,18 @@ public class ComponentSymbolProvider implements SymbolProvider { symbolLabel.append('\''); symbolLabel.append(" (@"); symbolLabel.append(annotationTypeName); + if (!metaAnnotationNames.isEmpty()) { + symbolLabel.append(" <: "); + boolean first = true; + for (String ma : metaAnnotationNames) { + if (!first) { + symbolLabel.append(", "); + } + symbolLabel.append("@"); + symbolLabel.append(ma); + first = false; + } + } symbolLabel.append(") "); symbolLabel.append(beanType); return symbolLabel.toString(); @@ -113,9 +112,4 @@ public class ComponentSymbolProvider implements SymbolProvider { return null; } - @Override - public String toString() { - return "ComponentSymbolProvider("+simpleAnnotationName+")"; - } - } 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 21ea8b2c3..3c0ca62cf 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 @@ -23,6 +23,6 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument; */ public interface SymbolProvider { - Collection getSymbols(Annotation node, ITypeBinding typeBinding, TextDocument doc); + Collection getSymbols(Annotation node, ITypeBinding typeBinding, Collection metaAnnotations, 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 9ab78c7bd..5558363bc 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 @@ -41,7 +41,7 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument; public class RequestMappingSymbolProvider implements SymbolProvider { @Override - public Collection getSymbols(Annotation node, ITypeBinding annotationType, TextDocument doc) { + public Collection getSymbols(Annotation node, ITypeBinding annotationType, Collection metaAnnotations, TextDocument doc) { if (node.getParent() instanceof MethodDeclaration) { try { Location location = new Location(doc.getUri(), doc.toRange(node.getStartPosition(), node.getLength())); 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 3390c998b..06ab00fe1 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 @@ -48,7 +48,8 @@ import org.eclipse.lsp4j.SymbolInformation; import org.eclipse.lsp4j.SymbolKind; import org.eclipse.lsp4j.TextDocumentIdentifier; import org.springframework.ide.vscode.boot.java.BootJavaLanguageServer; -import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchyAwareFactoryManager; +import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies; +import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchyAwareLookup; import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider; import org.springframework.ide.vscode.commons.java.IClasspath; import org.springframework.ide.vscode.commons.java.IJavaProject; @@ -67,7 +68,7 @@ public class SpringIndexer { private BootJavaLanguageServer server; private JavaProjectFinder projectFinder; - private AnnotationHierarchyAwareFactoryManager symbolProviders; + private AnnotationHierarchyAwareLookup symbolProviders; private List symbols; private ConcurrentMap> symbolsByDoc; @@ -98,7 +99,7 @@ public class SpringIndexer { }; - public SpringIndexer(BootJavaLanguageServer server, JavaProjectFinder projectFinder, AnnotationHierarchyAwareFactoryManager specificProviders) { + public SpringIndexer(BootJavaLanguageServer server, JavaProjectFinder projectFinder, AnnotationHierarchyAwareLookup specificProviders) { this.server = server; this.projectFinder = projectFinder; this.symbolProviders = specificProviders; @@ -384,10 +385,11 @@ public class SpringIndexer { if (typeBinding != null) { Collection providers = symbolProviders.get(typeBinding); + Collection metaAnnotations = AnnotationHierarchies.getMetaAnnotations(typeBinding, symbolProviders::containsKey); if (!providers.isEmpty()) { TextDocument doc = getTempTextDocument(docURI, docRef, content); for (SymbolProvider provider : providers) { - Collection sbls = provider.getSymbols(node, typeBinding, doc); + Collection sbls = provider.getSymbols(node, typeBinding, metaAnnotations, doc); if (sbls != null) { sbls.forEach(symbol -> { symbols.add(symbol); 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 48a50628e..48ab22d89 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 @@ -10,47 +10,37 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.beans.test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - import java.io.File; -import java.util.Collection; -import java.util.HashMap; import java.util.Iterator; import java.util.List; -import java.util.Map; import org.eclipse.lsp4j.SymbolInformation; import org.junit.Before; import org.junit.Test; import org.springframework.ide.vscode.boot.java.Annotations; -import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchyAwareFactoryManager; +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.boot.java.utils.SpringIndexer; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; -import org.springframework.ide.vscode.commons.languageserver.multiroot.WorkspaceFolder; import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness; import org.springframework.ide.vscode.project.harness.ProjectsHarness; -import com.google.common.collect.ImmutableList; - /** * @author Martin Lippert */ public class SpringIndexerBeansTest { - private AnnotationHierarchyAwareFactoryManager symbolProviders; + private AnnotationHierarchyAwareLookup symbolProviders; private BootLanguageServerHarness harness; private JavaProjectFinder projectFinder; @Before public void setup() throws Exception { - symbolProviders = new AnnotationHierarchyAwareFactoryManager<>(); + symbolProviders = new AnnotationHierarchyAwareLookup<>(); symbolProviders.put(Annotations.BEAN, new BeansSymbolProvider()); - symbolProviders.putFactory(Annotations.COMPONENT, ComponentSymbolProvider::new); + symbolProviders.put(Annotations.COMPONENT, new ComponentSymbolProvider()); harness = BootLanguageServerHarness.builder().build(); projectFinder = harness.getProjectFinder(); @@ -65,9 +55,7 @@ public class SpringIndexerBeansTest { String uriPrefix = "file://" + directory.getAbsolutePath(); indexer.assertDocumentSymbols(uriPrefix + "/src/main/java/org/test/SimpleConfiguration.java", - symbol("@Configuration", "@+ 'simpleConfiguration' (@+Component) SimpleConfiguration"), - symbol("@Configuration", "@+ 'simpleConfiguration' (@+Configuration) SimpleConfiguration"), - symbol("@Configuration", "@+ 'simpleConfiguration' (@Configuration) SimpleConfiguration"), + symbol("@Configuration", "@+ 'simpleConfiguration' (@Configuration <: @Component) SimpleConfiguration"), symbol("@Bean", "@+ 'simpleBean' (@Bean) BeanClass") ); } @@ -80,9 +68,7 @@ public class SpringIndexerBeansTest { String uriPrefix = "file://" + directory.getAbsolutePath(); String docUri = uriPrefix + "/src/main/java/org/test/SpecialConfiguration.java"; indexer.assertDocumentSymbols(docUri, - symbol("@Configuration", "@+ 'specialConfiguration' (@+Component) SpecialConfiguration"), - symbol("@Configuration", "@+ 'specialConfiguration' (@+Configuration) SpecialConfiguration"), - symbol("@Configuration", "@+ 'specialConfiguration' (@Configuration) SpecialConfiguration"), + symbol("@Configuration", "@+ 'specialConfiguration' (@Configuration <: @Component) SpecialConfiguration"), // @Bean("implicitNamedBean") symbol("implicitNamedBean", "@+ 'implicitNamedBean' (@Bean) BeanClass"), @@ -111,10 +97,7 @@ public class SpringIndexerBeansTest { String uriPrefix = "file://" + directory.getAbsolutePath(); indexer.assertDocumentSymbols(uriPrefix + "/src/main/java/org/test/FunctionClass.java", - symbol("@Configuration", "@+ 'functionClass' (@+Component) FunctionClass"), - symbol("@Configuration", "@+ 'functionClass' (@+Configuration) FunctionClass"), - symbol("@Configuration", "@+ 'functionClass' (@Configuration) FunctionClass"), - + symbol("@Configuration", "@+ 'functionClass' (@Configuration <: @Component) FunctionClass"), symbol("@Bean", "@> 'uppercase' (@Bean) Function") ); } @@ -127,7 +110,6 @@ public class SpringIndexerBeansTest { String uriPrefix = "file://" + directory.getAbsolutePath(); indexer.assertDocumentSymbols(uriPrefix + "/src/main/java/org/test/SimpleComponent.java", - symbol("@Component", "@+ 'simpleComponent' (@+Component) SimpleComponent"), symbol("@Component", "@+ 'simpleComponent' (@Component) SimpleComponent") ); // List symbols = indexer.getSymbols(uriPrefix + "/src/main/java/org/test/SimpleComponent.java"); @@ -143,9 +125,7 @@ public class SpringIndexerBeansTest { String uriPrefix = "file://" + directory.getAbsolutePath(); String docUri = uriPrefix + "/src/main/java/org/test/SimpleController.java"; indexer.assertDocumentSymbols(docUri, - symbol("@Controller", "@+ 'simpleController' (@+Component) SimpleController"), - symbol("@Controller", "@+ 'simpleController' (@+Controller) SimpleController"), - symbol("@Controller", "@+ 'simpleController' (@Controller) SimpleController") + symbol("@Controller", "@+ 'simpleController' (@Controller <: @Component) SimpleController") ); } @@ -157,10 +137,7 @@ public class SpringIndexerBeansTest { String uriPrefix = "file://" + directory.getAbsolutePath(); String docUri = uriPrefix + "/src/main/java/org/test/SimpleRestController.java"; indexer.assertDocumentSymbols(docUri, - symbol("@RestController", "@+ 'simpleRestController' (@+Component) SimpleRestController"), - symbol("@RestController", "@+ 'simpleRestController' (@+Controller) SimpleRestController"), - symbol("@RestController", "@+ 'simpleRestController' (@+RestController) SimpleRestController"), - symbol("@RestController", "@+ 'simpleRestController' (@RestController) SimpleRestController") + symbol("@RestController", "@+ 'simpleRestController' (@RestController <: @Controller, @Component) SimpleRestController") ); } diff --git a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerHarness.java b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerHarness.java index 141e3afdd..ebab4114b 100644 --- a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerHarness.java +++ b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerHarness.java @@ -25,7 +25,7 @@ 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.annotations.AnnotationHierarchyAwareFactoryManager; +import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchyAwareLookup; 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; @@ -106,7 +106,7 @@ public class SpringIndexerHarness { private SpringIndexer indexer; - public SpringIndexerHarness(BootJavaLanguageServer server, JavaProjectFinder projectFinder, AnnotationHierarchyAwareFactoryManager symbolProviders) { + public SpringIndexerHarness(BootJavaLanguageServer server, JavaProjectFinder projectFinder, AnnotationHierarchyAwareLookup symbolProviders) { this.indexer = new SpringIndexer(server, projectFinder, symbolProviders); } diff --git a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerTest.java b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerTest.java index 98c4656b2..159c6b02b 100644 --- a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerTest.java +++ b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerTest.java @@ -65,11 +65,11 @@ public class SpringIndexerTest { List allSymbols = indexer().getAllSymbols(""); - assertEquals(10, allSymbols.size()); + assertEquals(6, allSymbols.size()); String uriPrefix = "file://" + directory.getAbsolutePath(); - assertTrue(containsSymbol(allSymbols, "@+ 'mainClass' (@SpringBootApplication) MainClass", uriPrefix + "/src/main/java/org/test/MainClass.java", 6, 0, 6, 22)); + assertTrue(containsSymbol(allSymbols, "@+ 'mainClass' (@SpringBootApplication <: @SpringBootConfiguration, @Configuration, @Component) MainClass", uriPrefix + "/src/main/java/org/test/MainClass.java", 6, 0, 6, 22)); assertTrue(containsSymbol(allSymbols, "@/embedded-foo-mapping -- (no method defined)", uriPrefix + "/src/main/java/org/test/MainClass.java", 17, 1, 17, 41)); assertTrue(containsSymbol(allSymbols, "@/foo-root-mapping/embedded-foo-mapping-with-root -- (no method defined)", uriPrefix + "/src/main/java/org/test/MainClass.java", 27, 1, 27, 51)); assertTrue(containsSymbol(allSymbols, "@/mapping1 -- (no method defined)", uriPrefix + "/src/main/java/org/test/SimpleMappingClass.java", 6, 1, 6, 28)); @@ -85,12 +85,8 @@ public class SpringIndexerTest { String uriPrefix = "file://" + directory.getAbsolutePath(); List symbols = indexer().getSymbols(uriPrefix + "/src/main/java/org/test/MainClass.java"); - assertEquals(7, symbols.size()); - assertTrue(containsSymbol(symbols, "@+ 'mainClass' (@SpringBootApplication) MainClass", uriPrefix + "/src/main/java/org/test/MainClass.java", 6, 0, 6, 22)); - assertTrue(containsSymbol(symbols, "@+ 'mainClass' (@+SpringBootApplication) MainClass", uriPrefix + "/src/main/java/org/test/MainClass.java", 6, 0, 6, 22)); - assertTrue(containsSymbol(symbols, "@+ 'mainClass' (@+SpringBootConfiguration) MainClass", uriPrefix + "/src/main/java/org/test/MainClass.java", 6, 0, 6, 22)); - assertTrue(containsSymbol(symbols, "@+ 'mainClass' (@+Component) MainClass", uriPrefix + "/src/main/java/org/test/MainClass.java", 6, 0, 6, 22)); - assertTrue(containsSymbol(symbols, "@+ 'mainClass' (@+Configuration) MainClass", uriPrefix + "/src/main/java/org/test/MainClass.java", 6, 0, 6, 22)); + assertEquals(3, symbols.size()); + assertTrue(containsSymbol(symbols, "@+ 'mainClass' (@SpringBootApplication <: @SpringBootConfiguration, @Configuration, @Component) MainClass", uriPrefix + "/src/main/java/org/test/MainClass.java", 6, 0, 6, 22)); assertTrue(containsSymbol(symbols, "@/embedded-foo-mapping -- (no method defined)", uriPrefix + "/src/main/java/org/test/MainClass.java", 17, 1, 17, 41)); assertTrue(containsSymbol(symbols, "@/foo-root-mapping/embedded-foo-mapping-with-root -- (no method defined)", uriPrefix + "/src/main/java/org/test/MainClass.java", 27, 1, 27, 51)); @@ -112,11 +108,11 @@ public class SpringIndexerTest { List allSymbols = indexer().getAllSymbols(""); - assertEquals(10, allSymbols.size()); + assertEquals(6, allSymbols.size()); String uriPrefix = "file://" + directory.getAbsolutePath() + "/test-annotation-indexing"; - assertTrue(containsSymbol(allSymbols, "@+ 'mainClass' (@SpringBootApplication) MainClass", uriPrefix + "/src/main/java/org/test/MainClass.java", 6, 0, 6, 22)); + assertTrue(containsSymbol(allSymbols, "@+ 'mainClass' (@SpringBootApplication <: @SpringBootConfiguration, @Configuration, @Component) MainClass", uriPrefix + "/src/main/java/org/test/MainClass.java", 6, 0, 6, 22)); assertTrue(containsSymbol(allSymbols, "@/embedded-foo-mapping -- (no method defined)", uriPrefix + "/src/main/java/org/test/MainClass.java", 17, 1, 17, 41)); assertTrue(containsSymbol(allSymbols, "@/foo-root-mapping/embedded-foo-mapping-with-root -- (no method defined)", uriPrefix + "/src/main/java/org/test/MainClass.java", 27, 1, 27, 51)); assertTrue(containsSymbol(allSymbols, "@/mapping1 -- (no method defined)", uriPrefix + "/src/main/java/org/test/SimpleMappingClass.java", 6, 1, 6, 28)); @@ -145,14 +141,11 @@ public class SpringIndexerTest { // check for updated index in all symbols List allSymbols = indexer().getAllSymbols(""); - assertEquals(10, allSymbols.size()); + assertEquals(6, allSymbols.size()); String uriPrefix = "file://" + directory.getAbsolutePath(); - assertTrue(containsSymbol(allSymbols, "@+ 'mainClass' (@+Component) MainClass", uriPrefix + "/src/main/java/org/test/MainClass.java", 6, 0, 6, 22)); - assertTrue(containsSymbol(allSymbols, "@+ 'mainClass' (@+SpringBootApplication) MainClass", uriPrefix + "/src/main/java/org/test/MainClass.java", 6, 0, 6, 22)); - assertTrue(containsSymbol(allSymbols, "@+ 'mainClass' (@SpringBootApplication) MainClass", uriPrefix + "/src/main/java/org/test/MainClass.java", 6, 0, 6, 22)); - assertTrue(containsSymbol(allSymbols, "@+ 'mainClass' (@+Configuration) MainClass", uriPrefix + "/src/main/java/org/test/MainClass.java", 6, 0, 6, 22)); + assertTrue(containsSymbol(allSymbols, "@+ 'mainClass' (@SpringBootApplication <: @SpringBootConfiguration, @Configuration, @Component) MainClass", uriPrefix + "/src/main/java/org/test/MainClass.java", 6, 0, 6, 22)); assertTrue(containsSymbol(allSymbols, "@/embedded-foo-mapping -- (no method defined)", uriPrefix + "/src/main/java/org/test/MainClass.java", 17, 1, 17, 41)); assertTrue(containsSymbol(allSymbols, "@/foo-root-mapping/embedded-foo-mapping-with-root -- (no method defined)", uriPrefix + "/src/main/java/org/test/MainClass.java", 27, 1, 27, 51)); assertTrue(containsSymbol(allSymbols, "@/mapping1-CHANGED -- (no method defined)", uriPrefix + "/src/main/java/org/test/SimpleMappingClass.java", 6, 1, 6, 36)); @@ -168,7 +161,7 @@ public class SpringIndexerTest { List allSymbols = indexer().getAllSymbols("mapp"); - assertEquals(7, allSymbols.size()); + assertEquals(6, allSymbols.size()); String uriPrefix = "file://" + directory.getAbsolutePath(); @@ -233,7 +226,7 @@ public class SpringIndexerTest { File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-parent/test-annotation-indexing/").toURI()); List allSymbols = indexer().getAllSymbols(""); - assertEquals(10, allSymbols.size()); + assertEquals(6, allSymbols.size()); File pomFile = directory.toPath().resolve(MavenCore.POM_XML).toFile(); @@ -244,7 +237,7 @@ public class SpringIndexerTest { allSymbols = indexer().getAllSymbols(""); assertFalse(indexer().isInitializing()); - assertEquals(10, allSymbols.size()); + assertEquals(6, allSymbols.size()); } }