Support subtyping in SpringIndexer
This commit is contained in:
@@ -17,6 +17,7 @@ import java.util.concurrent.CompletableFuture;
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
import org.eclipse.lsp4j.InitializeParams;
|
||||
import org.eclipse.lsp4j.InitializeResult;
|
||||
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchyAwareFactoryManager;
|
||||
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;
|
||||
@@ -304,9 +305,8 @@ public class BootJavaLanguageServer extends SimpleLanguageServer {
|
||||
}
|
||||
|
||||
protected SpringIndexer createAnnotationIndexer(SimpleLanguageServer server, JavaProjectFinder projectFinder) {
|
||||
HashMap<String, SymbolProvider> providers = new HashMap<>();
|
||||
providers.put(Annotations.SPRING_REQUEST_MAPPING,
|
||||
new RequestMappingSymbolProvider());
|
||||
AnnotationHierarchyAwareFactoryManager<SymbolProvider> providers = new AnnotationHierarchyAwareFactoryManager<>();
|
||||
providers.put(Annotations.SPRING_REQUEST_MAPPING, new RequestMappingSymbolProvider());
|
||||
providers.put(Annotations.SPRING_GET_MAPPING,
|
||||
new RequestMappingSymbolProvider());
|
||||
providers.put(Annotations.SPRING_POST_MAPPING,
|
||||
@@ -319,7 +319,7 @@ public class BootJavaLanguageServer extends SimpleLanguageServer {
|
||||
new RequestMappingSymbolProvider());
|
||||
|
||||
providers.put(Annotations.BEAN, new BeansSymbolProvider());
|
||||
providers.put(Annotations.COMPONENT, new ComponentSymbolProvider());
|
||||
providers.putFactory(Annotations.COMPONENT, ComponentSymbolProvider::new);
|
||||
|
||||
return new SpringIndexer(this, projectFinder, providers);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*******************************************************************************
|
||||
* 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 java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.core.dom.IAnnotationBinding;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
/**
|
||||
* Utility class for working with annotation and discovering / understanding their
|
||||
* 'inheritance' structure.
|
||||
* <p>
|
||||
* Provides methods to ask questions about inheritance between annotations.
|
||||
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class AnnotationHierarchies {
|
||||
|
||||
protected boolean ignoreAnnotation(String fqname) {
|
||||
return fqname.startsWith("java."); //mostly intended to capture java.lang.annotation.* types. But really it should be
|
||||
//safe to ignore any type defined by the JRE since it can't possibly be inheriting from a spring annotation.
|
||||
};
|
||||
|
||||
public Collection<ITypeBinding> getDirectSuperAnnotations(ITypeBinding typeBinding) {
|
||||
IAnnotationBinding[] annotations = typeBinding.getAnnotations();
|
||||
if (annotations!=null && annotations.length!=0) {
|
||||
ImmutableList.Builder<ITypeBinding> superAnnotations = ImmutableList.builder();
|
||||
for (IAnnotationBinding ab : annotations) {
|
||||
ITypeBinding sa = ab.getAnnotationType();
|
||||
if (sa!=null) {
|
||||
if (!ignoreAnnotation(sa.getQualifiedName())) {
|
||||
superAnnotations.add(sa);
|
||||
}
|
||||
}
|
||||
}
|
||||
return superAnnotations.build();
|
||||
}
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
public Set<String> getTransitiveSuperAnnotations(ITypeBinding typeBinding) {
|
||||
Set<String> seen = new HashSet<>();
|
||||
findTransitiveSupers(typeBinding, seen);
|
||||
return seen;
|
||||
}
|
||||
|
||||
private void findTransitiveSupers(ITypeBinding typeBinding, Set<String> seen) {
|
||||
String qname = typeBinding.getQualifiedName();
|
||||
if (seen.add(qname)) {
|
||||
for (ITypeBinding superBinding : getDirectSuperAnnotations(typeBinding)) {
|
||||
findTransitiveSupers(superBinding, seen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*******************************************************************************
|
||||
* 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<T> {
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Factory<T> {
|
||||
T create(String fqAnnotationType);
|
||||
}
|
||||
|
||||
private AnnotationHierarchyAwareLookup<Factory<T>> factories = new AnnotationHierarchyAwareLookup<Factory<T>>();
|
||||
|
||||
/**
|
||||
* 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<T> factory) {
|
||||
factories.put(fqAnnotationType, false, factory);
|
||||
}
|
||||
|
||||
public Collection<T> get(ITypeBinding typeBinding) {
|
||||
ImmutableList.Builder<T> builder = ImmutableList.builder();
|
||||
for (Tuple2<String, Factory<T>> entry : factories.get(typeBinding)) {
|
||||
String superAnnotationName = entry.getT1();
|
||||
Factory<T> factory = entry.getT2();
|
||||
T element = factory.create(superAnnotationName);
|
||||
if (element!=null) {
|
||||
builder.add(element);
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/*******************************************************************************
|
||||
* 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 java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.springframework.ide.vscode.commons.util.Assert;
|
||||
import org.springframework.ide.vscode.commons.util.StringUtil;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
import reactor.util.function.Tuple2;
|
||||
import reactor.util.function.Tuples;
|
||||
|
||||
/**
|
||||
* A Map-like utilyt that allows putting and getting values associated with
|
||||
* annotation types.
|
||||
* <p>
|
||||
* The lookup is 'hierarchy aware' which means that is able to associate values
|
||||
* with a given type and all its subtypes all at once.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class AnnotationHierarchyAwareLookup<T> {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static class Binding<T> {
|
||||
T value;
|
||||
boolean isOverriding;
|
||||
public Binding(T value, boolean isOverriding) {
|
||||
this.isOverriding = isOverriding;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
private AnnotationHierarchies annotationHierarchies = new AnnotationHierarchies();
|
||||
|
||||
/**
|
||||
* Associates fq anotation type name to a Binding.
|
||||
*/
|
||||
private Map<String, Binding<T>> bindings = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Associates a value with a given annotation type (and all its subtypes implicitly).
|
||||
*
|
||||
* @param fqName Fully qualified type name for the annotation.
|
||||
* @param overrideSuperTypes Determines whether the binding has 'override' behavior. Override behavior
|
||||
* means that this binding stops the search for additional bindings associated
|
||||
* with a super type. If override behavior is disabled the search will continue
|
||||
* so that values associated with supertypes will also be found and returned
|
||||
* in addition to the more specific binding.
|
||||
* @param value
|
||||
*/
|
||||
public void put(String fqName, boolean overrideSuperTypes, T value) {
|
||||
Assert.isLegal(bindings.get(fqName)==null, "Multiple bindings to the same fqName are not supported");
|
||||
bindings.put(fqName, new Binding<>(value, overrideSuperTypes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all associations applicable to a given annotationType. Note that a single 'put' binding for
|
||||
* a supertype can result in mutiple applicable associations for single annotation type because
|
||||
* a single put actually creates associations for a type and all its subtypes implicitly.
|
||||
* <p>
|
||||
* So, for example:
|
||||
* <code>
|
||||
* AnnotationHierarchyAwareLookup registry = new AnnotationHierarchyAwareLookup<String>();
|
||||
* registry.put("spring.annotation.Component", "ComponentProvider");
|
||||
* </code>
|
||||
* Now, assuming that RestController is a sub annotation of Controller which is a subtype of Component,
|
||||
* then if we call `get(...typeBinding of RestController...`) we will get back a collection of 3 elements:
|
||||
* ("spring.annotation.Component", "ComponentProvider"),
|
||||
* ("spring.annotation.Controller", "ComponentProvider")
|
||||
* ("spring.annotation.RestController", "ComponentProvider")
|
||||
* <p>
|
||||
* This reflects the fact that a binding for ComponentProvider also can function as a provider for Controller
|
||||
* and RestController; and that RestController in turn can be interpreted as a specialized Component or Controller.
|
||||
* Therefore we would expect in a situation where a concrete annotation of type RestController is found in the AST,
|
||||
* 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<Tuple2<String, T>> get(ITypeBinding annotationType) {
|
||||
ImmutableSet.Builder<Tuple2<String, T>> associations = ImmutableSet.builder();
|
||||
findElements(annotationType, new HashSet<>(), associations::add);
|
||||
return associations.build();
|
||||
}
|
||||
|
||||
private void findElements(ITypeBinding typeBinding, HashSet<String> seen, Consumer<Tuple2<String, T>> 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.
|
||||
String qname = typeBinding.getQualifiedName();
|
||||
// int debugIndent = debug_in("findElements "+StringUtil.simpleName(qname));
|
||||
// Consumer<Tuple2<String, T>> requestor = (e) -> {
|
||||
// debug(debugIndent, "<== "+StringUtil.simpleName(e.getT1())+" from "+StringUtil.simpleName(qname));
|
||||
// _requestor.accept(e);
|
||||
// };
|
||||
|
||||
if (seen.add(qname)) {
|
||||
Binding<T> binding = bindings.get(qname);
|
||||
boolean isOverriding = false;
|
||||
if (binding!=null) {
|
||||
requestor.accept(Tuples.of(qname, 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()));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// debug_out("findElements "+qname);
|
||||
}
|
||||
|
||||
// private static int indent = 0;
|
||||
//
|
||||
// private static void debug(int indent, String msg) {
|
||||
// for (int i = 0; i < indent; i++) {
|
||||
// System.out.print(" ");
|
||||
// }
|
||||
// System.out.println(msg);
|
||||
// }
|
||||
// private static int debug_in(String msg) {
|
||||
// for (int i = 0; i < indent; i++) {
|
||||
// System.out.print(" ");
|
||||
// }
|
||||
// System.out.println(">> "+msg);
|
||||
// return indent ++;
|
||||
// }
|
||||
// private static void debug_out(String msg) {
|
||||
// indent --;
|
||||
// for (int i = 0; i < indent; i++) {
|
||||
// System.out.print(" ");
|
||||
// }
|
||||
// System.out.println("<< "+msg);
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -89,7 +89,7 @@ public class BeansSymbolProvider implements SymbolProvider {
|
||||
// }
|
||||
|
||||
@Override
|
||||
public Collection<SymbolInformation> getSymbols(Annotation node, TextDocument doc) {
|
||||
public Collection<SymbolInformation> getSymbols(Annotation node, ITypeBinding annotationType, TextDocument doc) {
|
||||
boolean isFunction = isFunctionBean(node);
|
||||
ImmutableList.Builder<SymbolInformation> symbols = ImmutableList.builder();
|
||||
String beanType = getBeanType(node);
|
||||
|
||||
@@ -11,45 +11,82 @@
|
||||
package org.springframework.ide.vscode.boot.java.beans;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
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.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.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
import org.springframework.ide.vscode.commons.util.StringUtil;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
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<SymbolInformation> getSymbols(Annotation node, TextDocument doc) {
|
||||
public Collection<SymbolInformation> getSymbols(Annotation node, ITypeBinding annotationType, TextDocument doc) {
|
||||
try {
|
||||
StringBuilder symbolLabel = new StringBuilder();
|
||||
symbolLabel.append("@+ ");
|
||||
|
||||
String beanName = getBeanName(node);
|
||||
String beanType = getBeanType(node);
|
||||
|
||||
symbolLabel.append('\'');
|
||||
symbolLabel.append(beanName);
|
||||
symbolLabel.append('\'');
|
||||
symbolLabel.append(" (@Component) ");
|
||||
symbolLabel.append(beanType);
|
||||
|
||||
SymbolInformation symbol = new SymbolInformation(symbolLabel.toString(), SymbolKind.Interface,
|
||||
new Location(doc.getUri(), doc.toRange(node.getStartPosition(), node.getLength())));
|
||||
return Collections.singleton(symbol);
|
||||
ImmutableList.Builder<SymbolInformation> symbols = ImmutableList.builder();
|
||||
symbols.add(createSymbol(node, doc, false));
|
||||
if (isExactAnnotationType(annotationType)) {
|
||||
symbols.add(createSymbol(node, doc, true));
|
||||
}
|
||||
return symbols.build();
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.log(e);
|
||||
}
|
||||
return null;
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
protected SymbolInformation createSymbol(Annotation node, TextDocument doc, boolean isExactAnnotationType) throws BadLocationException {
|
||||
String annotationTypeName = isExactAnnotationType
|
||||
? simpleAnnotationName
|
||||
: '+' + simpleAnnotationName;
|
||||
String beanName = getBeanName(node);
|
||||
String beanType = getBeanType(node);
|
||||
|
||||
SymbolInformation symbol = new SymbolInformation(
|
||||
beanLabel("+", annotationTypeName, 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) {
|
||||
StringBuilder symbolLabel = new StringBuilder();
|
||||
symbolLabel.append("@");
|
||||
symbolLabel.append(searchPrefix);
|
||||
symbolLabel.append(' ');
|
||||
symbolLabel.append('\'');
|
||||
symbolLabel.append(beanName);
|
||||
symbolLabel.append('\'');
|
||||
symbolLabel.append(" (@");
|
||||
symbolLabel.append(annotationTypeName);
|
||||
symbolLabel.append(") ");
|
||||
symbolLabel.append(beanType);
|
||||
return symbolLabel.toString();
|
||||
}
|
||||
|
||||
private String getBeanName(Annotation node) {
|
||||
@@ -76,4 +113,9 @@ public class ComponentSymbolProvider implements SymbolProvider {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ComponentSymbolProvider("+simpleAnnotationName+")";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -73,7 +73,6 @@ public class BootJavaCompletionEngine implements ICompletionEngine {
|
||||
ASTNode node = NodeFinder.perform(cu, offset, 0);
|
||||
|
||||
if (node != null) {
|
||||
System.out.println("AST node found: " + node.getClass().getName());
|
||||
Collection<ICompletionProposal> completions = new ArrayList<ICompletionProposal>();
|
||||
completions.addAll(collectCompletionsForAnnotations(node, offset, document));
|
||||
completions.addAll(snippets.getCompletions(document, offset, node, cu));
|
||||
|
||||
@@ -13,14 +13,16 @@ package org.springframework.ide.vscode.boot.java.handlers;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.lsp4j.SymbolInformation;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public interface SymbolProvider {
|
||||
|
||||
Collection<SymbolInformation> getSymbols(Annotation node, TextDocument doc);
|
||||
Collection<SymbolInformation> getSymbols(Annotation node, ITypeBinding typeBinding, TextDocument doc);
|
||||
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
public class RequestMappingSymbolProvider implements SymbolProvider {
|
||||
|
||||
@Override
|
||||
public Collection<SymbolInformation> getSymbols(Annotation node, TextDocument doc) {
|
||||
public Collection<SymbolInformation> getSymbols(Annotation node, ITypeBinding annotationType, TextDocument doc) {
|
||||
if (node.getParent() instanceof MethodDeclaration) {
|
||||
try {
|
||||
Location location = new Location(doc.getUri(), doc.toRange(node.getStartPosition(), node.getLength()));
|
||||
|
||||
@@ -48,6 +48,7 @@ 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.handlers.SymbolProvider;
|
||||
import org.springframework.ide.vscode.commons.java.IClasspath;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
@@ -64,7 +65,7 @@ public class SpringIndexer {
|
||||
|
||||
private BootJavaLanguageServer server;
|
||||
private JavaProjectFinder projectFinder;
|
||||
private Map<String, SymbolProvider> symbolProviders;
|
||||
private AnnotationHierarchyAwareFactoryManager<SymbolProvider> symbolProviders;
|
||||
|
||||
private List<SymbolInformation> symbols;
|
||||
private ConcurrentMap<String, List<SymbolInformation>> symbolsByDoc;
|
||||
@@ -95,7 +96,7 @@ public class SpringIndexer {
|
||||
|
||||
};
|
||||
|
||||
public SpringIndexer(BootJavaLanguageServer server, JavaProjectFinder projectFinder, Map<String, SymbolProvider> specificProviders) {
|
||||
public SpringIndexer(BootJavaLanguageServer server, JavaProjectFinder projectFinder, AnnotationHierarchyAwareFactoryManager<SymbolProvider> specificProviders) {
|
||||
this.server = server;
|
||||
this.projectFinder = projectFinder;
|
||||
this.symbolProviders = specificProviders;
|
||||
@@ -392,20 +393,19 @@ public class SpringIndexer {
|
||||
ITypeBinding typeBinding = node.resolveTypeBinding();
|
||||
|
||||
if (typeBinding != null) {
|
||||
String qualifiedTypeName = typeBinding.getQualifiedName();
|
||||
|
||||
SymbolProvider provider = symbolProviders.get(qualifiedTypeName);
|
||||
if (provider != null) {
|
||||
Collection<SymbolProvider> providers = symbolProviders.get(typeBinding);
|
||||
if (!providers.isEmpty()) {
|
||||
TextDocument doc = getTempTextDocument(docURI, docRef, content);
|
||||
Collection<SymbolInformation> sbls = provider.getSymbols(node, doc);
|
||||
if (sbls != null) {
|
||||
sbls.forEach(symbol -> {
|
||||
symbols.add(symbol);
|
||||
symbolsByDoc.computeIfAbsent(docURI, s -> new ArrayList<SymbolInformation>()).add(symbol);
|
||||
});
|
||||
for (SymbolProvider provider : providers) {
|
||||
Collection<SymbolInformation> sbls = provider.getSymbols(node, typeBinding, doc);
|
||||
if (sbls != null) {
|
||||
sbls.forEach(symbol -> {
|
||||
symbols.add(symbol);
|
||||
symbolsByDoc.computeIfAbsent(docURI, s -> new ArrayList<SymbolInformation>()).add(symbol);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
SymbolInformation symbol = provideDefaultSymbol(node, docURI, docRef, content);
|
||||
if (symbol != null) {
|
||||
symbols.add(symbol);
|
||||
|
||||
@@ -62,8 +62,6 @@ public class SpringResource {
|
||||
Path path = Paths.get(pathStr);
|
||||
IClasspath classpath = project.getClasspath();
|
||||
Path outputFolder = classpath.getOutputFolder();
|
||||
System.out.println("outf = "+outputFolder);
|
||||
System.out.println("path = "+pathStr);
|
||||
if (path.startsWith(outputFolder)) {
|
||||
return outputFolder.relativize(path).toString();
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ 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.beans.BeansSymbolProvider;
|
||||
import org.springframework.ide.vscode.boot.java.beans.ComponentSymbolProvider;
|
||||
import org.springframework.ide.vscode.boot.java.beans.test.SpringIndexerHarness.TestSymbolInfo;
|
||||
@@ -37,15 +38,15 @@ import org.springframework.ide.vscode.project.harness.ProjectsHarness;
|
||||
*/
|
||||
public class SpringIndexerBeansTest {
|
||||
|
||||
private Map<String, SymbolProvider> symbolProviders;
|
||||
private AnnotationHierarchyAwareFactoryManager<SymbolProvider> symbolProviders;
|
||||
private BootLanguageServerHarness harness;
|
||||
private JavaProjectFinder projectFinder;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
symbolProviders = new HashMap<>();
|
||||
symbolProviders = new AnnotationHierarchyAwareFactoryManager<>();
|
||||
symbolProviders.put(Annotations.BEAN, new BeansSymbolProvider());
|
||||
symbolProviders.put(Annotations.COMPONENT, new ComponentSymbolProvider());
|
||||
symbolProviders.putFactory(Annotations.COMPONENT, ComponentSymbolProvider::new);
|
||||
|
||||
harness = BootLanguageServerHarness.builder().build();
|
||||
projectFinder = harness.getProjectFinder();
|
||||
@@ -60,13 +61,14 @@ public class SpringIndexerBeansTest {
|
||||
|
||||
String uriPrefix = "file://" + directory.getAbsolutePath();
|
||||
indexer.assertDocumentSymbols(uriPrefix + "/src/main/java/org/test/SimpleConfiguration.java",
|
||||
symbol("@Configuration", "@Configuration"),
|
||||
symbol("@Configuration", "@+ 'simpleConfiguration' (@+Component) SimpleConfiguration"),
|
||||
symbol("@Configuration", "@+ 'simpleConfiguration' (@+Configuration) SimpleConfiguration"),
|
||||
symbol("@Configuration", "@+ 'simpleConfiguration' (@Configuration) SimpleConfiguration"),
|
||||
symbol("@Bean", "@+ 'simpleBean' (@Bean) BeanClass")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testScanSpecialConfigurationClass() throws Exception {
|
||||
@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());
|
||||
@@ -74,7 +76,9 @@ public class SpringIndexerBeansTest {
|
||||
String uriPrefix = "file://" + directory.getAbsolutePath();
|
||||
String docUri = uriPrefix + "/src/main/java/org/test/SpecialConfiguration.java";
|
||||
indexer.assertDocumentSymbols(docUri,
|
||||
symbol("@Configuration", "@Configuration"),
|
||||
symbol("@Configuration", "@+ 'specialConfiguration' (@+Component) SpecialConfiguration"),
|
||||
symbol("@Configuration", "@+ 'specialConfiguration' (@+Configuration) SpecialConfiguration"),
|
||||
symbol("@Configuration", "@+ 'specialConfiguration' (@Configuration) SpecialConfiguration"),
|
||||
|
||||
// @Bean("implicitNamedBean")
|
||||
symbol("implicitNamedBean", "@+ 'implicitNamedBean' (@Bean) BeanClass"),
|
||||
@@ -97,28 +101,66 @@ public class SpringIndexerBeansTest {
|
||||
|
||||
@Test
|
||||
public void testScanSimpleFunctionBean() 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/FunctionClass.java");
|
||||
assertEquals(2, symbols.size());
|
||||
assertTrue(containsSymbol(symbols, "@> 'uppercase' (@Bean) Function<String,String>", uriPrefix + "/src/main/java/org/test/FunctionClass.java", 10, 1, 10, 6));
|
||||
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("@Bean", "@> 'uppercase' (@Bean) Function<String,String>")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testScanSimpleComponentClass() 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/SimpleComponent.java");
|
||||
assertEquals(1, symbols.size());
|
||||
assertTrue(containsSymbol(symbols, "@+ 'simpleComponent' (@Component) SimpleComponent", uriPrefix + "/src/main/java/org/test/SimpleComponent.java", 4, 0, 4, 10));
|
||||
indexer.assertDocumentSymbols(uriPrefix + "/src/main/java/org/test/SimpleComponent.java",
|
||||
symbol("@Component", "@+ 'simpleComponent' (@+Component) SimpleComponent"),
|
||||
symbol("@Component", "@+ 'simpleComponent' (@Component) SimpleComponent")
|
||||
);
|
||||
// List<? extends SymbolInformation> symbols = indexer.getSymbols(uriPrefix + "/src/main/java/org/test/SimpleComponent.java");
|
||||
// assertEquals(1, symbols.size());
|
||||
// assertTrue(containsSymbol(symbols, "@+ 'simpleComponent' (@Component) SimpleComponent", uriPrefix + "/src/main/java/org/test/SimpleComponent.java", 4, 0, 4, 10));
|
||||
}
|
||||
|
||||
@Test public void testScanSimpleControllerClass() 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/SimpleController.java";
|
||||
indexer.assertDocumentSymbols(docUri,
|
||||
symbol("@Controller", "@+ 'simpleController' (@+Component) SimpleController"),
|
||||
symbol("@Controller", "@+ 'simpleController' (@+Controller) SimpleController"),
|
||||
symbol("@Controller", "@+ 'simpleController' (@Controller) SimpleController")
|
||||
);
|
||||
}
|
||||
|
||||
@Test public void testScanRestControllerClass() 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/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")
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////
|
||||
// harness code
|
||||
|
||||
|
||||
@@ -25,6 +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.handlers.SymbolProvider;
|
||||
import org.springframework.ide.vscode.boot.java.utils.SpringIndexer;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
@@ -104,7 +105,7 @@ public class SpringIndexerHarness {
|
||||
|
||||
private SpringIndexer indexer;
|
||||
|
||||
public SpringIndexerHarness(BootJavaLanguageServer server, JavaProjectFinder projectFinder, Map<String, SymbolProvider> symbolProviders) {
|
||||
public SpringIndexerHarness(BootJavaLanguageServer server, JavaProjectFinder projectFinder, AnnotationHierarchyAwareFactoryManager<SymbolProvider> symbolProviders) {
|
||||
this.indexer = new SpringIndexer(server, projectFinder, symbolProviders);
|
||||
}
|
||||
|
||||
|
||||
@@ -65,11 +65,11 @@ public class SpringIndexerTest {
|
||||
|
||||
List<? extends SymbolInformation> allSymbols = indexer().getAllSymbols("");
|
||||
|
||||
assertEquals(6, allSymbols.size());
|
||||
assertEquals(10, allSymbols.size());
|
||||
|
||||
String uriPrefix = "file://" + directory.getAbsolutePath();
|
||||
|
||||
assertTrue(containsSymbol(allSymbols, "@SpringBootApplication", 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, "@/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,8 +85,12 @@ public class SpringIndexerTest {
|
||||
|
||||
String uriPrefix = "file://" + directory.getAbsolutePath();
|
||||
List<? extends SymbolInformation> symbols = indexer().getSymbols(uriPrefix + "/src/main/java/org/test/MainClass.java");
|
||||
assertEquals(3, symbols.size());
|
||||
assertTrue(containsSymbol(symbols, "@SpringBootApplication", uriPrefix + "/src/main/java/org/test/MainClass.java", 6, 0, 6, 22));
|
||||
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));
|
||||
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));
|
||||
|
||||
@@ -108,11 +112,11 @@ public class SpringIndexerTest {
|
||||
|
||||
List<? extends SymbolInformation> allSymbols = indexer().getAllSymbols("");
|
||||
|
||||
assertEquals(6, allSymbols.size());
|
||||
assertEquals(10, allSymbols.size());
|
||||
|
||||
String uriPrefix = "file://" + directory.getAbsolutePath() + "/test-annotation-indexing";
|
||||
|
||||
assertTrue(containsSymbol(allSymbols, "@SpringBootApplication", 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, "@/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));
|
||||
@@ -141,11 +145,14 @@ public class SpringIndexerTest {
|
||||
|
||||
// check for updated index in all symbols
|
||||
List<? extends SymbolInformation> allSymbols = indexer().getAllSymbols("");
|
||||
assertEquals(6, allSymbols.size());
|
||||
assertEquals(10, allSymbols.size());
|
||||
|
||||
String uriPrefix = "file://" + directory.getAbsolutePath();
|
||||
|
||||
assertTrue(containsSymbol(allSymbols, "@SpringBootApplication", uriPrefix + "/src/main/java/org/test/MainClass.java", 6, 0, 6, 22));
|
||||
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, "@/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));
|
||||
@@ -161,7 +168,7 @@ public class SpringIndexerTest {
|
||||
|
||||
List<? extends SymbolInformation> allSymbols = indexer().getAllSymbols("mapp");
|
||||
|
||||
assertEquals(5, allSymbols.size());
|
||||
assertEquals(7, allSymbols.size());
|
||||
|
||||
String uriPrefix = "file://" + directory.getAbsolutePath();
|
||||
|
||||
@@ -226,7 +233,7 @@ public class SpringIndexerTest {
|
||||
File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-parent/test-annotation-indexing/").toURI());
|
||||
|
||||
List<? extends SymbolInformation> allSymbols = indexer().getAllSymbols("");
|
||||
assertEquals(6, allSymbols.size());
|
||||
assertEquals(10, allSymbols.size());
|
||||
|
||||
File pomFile = directory.toPath().resolve(MavenCore.POM_XML).toFile();
|
||||
|
||||
@@ -237,7 +244,7 @@ public class SpringIndexerTest {
|
||||
|
||||
allSymbols = indexer().getAllSymbols("");
|
||||
assertFalse(indexer().isInitializing());
|
||||
assertEquals(6, allSymbols.size());
|
||||
assertEquals(10, allSymbols.size());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.test;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
@Controller
|
||||
public class SimpleController {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.test;
|
||||
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
public class SimpleRestController {
|
||||
}
|
||||
Reference in New Issue
Block a user