From 79c71128a1dbb5a025b54634296b825ff662c402 Mon Sep 17 00:00:00 2001 From: Martin Lippert Date: Tue, 27 Feb 2018 17:13:34 +0100 Subject: [PATCH] added option to generate additional information while parsing symbols and do that for handler methods in webflux definitions --- .../boot/java/beans/BeansSymbolProvider.java | 21 ++--- .../java/beans/ComponentSymbolProvider.java | 11 +-- .../data/DataRepositorySymbolProvider.java | 18 ++--- .../handlers/EnhancedSymbolInformation.java | 36 +++++++++ .../boot/java/handlers/SymbolProvider.java | 7 +- .../RequestMappingSymbolProvider.java | 9 ++- .../WebfluxHandlerInformation.java | 40 ++++++++++ .../WebfluxRouterSymbolProvider.java | 43 +++++++++-- .../vscode/boot/java/utils/SpringIndexer.java | 76 ++++++++++++++++--- .../WebFluxMappingSymbolProviderTest.java | 60 +++++++++++++++ 10 files changed, 271 insertions(+), 50 deletions(-) create mode 100644 headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/EnhancedSymbolInformation.java create mode 100644 headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerInformation.java diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeansSymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeansSymbolProvider.java index 2937171a2..bf33c1ce8 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeansSymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeansSymbolProvider.java @@ -23,6 +23,7 @@ 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.EnhancedSymbolInformation; import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider; import org.springframework.ide.vscode.boot.java.utils.ASTUtils; import org.springframework.ide.vscode.boot.java.utils.FunctionUtils; @@ -46,17 +47,19 @@ public class BeansSymbolProvider implements SymbolProvider { private static final String[] NAME_ATTRIBUTES = {"value", "name"}; @Override - public Collection getSymbols(Annotation node, ITypeBinding annotationType, Collection metaAnnotations, TextDocument doc) { + public Collection getSymbols(Annotation node, ITypeBinding annotationType, Collection metaAnnotations, TextDocument doc) { boolean isFunction = isFunctionBean(node); - ImmutableList.Builder symbols = ImmutableList.builder(); + ImmutableList.Builder symbols = ImmutableList.builder(); String beanType = getBeanType(node); for (Tuple2 nameAndRegion : getBeanNames(node, doc)) { try { - symbols.add(new SymbolInformation( - beanLabel(isFunction, nameAndRegion.getT1(), beanType, "@Bean"), - SymbolKind.Interface, - new Location(doc.getUri(), doc.toRange(nameAndRegion.getT2())) + symbols.add(new EnhancedSymbolInformation( + new SymbolInformation( + beanLabel(isFunction, nameAndRegion.getT1(), beanType, "@Bean"), + SymbolKind.Interface, + new Location(doc.getUri(), doc.toRange(nameAndRegion.getT2()))), + null )); } catch (BadLocationException e) { Log.log(e); @@ -66,7 +69,7 @@ public class BeansSymbolProvider implements SymbolProvider { } @Override - public Collection getSymbols(TypeDeclaration typeDeclaration, TextDocument doc) { + public Collection getSymbols(TypeDeclaration typeDeclaration, TextDocument doc) { // this checks function beans that are defined as implementations of Function interfaces Tuple3 functionBean = FunctionUtils.getFunctionBean(typeDeclaration, doc); if (functionBean != null) { @@ -75,7 +78,7 @@ public class BeansSymbolProvider implements SymbolProvider { beanLabel(true, functionBean.getT1(), functionBean.getT2(), null), SymbolKind.Interface, new Location(doc.getUri(), doc.toRange(functionBean.getT3()))); - return ImmutableList.of(symbol); + return ImmutableList.of(new EnhancedSymbolInformation(symbol, null)); } catch (BadLocationException e) { Log.log(e); } @@ -166,7 +169,7 @@ public class BeansSymbolProvider implements SymbolProvider { } @Override - public Collection getSymbols(MethodDeclaration methodDeclaration, TextDocument doc) { + public Collection getSymbols(MethodDeclaration methodDeclaration, TextDocument doc) { return null; } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentSymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentSymbolProvider.java index 9f2b6191e..943e941e0 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentSymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentSymbolProvider.java @@ -21,6 +21,7 @@ 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.EnhancedSymbolInformation; 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; @@ -35,7 +36,7 @@ import com.google.common.collect.ImmutableList; public class ComponentSymbolProvider implements SymbolProvider { @Override - public Collection getSymbols(Annotation node, ITypeBinding annotationType, Collection metaAnnotations, TextDocument doc) { + public Collection getSymbols(Annotation node, ITypeBinding annotationType, Collection metaAnnotations, TextDocument doc) { try { return ImmutableList.of( createSymbol(node, annotationType, metaAnnotations, doc) @@ -47,7 +48,7 @@ public class ComponentSymbolProvider implements SymbolProvider { return ImmutableList.of(); } - protected SymbolInformation createSymbol(Annotation node, ITypeBinding annotationType, Collection metaAnnotations, TextDocument doc) throws BadLocationException { + protected EnhancedSymbolInformation createSymbol(Annotation node, ITypeBinding annotationType, Collection metaAnnotations, TextDocument doc) throws BadLocationException { String annotationTypeName = annotationType.getName(); Collection metaAnnotationNames = metaAnnotations.stream() .map(ITypeBinding::getName) @@ -58,7 +59,7 @@ public class ComponentSymbolProvider implements SymbolProvider { SymbolInformation symbol = new SymbolInformation( beanLabel("+", annotationTypeName, metaAnnotationNames, beanName, beanType), SymbolKind.Interface, new Location(doc.getUri(), doc.toRange(node.getStartPosition(), node.getLength()))); - return symbol; + return new EnhancedSymbolInformation(symbol, null); } protected String beanLabel(String searchPrefix, String annotationTypeName, Collection metaAnnotationNames, String beanName, String beanType) { @@ -113,12 +114,12 @@ public class ComponentSymbolProvider implements SymbolProvider { } @Override - public Collection getSymbols(TypeDeclaration typeDeclaration, TextDocument doc) { + public Collection getSymbols(TypeDeclaration typeDeclaration, TextDocument doc) { return null; } @Override - public Collection getSymbols(MethodDeclaration methodDeclaration, TextDocument doc) { + public Collection getSymbols(MethodDeclaration methodDeclaration, TextDocument doc) { return null; } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositorySymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositorySymbolProvider.java index 3f895e1d3..e4a5d6bc3 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositorySymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositorySymbolProvider.java @@ -19,6 +19,7 @@ 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.EnhancedSymbolInformation; import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider; import org.springframework.ide.vscode.boot.java.utils.ASTUtils; import org.springframework.ide.vscode.commons.util.BadLocationException; @@ -38,14 +39,8 @@ public class DataRepositorySymbolProvider implements SymbolProvider { private static final String REPOSITORY_TYPE = "org.springframework.data.repository.Repository"; - @Override - public Collection getSymbols(Annotation node, ITypeBinding annotationType, Collection metaAnnotations, TextDocument doc) { - return null; - } - - @Override - public Collection getSymbols(TypeDeclaration typeDeclaration, TextDocument doc) { + public Collection getSymbols(TypeDeclaration typeDeclaration, TextDocument doc) { // this checks spring data repository beans that are defined as extensions of the repository interface Tuple4 repositoryBean = getRepositoryBean(typeDeclaration, doc); if (repositoryBean != null) { @@ -54,7 +49,7 @@ public class DataRepositorySymbolProvider implements SymbolProvider { beanLabel(true, repositoryBean.getT1(), repositoryBean.getT2(), repositoryBean.getT3()), SymbolKind.Interface, new Location(doc.getUri(), doc.toRange(repositoryBean.getT4()))); - return ImmutableList.of(symbol); + return ImmutableList.of(new EnhancedSymbolInformation(symbol, null)); } catch (BadLocationException e) { Log.log(e); } @@ -142,7 +137,12 @@ public class DataRepositorySymbolProvider implements SymbolProvider { } @Override - public Collection getSymbols(MethodDeclaration methodDeclaration, TextDocument doc) { + public Collection getSymbols(Annotation node, ITypeBinding annotationType, Collection metaAnnotations, TextDocument doc) { + return null; + } + + @Override + public Collection getSymbols(MethodDeclaration methodDeclaration, TextDocument doc) { return null; } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/EnhancedSymbolInformation.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/EnhancedSymbolInformation.java new file mode 100644 index 000000000..7a20e0f57 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/EnhancedSymbolInformation.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 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.handlers; + +import org.eclipse.lsp4j.SymbolInformation; + +/** + * @author Martin Lippert + */ +public class EnhancedSymbolInformation { + + private final SymbolInformation symbol; + private final Object additionalInformation; + + public EnhancedSymbolInformation(SymbolInformation symbol, Object additionalInformation) { + this.symbol = symbol; + this.additionalInformation = additionalInformation; + } + + public SymbolInformation getSymbol() { + return symbol; + } + + public Object getAdditionalInformation() { + return additionalInformation; + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/SymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/SymbolProvider.java index 2f8fc8b7f..434e7e6f3 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/SymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/SymbolProvider.java @@ -16,7 +16,6 @@ import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.TypeDeclaration; -import org.eclipse.lsp4j.SymbolInformation; import org.springframework.ide.vscode.commons.util.text.TextDocument; /** @@ -25,8 +24,8 @@ 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); - Collection getSymbols(MethodDeclaration methodDeclaration, TextDocument doc); + Collection getSymbols(Annotation node, ITypeBinding typeBinding, Collection metaAnnotations, TextDocument doc); + Collection getSymbols(TypeDeclaration typeDeclaration, TextDocument doc); + Collection getSymbols(MethodDeclaration methodDeclaration, TextDocument doc); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingSymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingSymbolProvider.java index 6e0d0ee58..a2c34fb5a 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingSymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingSymbolProvider.java @@ -31,6 +31,7 @@ import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.SymbolInformation; import org.eclipse.lsp4j.SymbolKind; import org.springframework.ide.vscode.boot.java.Annotations; +import org.springframework.ide.vscode.boot.java.handlers.EnhancedSymbolInformation; import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider; import org.springframework.ide.vscode.boot.java.utils.ASTUtils; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -41,7 +42,7 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument; public class RequestMappingSymbolProvider implements SymbolProvider { @Override - public Collection getSymbols(Annotation node, ITypeBinding annotationType, Collection metaAnnotations, 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())); @@ -62,7 +63,7 @@ public class RequestMappingSymbolProvider implements SymbolProvider { return resultPath.startsWith("/") ? resultPath : "/" + resultPath; })) .map(p -> "@" + p + (methodStr.isEmpty() ? "" : " -- " + methodStr)) - .map(symbolLabel -> new SymbolInformation(symbolLabel, SymbolKind.Interface, location)) + .map(symbolLabel -> new EnhancedSymbolInformation(new SymbolInformation(symbolLabel, SymbolKind.Interface, location), null)) .collect(Collectors.toList()); } catch (Exception e) { e.printStackTrace(); @@ -177,12 +178,12 @@ public class RequestMappingSymbolProvider implements SymbolProvider { } @Override - public Collection getSymbols(TypeDeclaration typeDeclaration, TextDocument doc) { + public Collection getSymbols(TypeDeclaration typeDeclaration, TextDocument doc) { return null; } @Override - public Collection getSymbols(MethodDeclaration methodDeclaration, TextDocument doc) { + public Collection getSymbols(MethodDeclaration methodDeclaration, TextDocument doc) { return null; } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerInformation.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerInformation.java new file mode 100644 index 000000000..519505aa0 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerInformation.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 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.requestmapping; + +/** + * @author Martin Lippert + */ +public class WebfluxHandlerInformation { + + private final String symbol; + private String destinationClass; + private String methodKey; + + public WebfluxHandlerInformation(String symbol, String destinationClass, String methodKey) { + this.symbol = symbol; + this.destinationClass = destinationClass; + this.methodKey = methodKey; + } + + public String getSymbol() { + return symbol; + } + + public String getDestinationClass() { + return destinationClass; + } + + public String getMethodKey() { + return methodKey; + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxRouterSymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxRouterSymbolProvider.java index db3af6d1f..ad17b549c 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxRouterSymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxRouterSymbolProvider.java @@ -18,15 +18,18 @@ import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.Block; +import org.eclipse.jdt.core.dom.ExpressionMethodReference; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.MethodInvocation; +import org.eclipse.jdt.core.dom.MethodReference; 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; +import org.springframework.ide.vscode.boot.java.handlers.EnhancedSymbolInformation; import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider; import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -37,18 +40,18 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument; public class WebfluxRouterSymbolProvider implements SymbolProvider { @Override - public Collection getSymbols(Annotation node, ITypeBinding typeBinding, + public Collection getSymbols(Annotation node, ITypeBinding typeBinding, Collection metaAnnotations, TextDocument doc) { return null; } @Override - public Collection getSymbols(TypeDeclaration typeDeclaration, TextDocument doc) { + public Collection getSymbols(TypeDeclaration typeDeclaration, TextDocument doc) { return null; } @Override - public Collection getSymbols(MethodDeclaration methodDeclaration, TextDocument doc) { + public Collection getSymbols(MethodDeclaration methodDeclaration, TextDocument doc) { Type returnType = methodDeclaration.getReturnType2(); if (returnType != null) { ITypeBinding resolvedBinding = returnType.resolveBinding(); @@ -61,9 +64,9 @@ public class WebfluxRouterSymbolProvider implements SymbolProvider { return null; } - private Collection getSymbolsForRouterFunction(MethodDeclaration methodDeclaration, + private Collection getSymbolsForRouterFunction(MethodDeclaration methodDeclaration, TextDocument doc) { - List result = new ArrayList<>(); + List result = new ArrayList<>(); Block body = methodDeclaration.getBody(); body.accept(new ASTVisitor() { @@ -84,7 +87,7 @@ public class WebfluxRouterSymbolProvider implements SymbolProvider { return result; } - protected void extractMappingSymbol(MethodInvocation node, TextDocument doc, List result) { + protected void extractMappingSymbol(MethodInvocation node, TextDocument doc, List result) { String foundPath = extractPathFromRouterFunction(node); String path = extractPath(node, foundPath); String httpMethod = extractMethod(node); @@ -96,7 +99,10 @@ public class WebfluxRouterSymbolProvider implements SymbolProvider { try { Location location = new Location(doc.getUri(), doc.toRange(methodNameStart, node.getLength() - (methodNameStart - invocationStart))); String label = "@" + (path.startsWith("/") ? path : ("/" + path)) + (httpMethod == null || httpMethod.isEmpty() ? "" : " -- " + httpMethod); - result.add(new SymbolInformation(label, SymbolKind.Interface, location)); + + WebfluxHandlerInformation handler = extractHandlerInformation(node, label); + + result.add(new EnhancedSymbolInformation(new SymbolInformation(label, SymbolKind.Interface, location), handler)); } catch (BadLocationException e) { e.printStackTrace(); } @@ -155,5 +161,28 @@ public class WebfluxRouterSymbolProvider implements SymbolProvider { String method = methodFinder.getMethod(); return method; } + + private WebfluxHandlerInformation extractHandlerInformation(MethodInvocation node, String symbol) { + List arguments = node.arguments(); + + if (arguments != null) { + for (Object argument : arguments) { + if (argument instanceof ExpressionMethodReference) { + ExpressionMethodReference methodReference = (ExpressionMethodReference) argument; + IMethodBinding methodBinding = methodReference.resolveMethodBinding(); + + if (methodBinding != null && methodBinding.getDeclaringClass() != null && methodBinding.getMethodDeclaration() != null) { + ITypeBinding declaringClass = methodBinding.getDeclaringClass(); + String destinationClass = declaringClass.getBinaryName(); + String methodKey = methodBinding.getMethodDeclaration().getKey(); + + return new WebfluxHandlerInformation(symbol, destinationClass, methodKey); + } + } + } + } + + return null; + } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexer.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexer.java index 4224bcf29..971384da2 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexer.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexer.java @@ -29,6 +29,7 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -56,6 +57,7 @@ import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.boot.BootLanguageServerParams; 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.EnhancedSymbolInformation; 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; @@ -80,7 +82,9 @@ public class SpringIndexer { private final AnnotationHierarchyAwareLookup symbolProviders; private final List symbols; + private final List addonInformation; private final ConcurrentMap> symbolsByDoc; + private final ConcurrentMap> addonInformationByDoc; private final Thread updateWorker; private final BlockingQueue updateQueue; @@ -119,6 +123,8 @@ public class SpringIndexer { this.symbols = Collections.synchronizedList(new ArrayList<>()); this.symbolsByDoc = new ConcurrentHashMap<>(); + this.addonInformation = Collections.synchronizedList(new ArrayList<>()); + this.addonInformationByDoc = new ConcurrentHashMap<>(); this.updateQueue = new LinkedBlockingQueue<>(); this.updateWorker = new Thread(new Runnable() { @@ -208,6 +214,9 @@ public class SpringIndexer { symbols.clear(); symbolsByDoc.clear(); + addonInformation.clear(); + addonInformationByDoc.clear(); + Collection roots = server.getWorkspaceRoots(); log.debug("refresh spring indexer for roots: {}", roots.toString()); initialize(roots); @@ -305,6 +314,22 @@ public class SpringIndexer { return this.symbolsByDoc.get(docURI); } + public List getAllAdditionalInformation(Predicate filter) { + waitForInitializeTask(); + + if (filter != null) { + return addonInformation.stream().filter(filter).collect(Collectors.toList()); + } + else { + return null; + } + } + + public List getAdditonalInformation(String docURI) { + waitForInitializeTask(); + return this.addonInformationByDoc.get(docURI); + } + private List searchMatchingSymbols(List allsymbols, String query) { waitForInitializeTask(); return allsymbols.stream() @@ -365,6 +390,11 @@ public class SpringIndexer { symbols.removeAll(oldSymbols); } + List oldAddInInformation = addonInformationByDoc.remove(docURI); + if (oldAddInInformation != null) { + addonInformation.removeAll(oldAddInInformation); + } + AtomicReference docRef = new AtomicReference<>(); scanAST(cu, docURI, docRef, content); } @@ -464,11 +494,16 @@ public class SpringIndexer { if (!providers.isEmpty()) { TextDocument doc = getTempTextDocument(docURI, docRef, content); for (SymbolProvider provider : providers) { - Collection sbls = provider.getSymbols(typeDeclaration, doc); + Collection sbls = provider.getSymbols(typeDeclaration, doc); if (sbls != null) { - sbls.forEach(symbol -> { - symbols.add(symbol); - symbolsByDoc.computeIfAbsent(docURI, s -> new ArrayList()).add(symbol); + sbls.forEach(enhancedSymbol -> { + symbols.add(enhancedSymbol.getSymbol()); + symbolsByDoc.computeIfAbsent(docURI, s -> new ArrayList()).add(enhancedSymbol.getSymbol()); + + if (enhancedSymbol.getAdditionalInformation() != null) { + addonInformation.add(enhancedSymbol.getAdditionalInformation()); + addonInformationByDoc.computeIfAbsent(docURI, s -> new ArrayList()).add(enhancedSymbol.getAdditionalInformation()); + } }); } } @@ -480,11 +515,16 @@ public class SpringIndexer { if (!providers.isEmpty()) { TextDocument doc = getTempTextDocument(docURI, docRef, content); for (SymbolProvider provider : providers) { - Collection sbls = provider.getSymbols(methodDeclaration, doc); + Collection sbls = provider.getSymbols(methodDeclaration, doc); if (sbls != null) { - sbls.forEach(symbol -> { - symbols.add(symbol); - symbolsByDoc.computeIfAbsent(docURI, s -> new ArrayList()).add(symbol); + sbls.forEach(enhancedSymbol -> { + symbols.add(enhancedSymbol.getSymbol()); + symbolsByDoc.computeIfAbsent(docURI, s -> new ArrayList()).add(enhancedSymbol.getSymbol()); + + if (enhancedSymbol.getAdditionalInformation() != null) { + addonInformation.add(enhancedSymbol.getAdditionalInformation()); + addonInformationByDoc.computeIfAbsent(docURI, s -> new ArrayList()).add(enhancedSymbol.getAdditionalInformation()); + } }); } } @@ -500,11 +540,16 @@ public class SpringIndexer { if (!providers.isEmpty()) { TextDocument doc = getTempTextDocument(docURI, docRef, content); for (SymbolProvider provider : providers) { - Collection sbls = provider.getSymbols(node, typeBinding, metaAnnotations, doc); + Collection sbls = provider.getSymbols(node, typeBinding, metaAnnotations, doc); if (sbls != null) { - sbls.forEach(symbol -> { - symbols.add(symbol); - symbolsByDoc.computeIfAbsent(docURI, s -> new ArrayList()).add(symbol); + sbls.forEach(enhancedSymbol -> { + symbols.add(enhancedSymbol.getSymbol()); + symbolsByDoc.computeIfAbsent(docURI, s -> new ArrayList()).add(enhancedSymbol.getSymbol()); + + if (enhancedSymbol.getAdditionalInformation() != null) { + addonInformation.add(enhancedSymbol.getAdditionalInformation()); + addonInformationByDoc.computeIfAbsent(docURI, s -> new ArrayList()).add(enhancedSymbol.getAdditionalInformation()); + } }); } } @@ -660,10 +705,17 @@ public class SpringIndexer { @Override public void run() { try { + List oldSymbols = symbolsByDoc.remove(docURI); if (oldSymbols != null) { symbols.removeAll(oldSymbols); } + + List oldAddInInformation = addonInformationByDoc.remove(docURI); + if (oldAddInInformation != null) { + addonInformation.removeAll(oldAddInInformation); + } + } catch (Exception e) { log.error("{}", e); } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebFluxMappingSymbolProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebFluxMappingSymbolProviderTest.java index d7040c67a..6bef3ae57 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebFluxMappingSymbolProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebFluxMappingSymbolProviderTest.java @@ -11,15 +11,18 @@ package org.springframework.ide.vscode.boot.java.requestmapping.test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.io.File; import java.util.Iterator; import java.util.List; +import java.util.stream.Collectors; import org.eclipse.lsp4j.SymbolInformation; import org.junit.Before; import org.junit.Test; +import org.springframework.ide.vscode.boot.java.requestmapping.WebfluxHandlerInformation; import org.springframework.ide.vscode.project.harness.BootJavaLanguageServerHarness; import org.springframework.ide.vscode.project.harness.ProjectsHarness; @@ -45,6 +48,9 @@ public class WebFluxMappingSymbolProviderTest { assertEquals(4, symbols.size()); assertTrue(containsSymbol(symbols, "@/users", docUri, 19, 1, 19, 74)); assertTrue(containsSymbol(symbols, "@/users/{username}", docUri, 24, 1, 24, 85)); + + List addons = getAdditionalInformation(docUri); + assertNull(addons); } @Test @@ -59,6 +65,29 @@ public class WebFluxMappingSymbolProviderTest { assertTrue(containsSymbol(symbols, "@/echo -- POST", docUri, 23, 5, 23, 101)); assertTrue(containsSymbol(symbols, "@/quotes -- GET", docUri, 24, 5, 24, 86)); assertTrue(containsSymbol(symbols, "@/quotes -- GET", docUri, 25, 5, 25, 94)); + + List addons = getAdditionalInformation(docUri); + assertEquals(4, addons.size()); + + WebfluxHandlerInformation handlerInfo1 = getWebfluxHandler(addons, "@/hello -- GET").get(0); + assertEquals("@/hello -- GET", handlerInfo1.getSymbol()); + assertEquals("org.test.QuoteHandler", handlerInfo1.getDestinationClass()); + assertEquals("Lorg/test/QuoteHandler;.hello(Lorg/springframework/web/reactive/function/server/ServerRequest;)Lreactor/core/publisher/Mono;", handlerInfo1.getMethodKey()); + + WebfluxHandlerInformation handlerInfo2 = getWebfluxHandler(addons, "@/echo -- POST").get(0); + assertEquals("@/echo -- POST", handlerInfo2.getSymbol()); + assertEquals("org.test.QuoteHandler", handlerInfo2.getDestinationClass()); + assertEquals("Lorg/test/QuoteHandler;.echo(Lorg/springframework/web/reactive/function/server/ServerRequest;)Lreactor/core/publisher/Mono;", handlerInfo2.getMethodKey()); + + WebfluxHandlerInformation handlerInfo3 = getWebfluxHandler(addons, "@/quotes -- GET").get(0); + assertEquals("@/quotes -- GET", handlerInfo3.getSymbol()); + assertEquals("org.test.QuoteHandler", handlerInfo3.getDestinationClass()); + assertEquals("Lorg/test/QuoteHandler;.streamQuotes(Lorg/springframework/web/reactive/function/server/ServerRequest;)Lreactor/core/publisher/Mono;", handlerInfo3.getMethodKey()); + + WebfluxHandlerInformation handlerInfo4 = getWebfluxHandler(addons, "@/quotes -- GET").get(1); + assertEquals("@/quotes -- GET", handlerInfo4.getSymbol()); + assertEquals("org.test.QuoteHandler", handlerInfo4.getDestinationClass()); + assertEquals("Lorg/test/QuoteHandler;.fetchQuotes(Lorg/springframework/web/reactive/function/server/ServerRequest;)Lreactor/core/publisher/Mono;", handlerInfo4.getMethodKey()); } @Test @@ -72,6 +101,24 @@ public class WebFluxMappingSymbolProviderTest { assertTrue(containsSymbol(symbols, "@/person/{id} -- GET", docUri, 27, 6, 27, 45)); assertTrue(containsSymbol(symbols, "@/person/ -- POST", docUri, 29, 6, 29, 83)); assertTrue(containsSymbol(symbols, "@/person -- GET", docUri, 28, 7, 28, 60)); + + List addons = getAdditionalInformation(docUri); + assertEquals(3, addons.size()); + + WebfluxHandlerInformation handlerInfo1 = getWebfluxHandler(addons, "@/person/{id} -- GET").get(0); + assertEquals("@/person/{id} -- GET", handlerInfo1.getSymbol()); + assertEquals("org.test.PersonHandler", handlerInfo1.getDestinationClass()); + assertEquals("Lorg/test/PersonHandler;.getPerson(Lorg/springframework/web/reactive/function/server/ServerRequest;)Lreactor/core/publisher/Mono;", handlerInfo1.getMethodKey()); + + WebfluxHandlerInformation handlerInfo2 = getWebfluxHandler(addons, "@/person/ -- POST").get(0); + assertEquals("@/person/ -- POST", handlerInfo2.getSymbol()); + assertEquals("org.test.PersonHandler", handlerInfo2.getDestinationClass()); + assertEquals("Lorg/test/PersonHandler;.createPerson(Lorg/springframework/web/reactive/function/server/ServerRequest;)Lreactor/core/publisher/Mono;", handlerInfo2.getMethodKey()); + + WebfluxHandlerInformation handlerInfo3 = getWebfluxHandler(addons, "@/person -- GET").get(0); + assertEquals("@/person -- GET", handlerInfo3.getSymbol()); + assertEquals("org.test.PersonHandler", handlerInfo3.getDestinationClass()); + assertEquals("Lorg/test/PersonHandler;.listPeople(Lorg/springframework/web/reactive/function/server/ServerRequest;)Lreactor/core/publisher/Mono;", handlerInfo3.getMethodKey()); } private boolean containsSymbol(List symbols, String name, String uri, int startLine, int startCHaracter, int endLine, int endCharacter) { @@ -94,4 +141,17 @@ public class WebFluxMappingSymbolProviderTest { private List getSymbols(String docUri) { return harness.getServerWrapper().getComponents().getSpringIndexer().getSymbols(docUri); } + + private List getAdditionalInformation(String docUri) { + return harness.getServerWrapper().getComponents().getSpringIndexer().getAdditonalInformation(docUri); + } + + private List getWebfluxHandler(List addons, String symbol) { + return addons.stream() + .filter((obj) -> obj instanceof WebfluxHandlerInformation) + .map((obj -> (WebfluxHandlerInformation) obj)) + .filter((addon) -> addon.getSymbol().equals(symbol)) + .collect(Collectors.toList()); + } + }