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 a4ff9943f..2d31b911c 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2018 Pivotal, Inc. + * Copyright (c) 2017, 2019 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 @@ -27,10 +27,12 @@ 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.AbstractSymbolProvider; 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.CachedSymbol; import org.springframework.ide.vscode.boot.java.utils.FunctionUtils; +import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext; import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.Log; import org.springframework.ide.vscode.commons.util.text.DocumentRegion; @@ -46,37 +48,37 @@ import reactor.util.function.Tuples; * @author Martin Lippert * @author Kris De Volder */ -public class BeansSymbolProvider implements SymbolProvider { +public class BeansSymbolProvider extends AbstractSymbolProvider { private static final String[] NAME_ATTRIBUTES = {"value", "name"}; @Override - public Collection getSymbols(Annotation node, ITypeBinding annotationType, Collection metaAnnotations, TextDocument doc) { - if (isMethodAbstract(node)) return null; - - ImmutableList.Builder symbols = ImmutableList.builder(); + protected void addSymbolsPass1(Annotation node, ITypeBinding annotationType, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { + if (isMethodAbstract(node)) return; boolean isFunction = isFunctionBean(node); String beanType = getBeanType(node); String markerString = getAnnotations(node); for (Tuple2 nameAndRegion : getBeanNames(node, doc)) { try { - symbols.add(new EnhancedSymbolInformation( + EnhancedSymbolInformation enhancedSymbol = new EnhancedSymbolInformation( new SymbolInformation( beanLabel(isFunction, nameAndRegion.getT1(), beanType, "@Bean" + markerString), SymbolKind.Interface, new Location(doc.getUri(), doc.toRange(nameAndRegion.getT2()))), null - )); + ); + + context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), enhancedSymbol)); + } catch (BadLocationException e) { Log.log(e); } } - return symbols.build(); } @Override - public Collection getSymbols(TypeDeclaration typeDeclaration, TextDocument doc) { + protected void addSymbolsPass1(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) { // this checks function beans that are defined as implementations of Function interfaces Tuple3 functionBean = FunctionUtils.getFunctionBean(typeDeclaration, doc); if (functionBean != null) { @@ -85,12 +87,13 @@ 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(new EnhancedSymbolInformation(symbol, null)); + + context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), new EnhancedSymbolInformation(symbol, null))); + } catch (BadLocationException e) { Log.log(e); } } - return ImmutableList.of(); } protected Collection> getBeanNames(Annotation node, TextDocument doc) { @@ -213,9 +216,4 @@ public class BeansSymbolProvider implements SymbolProvider { return false; } - @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/beans/ComponentSymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentSymbolProvider.java index d583fc341..b5386043f 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2018 Pivotal, Inc. + * Copyright (c) 2017, 2019 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 @@ -16,36 +16,33 @@ import java.util.stream.Collectors; 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.MethodDeclaration; 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.AbstractSymbolProvider; 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.CachedSymbol; +import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext; import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.Log; 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 { +public class ComponentSymbolProvider extends AbstractSymbolProvider { @Override - public Collection getSymbols(Annotation node, ITypeBinding annotationType, Collection metaAnnotations, TextDocument doc) { + protected void addSymbolsPass1(Annotation node, ITypeBinding annotationType, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { try { - return ImmutableList.of( - createSymbol(node, annotationType, metaAnnotations, doc) - ); + EnhancedSymbolInformation enhancedSymbol = createSymbol(node, annotationType, metaAnnotations, doc); + context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), enhancedSymbol)); } catch (Exception e) { Log.log(e); } - return ImmutableList.of(); } protected EnhancedSymbolInformation createSymbol(Annotation node, ITypeBinding annotationType, Collection metaAnnotations, TextDocument doc) throws BadLocationException { @@ -110,14 +107,4 @@ public class ComponentSymbolProvider implements SymbolProvider { return null; } - @Override - public Collection getSymbols(TypeDeclaration typeDeclaration, 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/data/DataRepositorySymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositorySymbolProvider.java index c5c09ff2a..898e6bdee 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 @@ -10,11 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.data; -import java.util.Collection; - -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.Location; import org.eclipse.lsp4j.SymbolInformation; @@ -22,27 +18,27 @@ import org.eclipse.lsp4j.SymbolKind; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.boot.java.beans.BeanUtils; +import org.springframework.ide.vscode.boot.java.handlers.AbstractSymbolProvider; 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.CachedSymbol; +import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext; import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.text.DocumentRegion; import org.springframework.ide.vscode.commons.util.text.TextDocument; -import com.google.common.collect.ImmutableList; - import reactor.util.function.Tuple4; import reactor.util.function.Tuples; /** * @author Martin Lippert */ -public class DataRepositorySymbolProvider implements SymbolProvider { +public class DataRepositorySymbolProvider extends AbstractSymbolProvider { private static final Logger log = LoggerFactory.getLogger(DataRepositorySymbolProvider.class); @Override - public Collection getSymbols(TypeDeclaration typeDeclaration, TextDocument doc) { + protected void addSymbolsPass1(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, 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) { @@ -51,12 +47,12 @@ 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(new EnhancedSymbolInformation(symbol, null)); + EnhancedSymbolInformation enhancedSymbol = new EnhancedSymbolInformation(symbol, null); + context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), enhancedSymbol)); } catch (BadLocationException e) { log.error("error creating data repository symbol for a specific range", e); } } - return ImmutableList.of(); } protected String beanLabel(boolean isFunctionBean, String beanName, String beanType, String markerString) { @@ -135,13 +131,4 @@ public class DataRepositorySymbolProvider implements SymbolProvider { return BeanUtils.getBeanNameFromType(beanName); } - @Override - 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/AbstractSymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/AbstractSymbolProvider.java new file mode 100644 index 000000000..ec86c2d94 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/AbstractSymbolProvider.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2019 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 + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +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.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJava.SCAN_PASS; +import org.springframework.ide.vscode.commons.util.text.TextDocument; +import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext; + +/** + * @author Martin Lippert + */ +public class AbstractSymbolProvider implements SymbolProvider { + + @Override + public void addSymbols(Annotation node, ITypeBinding typeBinding, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { + if (SCAN_PASS.ONE.equals(context.getPass())) { + addSymbolsPass1(node, typeBinding, metaAnnotations, context, doc); + } + else if (SCAN_PASS.TWO.equals(context.getPass())) { + addSymbolsPass2(node, typeBinding, metaAnnotations, context, doc); + } + } + + @Override + public void addSymbols(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) { + if (SCAN_PASS.ONE.equals(context.getPass())) { + addSymbolsPass1(typeDeclaration, context, doc); + } + else if (SCAN_PASS.TWO.equals(context.getPass())) { + addSymbolsPass2(typeDeclaration, context, doc); + } + } + + @Override + public void addSymbols(MethodDeclaration methodDeclaration, SpringIndexerJavaContext context, TextDocument doc) { + if (SCAN_PASS.ONE.equals(context.getPass())) { + addSymbolsPass1(methodDeclaration, context, doc); + } + else if (SCAN_PASS.TWO.equals(context.getPass())) { + addSymbolsPass2(methodDeclaration, context, doc); + } + } + + + // + // implementations can decide whether to implement just pass1 or if they need 2 phases, they would have to implement both methods (pass1 + pass2) + // + + protected void addSymbolsPass1(Annotation node, ITypeBinding typeBinding, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { + } + + protected void addSymbolsPass1(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) { + } + + protected void addSymbolsPass1(MethodDeclaration methodDeclaration, SpringIndexerJavaContext context, TextDocument doc) { + } + + protected void addSymbolsPass2(Annotation node, ITypeBinding typeBinding, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { + } + + protected void addSymbolsPass2(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) { + } + + protected void addSymbolsPass2(MethodDeclaration methodDeclaration, SpringIndexerJavaContext context, TextDocument doc) { + } + +} 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 27b76d5ea..a69db8b9d 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2018 Pivotal, Inc. + * Copyright (c) 2017, 2019 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 @@ -16,6 +16,7 @@ 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.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext; import org.springframework.ide.vscode.commons.util.text.TextDocument; /** @@ -24,8 +25,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); + void addSymbols(Annotation node, ITypeBinding typeBinding, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc); + void addSymbols(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc); + void addSymbols(MethodDeclaration methodDeclaration, SpringIndexerJavaContext context, 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 605bed94c..f78fb3814 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2018 Pivotal, Inc. + * Copyright (c) 2017, 2019 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 @@ -29,18 +29,22 @@ import org.eclipse.jdt.core.dom.SingleMemberAnnotation; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.lsp4j.Location; import org.springframework.ide.vscode.boot.java.Annotations; +import org.springframework.ide.vscode.boot.java.handlers.AbstractSymbolProvider; 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.CachedSymbol; +import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext; import org.springframework.ide.vscode.commons.util.text.TextDocument; /** * @author Martin Lippert */ -public class RequestMappingSymbolProvider implements SymbolProvider { +public class RequestMappingSymbolProvider extends AbstractSymbolProvider { @Override - public Collection getSymbols(Annotation node, ITypeBinding annotationType, Collection metaAnnotations, TextDocument doc) { + protected void addSymbolsPass1(Annotation node, ITypeBinding annotationType, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { + if (node.getParent() instanceof MethodDeclaration) { try { Location location = new Location(doc.getUri(), doc.toRange(node.getStartPosition(), node.getLength())); @@ -50,7 +54,8 @@ public class RequestMappingSymbolProvider implements SymbolProvider { String[] contentTypes = getContentTypes(node); String[] acceptTypes = getAcceptTypes(node); - return (parentPath == null ? Stream.of("") : Arrays.stream(parentPath)).filter(Objects::nonNull) + Stream stream = parentPath == null ? Stream.of("") : Arrays.stream(parentPath); + stream.filter(Objects::nonNull) .flatMap(parent -> (path == null ? Stream.empty() : Arrays.stream(path)) .filter(Objects::nonNull).map(p -> { String separator = !parent.endsWith("/") && !p.startsWith("/") ? "/" : ""; @@ -61,12 +66,11 @@ public class RequestMappingSymbolProvider implements SymbolProvider { return resultPath.startsWith("/") ? resultPath : "/" + resultPath; })) .map(p -> RouteUtils.createRouteSymbol(location, p, methods, contentTypes, acceptTypes, null)) - .collect(Collectors.toList()); + .forEach((enhancedSymbol) -> context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), enhancedSymbol))); } catch (Exception e) { e.printStackTrace(); } } - return null; } private String[] getMethod(Annotation node) { @@ -173,7 +177,7 @@ public class RequestMappingSymbolProvider implements SymbolProvider { } return null; } - + private String[] getAcceptTypes(Annotation node) { if (node.isNormalAnnotation()) { NormalAnnotation normNode = (NormalAnnotation) node; @@ -212,14 +216,4 @@ public class RequestMappingSymbolProvider implements SymbolProvider { return new String[0]; } - @Override - public Collection getSymbols(TypeDeclaration typeDeclaration, 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/requestmapping/WebfluxRouterSymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxRouterSymbolProvider.java index 7cc37d0f9..33a6c4c95 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018 Pivotal, Inc. + * Copyright (c) 2018, 2019 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 @@ -17,7 +17,6 @@ import java.util.function.Function; 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; @@ -31,66 +30,58 @@ import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.Range; +import org.springframework.ide.vscode.boot.java.handlers.AbstractSymbolProvider; import org.springframework.ide.vscode.boot.java.handlers.EnhancedSymbolInformation; import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation; -import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider; +import org.springframework.ide.vscode.boot.java.utils.CachedSymbol; +import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJava.SCAN_PASS; +import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext; import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.text.TextDocument; /** * @author Martin Lippert */ -public class WebfluxRouterSymbolProvider implements SymbolProvider { +public class WebfluxRouterSymbolProvider extends AbstractSymbolProvider { @Override - public Collection getSymbols(Annotation node, ITypeBinding typeBinding, - Collection metaAnnotations, TextDocument doc) { - return null; - } - - @Override - public Collection getSymbols(TypeDeclaration typeDeclaration, TextDocument doc) { - return null; - } - - @Override - public Collection getSymbols(MethodDeclaration methodDeclaration, TextDocument doc) { + public void addSymbols(MethodDeclaration methodDeclaration, SpringIndexerJavaContext context, TextDocument doc) { Type returnType = methodDeclaration.getReturnType2(); if (returnType != null) { ITypeBinding resolvedBinding = returnType.resolveBinding(); if (resolvedBinding != null) { if (WebfluxUtils.ROUTER_FUNCTION_TYPE.equals(resolvedBinding.getBinaryName())) { - return getSymbolsForRouterFunction(methodDeclaration, doc); + + Block methodBody = methodDeclaration.getBody(); + if (methodBody != null && methodBody.statements() != null && methodBody.statements().size() > 0) { + addSymbolsForRouterFunction(methodBody, context, doc); + } + else if (SCAN_PASS.ONE.equals(context.getPass())) { + context.getNextPassFiles().add(context.getFile()); + } } } } - return null; } - private Collection getSymbolsForRouterFunction(MethodDeclaration methodDeclaration, - TextDocument doc) { - List result = new ArrayList<>(); - - Block body = methodDeclaration.getBody(); - body.accept(new ASTVisitor() { + private void addSymbolsForRouterFunction(Block methodBody, SpringIndexerJavaContext context, TextDocument doc) { + methodBody.accept(new ASTVisitor() { @Override public boolean visit(MethodInvocation node) { IMethodBinding methodBinding = node.resolveMethodBinding(); if (methodBinding != null && WebfluxUtils.isRouteMethodInvocation(methodBinding)) { - extractMappingSymbol(node, doc, result); + extractMappingSymbol(node, doc, context); } return super.visit(node); } }); - - return result; } - protected void extractMappingSymbol(MethodInvocation node, TextDocument doc, List result) { + protected void extractMappingSymbol(MethodInvocation node, TextDocument doc, SpringIndexerJavaContext context) { WebfluxRouteElement[] pathElements = extractPath(node, doc); WebfluxRouteElement[] httpMethods = extractMethods(node, doc); WebfluxRouteElement[] contentTypes = extractContentTypes(node, doc); @@ -113,8 +104,10 @@ public class WebfluxRouterSymbolProvider implements SymbolProvider { WebfluxHandlerInformation handler = extractHandlerInformation(node, path, httpMethods, contentTypes, acceptTypes); WebfluxElementsInformation elements = extractElementsInformation(pathElements, httpMethods, contentTypes, acceptTypes); - result.add(RouteUtils.createRouteSymbol(location, path, getElementStrings(httpMethods), - getElementStrings(contentTypes), getElementStrings(acceptTypes), new SymbolAddOnInformation[] {handler, elements})); + EnhancedSymbolInformation enhancedSymbol = RouteUtils.createRouteSymbol(location, path, getElementStrings(httpMethods), + getElementStrings(contentTypes), getElementStrings(acceptTypes), new SymbolAddOnInformation[] {handler, elements}); + + context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), enhancedSymbol)); } catch (BadLocationException e) { e.printStackTrace(); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/RestrictedDefaultSymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/RestrictedDefaultSymbolProvider.java index 23ee2ebe4..dcf9e0eff 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/RestrictedDefaultSymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/RestrictedDefaultSymbolProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018 Pivotal, Inc. + * Copyright (c) 2018, 2019 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 @@ -18,36 +18,29 @@ import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.IAnnotationBinding; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MethodDeclaration; -import org.eclipse.jdt.core.dom.TypeDeclaration; import org.springframework.ide.vscode.boot.java.Annotations; +import org.springframework.ide.vscode.boot.java.handlers.AbstractSymbolProvider; 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.Log; import org.springframework.ide.vscode.commons.util.text.TextDocument; -import com.google.common.collect.ImmutableList; - /** * @author Martin Lippert */ -public class RestrictedDefaultSymbolProvider implements SymbolProvider { +public class RestrictedDefaultSymbolProvider extends AbstractSymbolProvider { @Override - public Collection getSymbols(Annotation node, ITypeBinding typeBinding, - Collection metaAnnotations, TextDocument doc) { + protected void addSymbolsPass1(Annotation node, ITypeBinding typeBinding, + Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { // provide default symbol only in case this annotation is not combined with @Bean annotation - if (isCombinedWithAnnotation(node, Annotations.BEAN)) { - return null; - } - else { - ImmutableList.Builder symbols = ImmutableList.builder(); + if (!isCombinedWithAnnotation(node, Annotations.BEAN)) { try { - symbols.add(new EnhancedSymbolInformation(DefaultSymbolProvider.provideDefaultSymbol(node, doc), null)); + EnhancedSymbolInformation enhancedSymbol = new EnhancedSymbolInformation(DefaultSymbolProvider.provideDefaultSymbol(node, doc), null); + context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), enhancedSymbol)); } catch (Exception e) { Log.log(e); } - return symbols.build(); } } @@ -74,14 +67,4 @@ public class RestrictedDefaultSymbolProvider implements SymbolProvider { return false; } - @Override - public Collection getSymbols(TypeDeclaration typeDeclaration, 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/utils/SpringIndexerJava.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java index 4eaa007a9..252cb5509 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java @@ -54,6 +54,10 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument; */ public class SpringIndexerJava implements SpringIndexer { + public static enum SCAN_PASS { + ONE, TWO + } + private static final Logger log = LoggerFactory.getLogger(SpringIndexerJava.class); private final SymbolHandler symbolHandler; @@ -108,7 +112,7 @@ public class SpringIndexerJava implements SpringIndexer { } private void scanFile(IJavaProject project, String docURI, long lastModified, String content) throws Exception { - ASTParser parser = createParser(project); + ASTParser parser = createParser(project, false); String unitName = docURI.substring(docURI.lastIndexOf("/")); parser.setUnitName(unitName); @@ -118,12 +122,15 @@ public class SpringIndexerJava implements SpringIndexer { if (cu != null) { List generatedSymbols = new ArrayList(); - AtomicReference docRef = new AtomicReference<>(); - scanAST(project, cu, docURI, lastModified, docRef, content, generatedSymbols); + String file = new File(new URI(docURI)).getAbsolutePath(); + + SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, file, + lastModified, docRef, content, generatedSymbols, SCAN_PASS.ONE, new ArrayList<>()); + + scanAST(context); SymbolCacheKey cacheKey = getCacheKey(project); - String file = new File(new URI(docURI)).getAbsolutePath(); this.cache.update(cacheKey, file, lastModified, generatedSymbols); for (CachedSymbol symbol : generatedSymbols) { @@ -139,21 +146,11 @@ public class SpringIndexerJava implements SpringIndexer { if (symbols == null) { List generatedSymbols = new ArrayList(); - ASTParser parser = createParser(project); + String[] pass2Files = scanFiles(project, javaFiles, generatedSymbols, SCAN_PASS.ONE); + if (pass2Files.length > 0) { + scanFiles(project, pass2Files, generatedSymbols, SCAN_PASS.TWO); + } - FileASTRequestor requestor = new FileASTRequestor() { - @Override - public void acceptAST(String sourceFilePath, CompilationUnit cu) { - File file = new File(sourceFilePath); - String docURI = UriUtil.toUri(file).toString(); - long lastModified = file.lastModified(); - - AtomicReference docRef = new AtomicReference<>(); - scanAST(project, cu, docURI, lastModified, docRef, null, generatedSymbols); - } - }; - - parser.createASTs(javaFiles, null, new String[0], requestor, null); this.cache.store(cacheKey, javaFiles, generatedSymbols); symbols = (CachedSymbol[]) generatedSymbols.toArray(new CachedSymbol[generatedSymbols.size()]); @@ -170,16 +167,42 @@ public class SpringIndexerJava implements SpringIndexer { } } - private void scanAST(final IJavaProject project, final CompilationUnit cu, final String docURI, long lastModified, AtomicReference docRef, final String content, List generatedSymbols) { - cu.accept(new ASTVisitor() { + private String[] scanFiles(IJavaProject project, String[] javaFiles, List generatedSymbols, SCAN_PASS pass) + throws Exception { + ASTParser parser = createParser(project, SCAN_PASS.ONE.equals(pass)); + List nextPassFiles = new ArrayList<>(); + + FileASTRequestor requestor = new FileASTRequestor() { + @Override + public void acceptAST(String sourceFilePath, CompilationUnit cu) { + File file = new File(sourceFilePath); + String docURI = UriUtil.toUri(file).toString(); + long lastModified = file.lastModified(); + AtomicReference docRef = new AtomicReference<>(); + + SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, sourceFilePath, + lastModified, docRef, null, generatedSymbols, pass, nextPassFiles); + + scanAST(context); + } + }; + + parser.createASTs(javaFiles, null, new String[0], requestor, null); + + return (String[]) nextPassFiles.toArray(new String[nextPassFiles.size()]); + } + + private void scanAST(final SpringIndexerJavaContext context) { + + context.getCu().accept(new ASTVisitor() { @Override public boolean visit(TypeDeclaration node) { try { - extractSymbolInformation(project, node, docURI, docRef, content, lastModified, generatedSymbols); + extractSymbolInformation(node, context); } catch (Exception e) { - log.error("error extracting symbol information in project '" + project.getElementName() + "' - for docURI '" + docURI + "' - on node: " + node.toString(), e); + log.error("error extracting symbol information in project '" + context.getProject().getElementName() + "' - for docURI '" + context.getDocURI() + "' - on node: " + node.toString(), e); } return super.visit(node); } @@ -187,10 +210,10 @@ public class SpringIndexerJava implements SpringIndexer { @Override public boolean visit(MethodDeclaration node) { try { - extractSymbolInformation(project, node, docURI, docRef, content, lastModified, generatedSymbols); + extractSymbolInformation(node, context); } catch (Exception e) { - log.error("error extracting symbol information in project '" + project.getElementName() + "' - for docURI '" + docURI + "' - on node: " + node.toString(), e); + log.error("error extracting symbol information in project '" + context.getProject().getElementName() + "' - for docURI '" + context.getDocURI() + "' - on node: " + node.toString(), e); } return super.visit(node); } @@ -198,10 +221,10 @@ public class SpringIndexerJava implements SpringIndexer { @Override public boolean visit(SingleMemberAnnotation node) { try { - extractSymbolInformation(project, node, docURI, docRef, content, lastModified, generatedSymbols); + extractSymbolInformation(node, context); } catch (Exception e) { - log.error("error extracting symbol information in project '" + project.getElementName() + "' - for docURI '" + docURI + "' - on node: " + node.toString(), e); + log.error("error extracting symbol information in project '" + context.getProject().getElementName() + "' - for docURI '" + context.getDocURI() + "' - on node: " + node.toString(), e); } return super.visit(node); @@ -210,10 +233,10 @@ public class SpringIndexerJava implements SpringIndexer { @Override public boolean visit(NormalAnnotation node) { try { - extractSymbolInformation(project, node, docURI, docRef, content, lastModified, generatedSymbols); + extractSymbolInformation(node, context); } catch (Exception e) { - log.error("error extracting symbol information in project '" + project.getElementName() + "' - for docURI '" + docURI + "' - on node: " + node.toString(), e); + log.error("error extracting symbol information in project '" + context.getProject().getElementName() + "' - for docURI '" + context.getDocURI() + "' - on node: " + node.toString(), e); } return super.visit(node); @@ -222,10 +245,10 @@ public class SpringIndexerJava implements SpringIndexer { @Override public boolean visit(MarkerAnnotation node) { try { - extractSymbolInformation(project, node, docURI, docRef, content, lastModified, generatedSymbols); + extractSymbolInformation(node, context); } catch (Exception e) { - log.error("error extracting symbol information in project '" + project.getElementName() + "' - for docURI '" + docURI + "' - on node: " + node.toString(), e); + log.error("error extracting symbol information in project '" + context.getProject().getElementName() + "' - for docURI '" + context.getDocURI() + "' - on node: " + node.toString(), e); } return super.visit(node); @@ -233,84 +256,66 @@ public class SpringIndexerJava implements SpringIndexer { }); } - private void extractSymbolInformation(IJavaProject project, TypeDeclaration typeDeclaration, String docURI, AtomicReference docRef, String content, - long lastModified, List generatedSymbols) throws Exception { + private void extractSymbolInformation(TypeDeclaration typeDeclaration, final SpringIndexerJavaContext context) throws Exception { Collection providers = symbolProviders.getAll(); if (!providers.isEmpty()) { - TextDocument doc = DocumentUtils.getTempTextDocument(docURI, docRef, content); + TextDocument doc = DocumentUtils.getTempTextDocument(context.getDocURI(), context.getDocRef(), context.getContent()); for (SymbolProvider provider : providers) { - Collection sbls = provider.getSymbols(typeDeclaration, doc); - if (sbls != null) { - sbls.forEach(enhancedSymbol -> { - generatedSymbols.add(new CachedSymbol(docURI, lastModified, enhancedSymbol)); - }); - } + provider.addSymbols(typeDeclaration, context, doc); } } } - private void extractSymbolInformation(IJavaProject project, MethodDeclaration methodDeclaration, String docURI, AtomicReference docRef, String content, - long lastModified, List generatedSymbols) throws Exception { + private void extractSymbolInformation(MethodDeclaration methodDeclaration, final SpringIndexerJavaContext context) throws Exception { Collection providers = symbolProviders.getAll(); if (!providers.isEmpty()) { - TextDocument doc = DocumentUtils.getTempTextDocument(docURI, docRef, content); + TextDocument doc = DocumentUtils.getTempTextDocument(context.getDocURI(), context.getDocRef(), context.getContent()); for (SymbolProvider provider : providers) { - Collection sbls = provider.getSymbols(methodDeclaration, doc); - if (sbls != null) { - sbls.forEach(enhancedSymbol -> { - generatedSymbols.add(new CachedSymbol(docURI, lastModified, enhancedSymbol)); - }); - } + provider.addSymbols(methodDeclaration, context, doc); } } } - private void extractSymbolInformation(IJavaProject project, Annotation node, String docURI, AtomicReference docRef, String content, - long lastModified, List generatedSymbols) throws Exception { + private void extractSymbolInformation(Annotation node, final SpringIndexerJavaContext context) throws Exception { ITypeBinding typeBinding = node.resolveTypeBinding(); if (typeBinding != null) { Collection providers = symbolProviders.get(typeBinding); Collection metaAnnotations = AnnotationHierarchies.getMetaAnnotations(typeBinding, symbolProviders::containsKey); if (!providers.isEmpty()) { - TextDocument doc = DocumentUtils.getTempTextDocument(docURI, docRef, content); + TextDocument doc = DocumentUtils.getTempTextDocument(context.getDocURI(), context.getDocRef(), context.getContent()); for (SymbolProvider provider : providers) { - Collection sbls = provider.getSymbols(node, typeBinding, metaAnnotations, doc); - if (sbls != null) { - sbls.forEach(enhancedSymbol -> { - generatedSymbols.add(new CachedSymbol(docURI, lastModified, enhancedSymbol)); - }); - } + provider.addSymbols(node, typeBinding, metaAnnotations, context, doc); } } else { - SymbolInformation symbol = provideDefaultSymbol(project, node, docURI, docRef, content); + SymbolInformation symbol = provideDefaultSymbol(node, context); if (symbol != null) { EnhancedSymbolInformation enhancedSymbol = new EnhancedSymbolInformation(symbol, null); - generatedSymbols.add(new CachedSymbol(docURI, lastModified, enhancedSymbol)); + context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), enhancedSymbol)); } } } } - private SymbolInformation provideDefaultSymbol(IJavaProject project, Annotation node, String docURI, AtomicReference docRef, String content) { + private SymbolInformation provideDefaultSymbol(Annotation node, final SpringIndexerJavaContext context) { try { ITypeBinding type = node.resolveTypeBinding(); if (type != null) { String qualifiedName = type.getQualifiedName(); if (qualifiedName != null && qualifiedName.startsWith("org.springframework")) { - TextDocument doc = DocumentUtils.getTempTextDocument(docURI, docRef, content); + TextDocument doc = DocumentUtils.getTempTextDocument(context.getDocURI(), context.getDocRef(), context.getContent()); return DefaultSymbolProvider.provideDefaultSymbol(node, doc); } } } catch (Exception e) { - log.error("error creating default symbol in project '" + project.getElementName() + "' - for docURI '" + docURI + "' - on node: " + node.toString(), e); + log.error("error creating default symbol in project '" + context.getProject().getElementName() + "' - for docURI '" + context.getDocURI() + "' - on node: " + node.toString(), e); } return null; } - private ASTParser createParser(IJavaProject project) throws Exception { + private ASTParser createParser(IJavaProject project, boolean ignoreMethodBodies) throws Exception { String[] classpathEntries = getClasspathEntries(project); ASTParser parser = ASTParser.newParser(AST.JLS11); @@ -321,8 +326,6 @@ public class SpringIndexerJava implements SpringIndexer { parser.setStatementsRecovery(true); parser.setBindingsRecovery(true); parser.setResolveBindings(true); - - boolean ignoreMethodBodies = "true".equals(System.getProperty("boot.ls.symbols.ignoreMethodBodies", "false")); parser.setIgnoreMethodBodies(ignoreMethodBodies); String[] sourceEntries = new String[] {}; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJavaContext.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJavaContext.java new file mode 100644 index 000000000..1c496c477 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJavaContext.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2017, 2019 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 + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.utils; + +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJava.SCAN_PASS; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.util.text.TextDocument; + +/** + * @author Martin Lippert + */ +public class SpringIndexerJavaContext { + + private final IJavaProject project; + private final CompilationUnit cu; + private final String docURI; + private final String file; + private final long lastModified; + private final AtomicReference docRef; + private final String content; + private final List generatedSymbols; + private final SCAN_PASS pass; + private final List nextPassFiles; + + public SpringIndexerJavaContext(IJavaProject project, CompilationUnit cu, String docURI, String file, long lastModified, + AtomicReference docRef, String content, List generatedSymbols, SCAN_PASS pass, + List nextPassFiles) { + super(); + this.project = project; + this.cu = cu; + this.docURI = docURI; + this.file = file; + this.lastModified = lastModified; + this.docRef = docRef; + this.content = content; + this.generatedSymbols = generatedSymbols; + this.pass = pass; + this.nextPassFiles = nextPassFiles; + } + + public IJavaProject getProject() { + return project; + } + + public CompilationUnit getCu() { + return cu; + } + + public String getDocURI() { + return docURI; + } + + public String getFile() { + return file; + } + + public long getLastModified() { + return lastModified; + } + + public AtomicReference getDocRef() { + return docRef; + } + + public String getContent() { + return content; + } + + public List getGeneratedSymbols() { + return generatedSymbols; + } + + public SCAN_PASS getPass() { + return pass; + } + + public List getNextPassFiles() { + return nextPassFiles; + } + +} diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingLiveHoverTestWithContextPath.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingLiveHoverTestWithContextPath.java index 248a14f90..f3f97ed06 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingLiveHoverTestWithContextPath.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingLiveHoverTestWithContextPath.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018 Pivotal, Inc. + * Copyright (c) 2018, 2019 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