From 025382bedb535ec88399ed54d3887e062a2dd621 Mon Sep 17 00:00:00 2001 From: Martin Lippert Date: Wed, 26 Feb 2025 15:39:03 +0100 Subject: [PATCH] GH-1431: replaced scan pass logic with exception handling and removed AbstractSymbolProvider GH-1431: changed indexing of components to index embedded components like bean methods, request mappings, etc. GH-1431: added new tests for index structure Signed-off-by: Martin Lippert --- .../vscode/boot/java/beans/BeansIndexer.java | 166 ++++++++++ .../boot/java/beans/BeansSymbolProvider.java | 155 +-------- .../java/beans/ComponentSymbolProvider.java | 163 ++++++--- .../java/beans/FeignClientSymbolProvider.java | 9 +- .../data/DataRepositorySymbolProvider.java | 6 +- .../java/events/EventListenerIndexer.java | 116 +++++++ .../events/EventListenerSymbolProvider.java | 93 +----- .../java/handlers/AbstractSymbolProvider.java | 81 ----- .../boot/java/handlers/SymbolProvider.java | 8 +- .../requestmapping/RequestMappingIndexer.java | 310 ++++++++++++++++++ .../RequestMappingSymbolProvider.java | 294 +---------------- .../WebfluxRouterSymbolProvider.java | 59 ++-- .../java/utils/DefaultSymbolProvider.java | 5 +- .../RestrictedDefaultSymbolProvider.java | 24 +- .../java/utils/SpringFactoriesIndexer.java | 4 +- .../boot/java/utils/SpringIndexerJava.java | 182 +++++----- .../java/utils/SpringIndexerJavaContext.java | 32 +- .../index/test/SpringIndexStructureTest.java | 148 +++++++++ .../events/test/SpringIndexerEventsTest.java | 17 +- ...stMappingDependentConstantChangedTest.java | 28 +- .../RequestMappingSymbolProviderTest.java | 156 ++++----- ...mEventPublisherWithAdditionalElements.java | 21 ++ ...RequestMappingPathOverMultipleClasses.java | 2 + .../test/MappingsWithConcatenatedStrings.java | 3 + .../org/test/MultiRequestMappingClass.java | 2 + .../java/org/test/ParentMappingClass.java | 3 + .../java/org/test/ParentMappingClass2.java | 2 + .../java/org/test/ParentMappingClass3.java | 3 + .../org/test/PingConstantRequestMapping.java | 2 + .../org/test/PongConstantRequestMapping.java | 2 + .../org/test/RequestMappingMediaTypes.java | 2 + .../java/org/test/RequestMethodClass.java | 3 + ...appingClassWithConstantFromBinaryType.java | 2 + ...pingClassWithConstantInDifferentClass.java | 2 + ...leMappingClassWithConstantInSameClass.java | 2 + .../SubclassWithMappingFromParent.java | 2 + ...lassWithMappingFromParentWithConstant.java | 2 + ...classWithMappingFromParentWithMethods.java | 2 + ...pingFromParentWithStringConcatenation.java | 2 + ...ntWithStringConcatenationPerAttribute.java | 2 + .../inheritance/SuperControllerLevel4.java | 2 + .../.mvn/wrapper/maven-wrapper.properties | 19 ++ .../test-spring-index-structure/mvnw | 259 +++++++++++++++ .../test-spring-index-structure/mvnw.cmd | 149 +++++++++ .../test-spring-index-structure/pom.xml | 72 ++++ .../src/main/java/org/test/BeanClass1.java | 5 + .../src/main/java/org/test/BeanClass2.java | 5 + .../src/main/java/org/test/MainClass.java | 23 ++ .../java/org/test/RestControllerExample.java | 54 +++ 49 files changed, 1834 insertions(+), 871 deletions(-) create mode 100644 headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeansIndexer.java create mode 100644 headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/events/EventListenerIndexer.java delete mode 100644 headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/AbstractSymbolProvider.java create mode 100644 headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingIndexer.java create mode 100644 headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringIndexStructureTest.java create mode 100644 headless-services/spring-boot-language-server/src/test/resources/test-projects/test-events-indexing/src/main/java/com/example/events/demo/CustomEventPublisherWithAdditionalElements.java create mode 100644 headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-index-structure/.mvn/wrapper/maven-wrapper.properties create mode 100755 headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-index-structure/mvnw create mode 100644 headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-index-structure/mvnw.cmd create mode 100644 headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-index-structure/pom.xml create mode 100644 headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-index-structure/src/main/java/org/test/BeanClass1.java create mode 100644 headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-index-structure/src/main/java/org/test/BeanClass2.java create mode 100644 headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-index-structure/src/main/java/org/test/MainClass.java create mode 100644 headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-index-structure/src/main/java/org/test/RestControllerExample.java diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeansIndexer.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeansIndexer.java new file mode 100644 index 0000000000..3860c21031 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeansIndexer.java @@ -0,0 +1,166 @@ +/******************************************************************************* + * Copyright (c) 2017, 2025 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.beans; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.jdt.core.dom.ASTNode; +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.Modifier; +import org.eclipse.jdt.core.dom.ParameterizedType; +import org.eclipse.jdt.core.dom.Type; +import org.eclipse.lsp4j.Location; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ide.vscode.boot.java.Annotations; +import org.springframework.ide.vscode.boot.java.reconcilers.RequiredCompleteAstException; +import org.springframework.ide.vscode.boot.java.requestmapping.WebfluxRouterSymbolProvider; +import org.springframework.ide.vscode.boot.java.utils.ASTUtils; +import org.springframework.ide.vscode.boot.java.utils.FunctionUtils; +import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext; +import org.springframework.ide.vscode.commons.protocol.spring.AnnotationMetadata; +import org.springframework.ide.vscode.commons.protocol.spring.Bean; +import org.springframework.ide.vscode.commons.protocol.spring.InjectionPoint; +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 reactor.util.function.Tuple2; + +/** + * @author Martin Lippert + * @author Kris De Volder + */ +public class BeansIndexer { + + private static final Logger log = LoggerFactory.getLogger(BeansIndexer.class); + + public static void indexBeanMethod(Bean configBean, Annotation node, SpringIndexerJavaContext context, TextDocument doc) { + if (node == null) return; + + ASTNode parent = node.getParent(); + if (parent == null || !(parent instanceof MethodDeclaration)) return; + + MethodDeclaration method = (MethodDeclaration) parent; + if (isMethodAbstract(method)) return; + + boolean isWebfluxRouter = WebfluxRouterSymbolProvider.isWebfluxRouterBean(method); + if (isWebfluxRouter) { + if (!context.isFullAst()) { + throw new RequiredCompleteAstException(); + } + } + + boolean isFunction = isFunctionBean(method); + ITypeBinding beanType = getBeanType(method); + String markerString = getAnnotations(method); + + for (Tuple2 nameAndRegion : BeanUtils.getBeanNamesFromBeanAnnotationWithRegions(node, doc)) { + try { + Location location = new Location(doc.getUri(), doc.toRange(nameAndRegion.getT2())); + + String beanLabel = beanLabel(isFunction, nameAndRegion.getT1(), beanType.getName(), "@Bean" + markerString); + + InjectionPoint[] injectionPoints = ASTUtils.findInjectionPoints(method, doc); + + Set supertypes = new HashSet<>(); + ASTUtils.findSupertypes(beanType, supertypes); + + Collection annotationsOnMethod = ASTUtils.getAnnotations(method); + AnnotationMetadata[] annotations = ASTUtils.getAnnotationsMetadata(annotationsOnMethod, doc); + + Bean beanDefinition = new Bean(nameAndRegion.getT1(), beanType.getQualifiedName(), location, injectionPoints, supertypes, annotations, false, beanLabel); + + if (isWebfluxRouter) { + WebfluxRouterSymbolProvider.createWebfluxElements(beanDefinition, method, context, doc); + } + + configBean.addChild(beanDefinition); + + } catch (BadLocationException e) { + log.error("", e); + } + } + } + + public static String beanLabel(boolean isFunctionBean, String beanName, String beanType, String markerString) { + StringBuilder symbolLabel = new StringBuilder(); + symbolLabel.append('@'); + symbolLabel.append(isFunctionBean ? '>' : '+'); + symbolLabel.append(' '); + symbolLabel.append('\''); + symbolLabel.append(beanName); + symbolLabel.append('\''); + + markerString = markerString != null && markerString.length() > 0 ? " (" + markerString + ") " : " "; + symbolLabel.append(markerString); + + symbolLabel.append(beanType); + return symbolLabel.toString(); + } + + public static ITypeBinding getBeanType(MethodDeclaration method) { + return method.getReturnType2().resolveBinding(); + } + + public static boolean isFunctionBean(MethodDeclaration method) { + String returnType = null; + + if (method.getReturnType2().isParameterizedType()) { + ParameterizedType paramType = (ParameterizedType) method.getReturnType2(); + Type type = paramType.getType(); + ITypeBinding typeBinding = type.resolveBinding(); + returnType = typeBinding.getBinaryName(); + } + else { + returnType = method.getReturnType2().resolveBinding().getQualifiedName(); + } + + return FunctionUtils.FUNCTION_FUNCTION_TYPE.equals(returnType) || FunctionUtils.FUNCTION_CONSUMER_TYPE.equals(returnType) + || FunctionUtils.FUNCTION_SUPPLIER_TYPE.equals(returnType); + } + + public static String getAnnotations(MethodDeclaration method) { + StringBuilder result = new StringBuilder(); + + List modifiers = method.modifiers(); + for (Object modifier : modifiers) { + if (modifier instanceof Annotation) { + Annotation annotation = (Annotation) modifier; + IAnnotationBinding annotationBinding = annotation.resolveAnnotationBinding(); + String type = annotationBinding.getAnnotationType().getBinaryName(); + + if (type != null && !Annotations.BEAN.equals(type)) { + result.append(' '); + result.append(annotation.toString()); + } + } + } + return result.toString(); + } + + public static boolean isMethodAbstract(MethodDeclaration method) { + List modifiers = method.modifiers(); + for (Object modifier : modifiers) { + if (modifier instanceof Modifier && ((Modifier) modifier).isAbstract()) { + return true; + } + } + return false; + } + +} 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 05c5aa0a7c..d5abeae006 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 @@ -10,21 +10,14 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.beans; -import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; -import java.util.List; import java.util.Set; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.Annotation; -import org.eclipse.jdt.core.dom.Block; -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.Modifier; -import org.eclipse.jdt.core.dom.ParameterizedType; -import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.SymbolKind; @@ -32,18 +25,14 @@ import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.ide.vscode.boot.java.Annotations; -import org.springframework.ide.vscode.boot.java.handlers.AbstractSymbolProvider; -import org.springframework.ide.vscode.boot.java.requestmapping.WebfluxRouterSymbolProvider; +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.SpringIndexerJava.SCAN_PASS; import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext; import org.springframework.ide.vscode.commons.protocol.spring.AnnotationMetadata; import org.springframework.ide.vscode.commons.protocol.spring.Bean; import org.springframework.ide.vscode.commons.protocol.spring.InjectionPoint; -import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement; 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; @@ -54,7 +43,7 @@ * @author Martin Lippert * @author Kris De Volder */ -public class BeansSymbolProvider extends AbstractSymbolProvider { +public class BeansSymbolProvider implements SymbolProvider { private static final Logger log = LoggerFactory.getLogger(BeansSymbolProvider.class); @@ -66,68 +55,24 @@ public void addSymbols(Annotation node, ITypeBinding typeBinding, Collection childElements = new ArrayList<>(); - - boolean isWebfluxRouter = WebfluxRouterSymbolProvider.isWebfluxRouterBean(method); - - // for webflux details, we need full method body ASTs - if (isWebfluxRouter) { - Block methodBody = method.getBody(); - if ((methodBody == null || methodBody.statements() == null || methodBody.statements().size() == 0) - && SCAN_PASS.ONE.equals(context.getPass())) { - context.getNextPassFiles().add(context.getFile()); - return; - } - else { - WebfluxRouterSymbolProvider.createWebfluxElements(method, context, doc, childElements); - } - } else if (!isWebfluxRouter && SCAN_PASS.TWO.equals(context.getPass())) { - return; - } - - boolean isFunction = isFunctionBean(method); + boolean isFunction = BeansIndexer.isFunctionBean(method); - ITypeBinding beanType = getBeanType(method); - String markerString = getAnnotations(method); + ITypeBinding beanType = BeansIndexer.getBeanType(method); + String markerString = BeansIndexer.getAnnotations(method); - // lookup parent config - SpringIndexElement configParent = findNearestConfigBean(context.getBeans(), doc.getUri()); - for (Tuple2 nameAndRegion : BeanUtils.getBeanNamesFromBeanAnnotationWithRegions(node, doc)) { try { Location location = new Location(doc.getUri(), doc.toRange(nameAndRegion.getT2())); WorkspaceSymbol symbol = new WorkspaceSymbol( - beanLabel(isFunction, nameAndRegion.getT1(), beanType.getName(), "@Bean" + markerString), + BeansIndexer.beanLabel(isFunction, nameAndRegion.getT1(), beanType.getName(), "@Bean" + markerString), SymbolKind.Interface, Either.forLeft(location) ); - InjectionPoint[] injectionPoints = ASTUtils.findInjectionPoints(method, doc); - - Set supertypes = new HashSet<>(); - ASTUtils.findSupertypes(beanType, supertypes); - - Collection annotationsOnMethod = ASTUtils.getAnnotations(method); - AnnotationMetadata[] annotations = ASTUtils.getAnnotationsMetadata(annotationsOnMethod, doc); - - Bean beanDefinition = new Bean(nameAndRegion.getT1(), beanType.getQualifiedName(), location, injectionPoints, supertypes, annotations, false, symbol.getName()); - if (childElements.size() > 0) { - for (SpringIndexElement springIndexElement : childElements) { - beanDefinition.addChild(springIndexElement); - } - } - context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), symbol)); - - if (configParent != null) { - configParent.addChild(beanDefinition); - } - else { - context.getBeans().add(new CachedBean(context.getDocURI(), beanDefinition)); - } } catch (BadLocationException e) { log.error("", e); @@ -135,22 +80,12 @@ public void addSymbols(Annotation node, ITypeBinding typeBinding, Collection beans, String docURI) { - int i = beans.size() - 1; - - while (i >= 0 && beans.get(i).getDocURI().equals(docURI)) { - if (beans.get(i).getBean() instanceof Bean bean && bean.isConfiguration() && docURI.equals(docURI)) { - return beans.get(i).getBean(); - } - i--; - } - - return null; + @Override + public void addSymbols(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) { + indexFunctionBeans(typeDeclaration, context, doc); } - @Override - protected void addSymbolsPass1(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) { - // this checks function beans that are defined as implementations of Function interfaces + private void indexFunctionBeans(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) { ITypeBinding functionBean = FunctionUtils.getFunctionBean(typeDeclaration, doc); if (functionBean != null) { try { @@ -159,7 +94,7 @@ protected void addSymbolsPass1(TypeDeclaration typeDeclaration, SpringIndexerJav Location beanLocation = new Location(doc.getUri(), doc.toRange(ASTUtils.nodeRegion(doc, typeDeclaration.getName()))); WorkspaceSymbol symbol = new WorkspaceSymbol( - beanLabel(true, beanName, beanType.getName(), null), + BeansIndexer.beanLabel(true, beanName, beanType.getName(), null), SymbolKind.Interface, Either.forLeft(beanLocation)); @@ -183,70 +118,4 @@ protected void addSymbolsPass1(TypeDeclaration typeDeclaration, SpringIndexerJav } } - public static String beanLabel(boolean isFunctionBean, String beanName, String beanType, String markerString) { - StringBuilder symbolLabel = new StringBuilder(); - symbolLabel.append('@'); - symbolLabel.append(isFunctionBean ? '>' : '+'); - symbolLabel.append(' '); - symbolLabel.append('\''); - symbolLabel.append(beanName); - symbolLabel.append('\''); - - markerString = markerString != null && markerString.length() > 0 ? " (" + markerString + ") " : " "; - symbolLabel.append(markerString); - - symbolLabel.append(beanType); - return symbolLabel.toString(); - } - - protected ITypeBinding getBeanType(MethodDeclaration method) { - return method.getReturnType2().resolveBinding(); - } - - private boolean isFunctionBean(MethodDeclaration method) { - String returnType = null; - - if (method.getReturnType2().isParameterizedType()) { - ParameterizedType paramType = (ParameterizedType) method.getReturnType2(); - Type type = paramType.getType(); - ITypeBinding typeBinding = type.resolveBinding(); - returnType = typeBinding.getBinaryName(); - } - else { - returnType = method.getReturnType2().resolveBinding().getQualifiedName(); - } - - return FunctionUtils.FUNCTION_FUNCTION_TYPE.equals(returnType) || FunctionUtils.FUNCTION_CONSUMER_TYPE.equals(returnType) - || FunctionUtils.FUNCTION_SUPPLIER_TYPE.equals(returnType); - } - - private String getAnnotations(MethodDeclaration method) { - StringBuilder result = new StringBuilder(); - - List modifiers = method.modifiers(); - for (Object modifier : modifiers) { - if (modifier instanceof Annotation) { - Annotation annotation = (Annotation) modifier; - IAnnotationBinding annotationBinding = annotation.resolveAnnotationBinding(); - String type = annotationBinding.getAnnotationType().getBinaryName(); - - if (type != null && !Annotations.BEAN.equals(type)) { - result.append(' '); - result.append(annotation.toString()); - } - } - } - return result.toString(); - } - - private boolean isMethodAbstract(MethodDeclaration method) { - List modifiers = method.modifiers(); - for (Object modifier : modifiers) { - if (modifier instanceof Modifier && ((Modifier) modifier).isAbstract()) { - return true; - } - } - return false; - } - } 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 8ae0fd32dc..268e67f4e2 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 @@ -14,7 +14,6 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; -import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -34,9 +33,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.boot.java.Annotations; +import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies; import org.springframework.ide.vscode.boot.java.events.EventListenerIndexElement; +import org.springframework.ide.vscode.boot.java.events.EventListenerIndexer; import org.springframework.ide.vscode.boot.java.events.EventPublisherIndexElement; -import org.springframework.ide.vscode.boot.java.handlers.AbstractSymbolProvider; +import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider; +import org.springframework.ide.vscode.boot.java.reconcilers.RequiredCompleteAstException; +import org.springframework.ide.vscode.boot.java.requestmapping.RequestMappingIndexer; 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.DefaultSymbolProvider; @@ -53,12 +56,12 @@ * @author Martin Lippert * @author Kris De Volder */ -public class ComponentSymbolProvider extends AbstractSymbolProvider { +public class ComponentSymbolProvider implements SymbolProvider { private static final Logger log = LoggerFactory.getLogger(ComponentSymbolProvider.class); @Override - protected void addSymbolsPass1(Annotation node, ITypeBinding annotationType, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { + public void addSymbols(Annotation node, ITypeBinding annotationType, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { try { if (node != null && node.getParent() != null && node.getParent() instanceof TypeDeclaration) { createSymbol(node, annotationType, metaAnnotations, context, doc); @@ -69,7 +72,7 @@ else if (Annotations.NAMED_ANNOTATIONS.contains(annotationType.getQualifiedName( context.getBeans().add(new CachedBean(context.getDocURI(), new SimpleSymbolElement(symbol))); } } - catch (Exception e) { + catch (BadLocationException e) { log.error("", e); } } @@ -111,30 +114,106 @@ protected void createSymbol(Annotation node, ITypeBinding annotationType, Collec Bean beanDefinition = new Bean(beanName, beanType.getQualifiedName(), location, injectionPoints, supertypes, annotations, isConfiguration, symbol.getName()); - // type implements event listener - move those already created event index elements under the bean node - List alreadyCreatedEventListenerChilds = context.getBeans().stream() - .filter(cachedBean -> cachedBean.getDocURI().equals(doc.getUri())) - .filter(cachedBean -> cachedBean.getBean() instanceof EventListenerIndexElement) - .toList(); - - for (CachedBean eventListener : alreadyCreatedEventListenerChilds) { - context.getBeans().remove(eventListener); - beanDefinition.addChild(eventListener.getBean()); - } - // event publisher checks + boolean usesEventPublisher = false; for (InjectionPoint injectionPoint : injectionPoints) { if (Annotations.EVENT_PUBLISHER.equals(injectionPoint.getType())) { - context.getNextPassFiles().add(context.getFile()); + usesEventPublisher = true; + } + } + + if (usesEventPublisher) { + if (context.isFullAst()) { + scanEventPublisherInvocations(beanDefinition, node, annotationType, metaAnnotations, context, doc); + } + else { + throw new RequiredCompleteAstException(); } } + + indexBeanMethods(beanDefinition, type, annotationType, metaAnnotations, context, doc); + indexEventListeners(beanDefinition, type, annotationType, metaAnnotations, context, doc); + indexEventListenerInterfaceImplementation(beanDefinition, type, context, doc); + indexRequestMappings(beanDefinition, type, annotationType, metaAnnotations, context, doc); context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), symbol)); context.getBeans().add(new CachedBean(context.getDocURI(), beanDefinition)); } - @Override - protected void addSymbolsPass2(Annotation node, ITypeBinding annotationType, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { + private void indexBeanMethods(Bean bean, TypeDeclaration type, ITypeBinding annotationType, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { + AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(type); + if (bean.isConfiguration()) { + MethodDeclaration[] methods = type.getMethods(); + if (methods == null) { + return; + } + + for (int i = 0; i < methods.length; i++) { + MethodDeclaration methodDecl = methods[i]; + Collection annotations = ASTUtils.getAnnotations(methodDecl); + + for (Annotation annotation : annotations) { + ITypeBinding typeBinding = annotation.resolveTypeBinding(); + + boolean isBeanMethod = annotationHierarchies.isAnnotatedWith(typeBinding, Annotations.BEAN); + if (isBeanMethod) { + BeansIndexer.indexBeanMethod(bean, annotation, context, doc); + } + } + } + } + } + + private void indexEventListeners(Bean bean, TypeDeclaration type, ITypeBinding annotationType, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { + AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(type); + + MethodDeclaration[] methods = type.getMethods(); + if (methods == null) { + return; + } + + for (int i = 0; i < methods.length; i++) { + MethodDeclaration methodDecl = methods[i]; + Collection annotations = ASTUtils.getAnnotations(methodDecl); + + for (Annotation annotation : annotations) { + ITypeBinding typeBinding = annotation.resolveTypeBinding(); + + boolean isEventListenerAnnotation = annotationHierarchies.isAnnotatedWith(typeBinding, Annotations.EVENT_LISTENER); + if (isEventListenerAnnotation) { + EventListenerIndexer.indexEventListener(bean, annotation, context, doc); + } + } + } + } + + private void indexRequestMappings(Bean controller, TypeDeclaration type, ITypeBinding annotationType, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { + AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(type); + boolean isController = annotationHierarchies.isAnnotatedWith(annotationType, Annotations.CONTROLLER); + + if (isController) { + MethodDeclaration[] methods = type.getMethods(); + if (methods == null) { + return; + } + + for (int i = 0; i < methods.length; i++) { + MethodDeclaration methodDecl = methods[i]; + Collection annotations = ASTUtils.getAnnotations(methodDecl); + + for (Annotation annotation : annotations) { + ITypeBinding typeBinding = annotation.resolveTypeBinding(); + + boolean isRequestMappingAnnotation = annotationHierarchies.isAnnotatedWith(typeBinding, Annotations.SPRING_REQUEST_MAPPING); + if (isRequestMappingAnnotation) { + RequestMappingIndexer.indexRequestMapping(controller, annotation, context, doc); + } + } + } + } + } + + private void scanEventPublisherInvocations(Bean component, Annotation node, ITypeBinding annotationType, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { TypeDeclaration type = (TypeDeclaration) node.getParent(); type.accept(new ASTVisitor() { @@ -161,11 +240,8 @@ public boolean visit(MethodInvocation methodInvocation) { ASTUtils.findSupertypes(eventTypeBinding, typesFromhierarchy); EventPublisherIndexElement eventPublisherIndexElement = new EventPublisherIndexElement(eventTypeBinding.getQualifiedName(), location, typesFromhierarchy); - Bean publisherBeanElement = findBean(node, methodInvocation, context, doc); - if (publisherBeanElement != null) { - publisherBeanElement.addChild(eventPublisherIndexElement); - } - + component.addChild(eventPublisherIndexElement); + // symbol String symbolLabel = "@EventPublisher (" + eventTypeBinding.getName() + ")"; WorkspaceSymbol symbol = new WorkspaceSymbol(symbolLabel, SymbolKind.Interface, Either.forLeft(location)); @@ -184,7 +260,18 @@ public boolean visit(MethodInvocation methodInvocation) { } @Override - protected void addSymbolsPass1(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) { + public void addSymbols(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) { + AnnotationHierarchies annotationHierarchies = AnnotationHierarchies.get(typeDeclaration); + boolean isComponment = annotationHierarchies.isAnnotatedWith(typeDeclaration.resolveBinding(), Annotations.COMPONENT); + + // check for event listener implementations on classes that are not annotated with component, but created via bean methods (for example) + if (!isComponment) { + indexEventListenerInterfaceImplementation(null, typeDeclaration, context, doc); + } + + } + + private void indexEventListenerInterfaceImplementation(Bean bean, TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) { try { ITypeBinding typeBinding = typeDeclaration.resolveBinding(); if (typeBinding == null) return; @@ -209,7 +296,13 @@ protected void addSymbolsPass1(TypeDeclaration typeDeclaration, SpringIndexerJav AnnotationMetadata[] handleEventMethodAnnotations = ASTUtils.getAnnotationsMetadata(annotationsOnHandleEventMethod, doc); EventListenerIndexElement eventElement = new EventListenerIndexElement(eventTypeFq, handleMethodLocation, typeBinding.getQualifiedName(), handleEventMethodAnnotations); - context.getBeans().add(new CachedBean(doc.getUri(), eventElement)); + + if (bean != null) { + bean.addChild(eventElement); + } + else { + context.getBeans().add(new CachedBean(context.getDocURI(), eventElement)); + } } } catch (BadLocationException e) { log.error("", e); @@ -230,24 +323,6 @@ private MethodDeclaration findHandleEventMethod(TypeDeclaration type) { return null; } - private Bean findBean(Annotation annotation, MethodInvocation methodInvocation, SpringIndexerJavaContext context, TextDocument doc) { - TypeDeclaration declaringType = ASTUtils.findDeclaringType(methodInvocation); - if (declaringType != null) { - String beanName = BeanUtils.getBeanNameFromComponentAnnotation(annotation, declaringType); - if (beanName != null) { - Optional first = context.getBeans().stream().filter(cachedBean -> cachedBean.getDocURI().equals(doc.getUri())) - .map(cachedBean -> cachedBean.getBean()) - .filter(bean -> bean instanceof Bean) - .map(bean -> (Bean) bean) - .filter(bean -> bean.getName().equals(beanName)) - .findFirst(); - return first.get(); - } - } - - return null; - } - protected String beanLabel(String searchPrefix, String annotationTypeName, Collection metaAnnotationNames, String beanName, String beanType) { StringBuilder symbolLabel = new StringBuilder(); symbolLabel.append("@"); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/FeignClientSymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/FeignClientSymbolProvider.java index 5c4a9c2bc0..297153d2ed 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/FeignClientSymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/FeignClientSymbolProvider.java @@ -31,7 +31,7 @@ import org.eclipse.lsp4j.jsonrpc.messages.Tuple.Two; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.ide.vscode.boot.java.handlers.AbstractSymbolProvider; +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; @@ -41,13 +41,12 @@ import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.text.TextDocument; -public class FeignClientSymbolProvider extends AbstractSymbolProvider { +public class FeignClientSymbolProvider implements SymbolProvider { private static final Logger log = LoggerFactory.getLogger(FeignClientSymbolProvider.class); @Override - protected void addSymbolsPass1(Annotation node, ITypeBinding annotationType, Collection metaAnnotations, - SpringIndexerJavaContext context, TextDocument doc) { + public void addSymbols(Annotation node, ITypeBinding annotationType, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { try { if (node != null && node.getParent() != null && node.getParent() instanceof TypeDeclaration) { Two result = createSymbol(node, annotationType, metaAnnotations, doc); @@ -58,7 +57,7 @@ protected void addSymbolsPass1(Annotation node, ITypeBinding annotationType, Col context.getBeans().add(new CachedBean(context.getDocURI(), beanDefinition)); } } - catch (Exception e) { + catch (BadLocationException e) { log.error("", e); } } 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 a8c80860b0..da81c2d30e 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 @@ -25,7 +25,7 @@ import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.boot.java.beans.BeanUtils; import org.springframework.ide.vscode.boot.java.beans.CachedBean; -import org.springframework.ide.vscode.boot.java.handlers.AbstractSymbolProvider; +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; @@ -42,12 +42,12 @@ /** * @author Martin Lippert */ -public class DataRepositorySymbolProvider extends AbstractSymbolProvider { +public class DataRepositorySymbolProvider implements SymbolProvider { private static final Logger log = LoggerFactory.getLogger(DataRepositorySymbolProvider.class); @Override - protected void addSymbolsPass1(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) { + public void addSymbols(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); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/events/EventListenerIndexer.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/events/EventListenerIndexer.java new file mode 100644 index 0000000000..c1670b92c9 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/events/EventListenerIndexer.java @@ -0,0 +1,116 @@ +/******************************************************************************* + * Copyright (c) 2025 Broadcom + * 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: + * Broadcom - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.events; + +import java.util.Collection; +import java.util.List; + +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.IVariableBinding; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.SingleVariableDeclaration; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.lsp4j.Location; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ide.vscode.boot.java.utils.ASTUtils; +import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext; +import org.springframework.ide.vscode.commons.protocol.spring.AnnotationMetadata; +import org.springframework.ide.vscode.commons.protocol.spring.Bean; +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; + +/** + * @author Martin Lippert + */ +public class EventListenerIndexer { + + private static final Logger log = LoggerFactory.getLogger(EventListenerIndexer.class); + + public static void indexEventListener(Bean component, Annotation node, SpringIndexerJavaContext context, TextDocument doc) { + if (node == null) return; + + ASTNode parent = node.getParent(); + if (parent == null || !(parent instanceof MethodDeclaration)) return; + + MethodDeclaration method = (MethodDeclaration) parent; + + try { +// String symbolLabel = createEventListenerSymbolLabel(node, method); + + // index element for event listener + Collection annotationsOnMethod = ASTUtils.getAnnotations(method); + AnnotationMetadata[] annotations = ASTUtils.getAnnotationsMetadata(annotationsOnMethod, doc); + + ITypeBinding eventType = getEventType(node, method); + DocumentRegion nodeRegion = ASTUtils.nodeRegion(doc, method.getName()); + Location location = new Location(doc.getUri(), nodeRegion.asRange()); + + String containerBeanType = null; + TypeDeclaration type = ASTUtils.findDeclaringType(method); + if (type != null) { + ITypeBinding binding = type.resolveBinding(); + if (binding != null) { + containerBeanType = binding.getQualifiedName(); + } + } + + EventListenerIndexElement eventListenerIndexElement = new EventListenerIndexElement(eventType != null ? eventType.getQualifiedName() : "", location, containerBeanType, annotations); + component.addChild(eventListenerIndexElement); + + } catch (BadLocationException e) { + log.error("", e); + } + } + + public static String createEventListenerSymbolLabel(Annotation node, MethodDeclaration method) { + // event listener annotation type + String annotationTypeName = getAnnotationTypeName(node); + ITypeBinding eventType = getEventType(node, method); + + if (annotationTypeName != null) { + return "@" + annotationTypeName + (eventType != null ? " (" + eventType.getName() + ")" : ""); + } + else { + return node.toString(); + } + } + + public static ITypeBinding getEventType(Annotation node, MethodDeclaration method) { + List parameters = method.parameters(); + if (parameters != null && parameters.size() == 1) { + SingleVariableDeclaration param = (SingleVariableDeclaration) parameters.get(0); + + IVariableBinding paramBinding = param.resolveBinding(); + if (paramBinding != null) { + ITypeBinding paramType = paramBinding.getType(); + return paramType != null ? paramType : null; + } + } + + return null; + } + + public static String getAnnotationTypeName(Annotation node) { + ITypeBinding typeBinding = node.resolveTypeBinding(); + if (typeBinding != null) { + return typeBinding.getName(); + } + else { + return null; + } + } + + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/events/EventListenerSymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/events/EventListenerSymbolProvider.java index 4561601f5e..80e2f3be62 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/events/EventListenerSymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/events/EventListenerSymbolProvider.java @@ -9,41 +9,34 @@ * Broadcom - initial API and implementation *******************************************************************************/ package org.springframework.ide.vscode.boot.java.events; + import java.util.Collection; -import java.util.List; 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.IVariableBinding; import org.eclipse.jdt.core.dom.MethodDeclaration; -import org.eclipse.jdt.core.dom.SingleVariableDeclaration; -import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.SymbolKind; import org.eclipse.lsp4j.WorkspaceSymbol; import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.ide.vscode.boot.java.beans.CachedBean; -import org.springframework.ide.vscode.boot.java.handlers.AbstractSymbolProvider; -import org.springframework.ide.vscode.boot.java.utils.ASTUtils; +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.protocol.spring.AnnotationMetadata; 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; /** * @author Martin Lippert */ -public class EventListenerSymbolProvider extends AbstractSymbolProvider { +public class EventListenerSymbolProvider implements SymbolProvider { private static final Logger log = LoggerFactory.getLogger(EventListenerSymbolProvider.class); @Override - protected void addSymbolsPass1(Annotation node, ITypeBinding typeBinding, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { + public void addSymbols(Annotation node, ITypeBinding typeBinding, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { if (node == null) return; ASTNode parent = node.getParent(); @@ -51,89 +44,17 @@ protected void addSymbolsPass1(Annotation node, ITypeBinding typeBinding, Collec MethodDeclaration method = (MethodDeclaration) parent; - // symbol try { - String symbolLabel = createEventListenerSymbolLabel(node, method); + String symbolLabel = EventListenerIndexer.createEventListenerSymbolLabel(node, method); WorkspaceSymbol symbol = new WorkspaceSymbol(symbolLabel, SymbolKind.Interface, Either.forLeft(new Location(doc.getUri(), doc.toRange(node.getStartPosition(), node.getLength())))); context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), symbol)); - - - // index element for event listener - Collection annotationsOnMethod = ASTUtils.getAnnotations(method); - AnnotationMetadata[] annotations = ASTUtils.getAnnotationsMetadata(annotationsOnMethod, doc); - - List beans = context.getBeans(); - if (beans.size() > 0 ) { - - CachedBean cachedBean = beans.get(beans.size() - 1); - if (cachedBean.getDocURI().equals(doc.getUri())) { - - ITypeBinding eventType = getEventType(node, method); - DocumentRegion nodeRegion = ASTUtils.nodeRegion(doc, method.getName()); - Location location = new Location(doc.getUri(), nodeRegion.asRange()); - - String containerBeanType = null; - TypeDeclaration type = ASTUtils.findDeclaringType(method); - if (type != null) { - ITypeBinding binding = type.resolveBinding(); - if (binding != null) { - containerBeanType = binding.getQualifiedName(); - } - } - - cachedBean.getBean().addChild(new EventListenerIndexElement(eventType != null ? eventType.getQualifiedName() : "", location, containerBeanType, annotations)); - } - } - } catch (BadLocationException e) { - log.error("", e); - } - } - - @Override - protected void addSymbolsPass1(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) { - super.addSymbolsPass1(typeDeclaration, context, doc); - } - - private String createEventListenerSymbolLabel(Annotation node, MethodDeclaration method) { - // event listener annotation type - String annotationTypeName = getAnnotationTypeName(node); - ITypeBinding eventType = getEventType(node, method); - - if (annotationTypeName != null) { - return "@" + annotationTypeName + (eventType != null ? " (" + eventType.getName() + ")" : ""); } - else { - return node.toString(); + catch (BadLocationException e) { + log.error("", e); } } - private ITypeBinding getEventType(Annotation node, MethodDeclaration method) { - List parameters = method.parameters(); - if (parameters != null && parameters.size() == 1) { - SingleVariableDeclaration param = (SingleVariableDeclaration) parameters.get(0); - - IVariableBinding paramBinding = param.resolveBinding(); - if (paramBinding != null) { - ITypeBinding paramType = paramBinding.getType(); - return paramType != null ? paramType : null; - } - } - - return null; - } - - private String getAnnotationTypeName(Annotation node) { - ITypeBinding typeBinding = node.resolveTypeBinding(); - if (typeBinding != null) { - return typeBinding.getName(); - } - else { - 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 deleted file mode 100644 index ec86c2d94f..0000000000 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/AbstractSymbolProvider.java +++ /dev/null @@ -1,81 +0,0 @@ -/******************************************************************************* - * 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 a69db8b9dd..4cb35e0e63 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, 2019 Pivotal, Inc. + * Copyright (c) 2017, 2025 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 @@ -25,8 +25,8 @@ */ public interface SymbolProvider { - 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); + default void addSymbols(Annotation node, ITypeBinding typeBinding, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) {}; + default void addSymbols(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) {}; + default 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/RequestMappingIndexer.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingIndexer.java new file mode 100644 index 0000000000..949006a6db --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingIndexer.java @@ -0,0 +1,310 @@ +/******************************************************************************* + * Copyright (c) 2017, 2025 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.requestmapping; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Stream; + +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.Annotation; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jdt.core.dom.IAnnotationBinding; +import org.eclipse.jdt.core.dom.IMemberValuePairBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.MemberValuePair; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.NormalAnnotation; +import org.eclipse.jdt.core.dom.SingleMemberAnnotation; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.lsp4j.Location; +import org.eclipse.lsp4j.WorkspaceSymbol; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ide.vscode.boot.java.Annotations; +import org.springframework.ide.vscode.boot.java.utils.ASTUtils; +import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext; +import org.springframework.ide.vscode.commons.protocol.spring.Bean; +import org.springframework.ide.vscode.commons.util.text.TextDocument; + +/** + * @author Martin Lippert + */ +public class RequestMappingIndexer { + + private static final Set ATTRIBUTE_NAME_VALUE_PATH = Set.of("value", "path"); + private static final Set ATTRIBUTE_NAME_METHOD = Set.of("method"); + private static final Set ATTRIBUTE_NAME_CONSUMES = Set.of("consumes"); + private static final Set ATTRIBUTE_NAME_PRODUCES = Set.of("produces"); + + private static final Logger log = LoggerFactory.getLogger(RequestMappingIndexer.class); + + public static void indexRequestMapping(Bean controller, Annotation node, SpringIndexerJavaContext context, TextDocument doc) { + + if (node.getParent() instanceof MethodDeclaration) { + try { + Location location = new Location(doc.getUri(), doc.toRange(node.getStartPosition(), node.getLength())); + String[] path = getPath(node, context); + String[] parentPath = getParentPath(node, context); + String[] methods = getMethod(node, context); + String[] contentTypes = getContentTypes(node, context); + String[] acceptTypes = getAcceptTypes(node, context); + + 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 -> { + return combinePath(parent, p); + })) + .forEach(p -> { + // symbol + WorkspaceSymbol symbol = RouteUtils.createRouteSymbol(location, p, methods, contentTypes, acceptTypes); + + // index element for request mapping + RequestMappingIndexElement requestMappingIndexElement = new RequestMappingIndexElement(p, methods, contentTypes, acceptTypes, location.getRange(), symbol.getName()); + controller.addChild(requestMappingIndexElement); + }); + + } catch (Exception e) { + log.error("problem occured while scanning for request mapping symbols from " + doc.getUri(), e); + } + } + } + + public static String combinePath(String parent, String path) { + String separator = !parent.endsWith("/") && !path.startsWith("/") && !path.isEmpty() ? "/" : ""; + String resultPath = parent + separator + path; + + String result = resultPath.startsWith("/") ? resultPath : "/" + resultPath; + return result; + } + + public static String[] getPath(Annotation node, SpringIndexerJavaContext context) { + String[] result = getAttributeValuesFromAnnotation(node, context, ATTRIBUTE_NAME_VALUE_PATH); + + if (result == null && node.isSingleMemberAnnotation()) { + SingleMemberAnnotation singleNode = (SingleMemberAnnotation) node; + Expression expression = singleNode.getValue(); + result = ASTUtils.getExpressionValueAsArray(expression, context::addDependency); + } + + return result != null ? result : new String[] { "" }; + } + + public static String[] getParentPath(Annotation node, SpringIndexerJavaContext context) { + Annotation parentAnnotation = getAnnotationFromClassLevel(node); + if (parentAnnotation != null) { + return getPath(parentAnnotation, context); + } + else { + return getAttributeValuesFromSupertypes(node, context, ATTRIBUTE_NAME_VALUE_PATH); + } + } + + public static String[] getAcceptTypes(Annotation node, SpringIndexerJavaContext context) { + return getAttributeValues(node, context, ATTRIBUTE_NAME_CONSUMES); + } + + public static String[] getContentTypes(Annotation node, SpringIndexerJavaContext context) { + return getAttributeValues(node, context, ATTRIBUTE_NAME_PRODUCES); + } + + public static String[] getMethod(Annotation node, SpringIndexerJavaContext context) { + // extract from annotation type + String[] methods = getRequestMethod(node); + + // extract from annotation params + if (methods == null) { + methods = getAttributeValues(node, context, ATTRIBUTE_NAME_METHOD); + } + return methods; + } + + private static String[] getRequestMethod(Annotation annotation) { + ITypeBinding type = annotation.resolveTypeBinding(); + if (type != null) { + switch (type.getQualifiedName()) { + case Annotations.SPRING_GET_MAPPING: + return new String[] { "GET" }; + case Annotations.SPRING_POST_MAPPING: + return new String[] { "POST" }; + case Annotations.SPRING_DELETE_MAPPING: + return new String[] { "DELETE" }; + case Annotations.SPRING_PUT_MAPPING: + return new String[] { "PUT" }; + case Annotations.SPRING_PATCH_MAPPING: + return new String[] { "PATCH" }; + } + } + return null; + } + + private static String[] getAttributeValues(Annotation node, SpringIndexerJavaContext context, Set attributeNames) { + String[] result = getAttributeValuesFromAnnotation(node, context, attributeNames); + + // extract from parent annotations + if (result == null) { + Annotation parentAnnotation = getAnnotationFromClassLevel(node); + if (parentAnnotation != null) { + result = getAttributeValuesFromAnnotation(parentAnnotation, context, attributeNames); + } + else { + result = getAttributeValuesFromSupertypes(node, context, attributeNames); + } + } + + return result; + } + + private static String[] getAttributeValuesFromAnnotation(Annotation node, SpringIndexerJavaContext context, Set attributeNames) { + if (node.isNormalAnnotation()) { + NormalAnnotation normNode = (NormalAnnotation) node; + List values = normNode.values(); + for (Iterator iterator = values.iterator(); iterator.hasNext();) { + Object object = iterator.next(); + if (object instanceof MemberValuePair) { + MemberValuePair pair = (MemberValuePair) object; + String valueName = pair.getName().getIdentifier(); + if (valueName != null && attributeNames.contains(valueName)) { + Expression expression = pair.getValue(); + return ASTUtils.getExpressionValueAsArray(expression, context::addDependency); + } + } + } + } + return null; + } + + private static String[] getAttributeValuesFromSupertypes(Annotation node, SpringIndexerJavaContext context, Set attributeNames) { + IAnnotationBinding annotationBinding = getAnnotationFromSupertypes(node, context); + IMemberValuePairBinding valuePair = getValuePair(annotationBinding, attributeNames); + + ASTUtils.MemberValuePairAndType result = ASTUtils.getValuesFromValuePair(valuePair); + + if (result != null) { + if (result.dereferencedType != null) { + context.addDependency(result.dereferencedType); + } + return result.values; + } + else { + return null; + } + } + + private static IMemberValuePairBinding getValuePair(IAnnotationBinding annotationBinding, Set names) { + if (annotationBinding != null) { + IMemberValuePairBinding[] valuePairs = annotationBinding.getDeclaredMemberValuePairs(); + + if (valuePairs != null ) { + + for (int j = 0; j < valuePairs.length; j++) { + String valueName = valuePairs[j].getName(); + + if (valueName != null && names.contains(valueName)) { + return valuePairs[j]; + } + } + } + } + return null; + } + + private static Annotation getAnnotationFromClassLevel(Annotation node) { + // lookup class level request mapping annotation + ASTNode parent = node.getParent() != null ? node.getParent().getParent() : null; + while (parent != null && !(parent instanceof TypeDeclaration)) { + parent = parent.getParent(); + } + + if (parent != null) { + TypeDeclaration type = (TypeDeclaration) parent; + List modifiers = type.modifiers(); + Iterator iterator = modifiers.iterator(); + while (iterator.hasNext()) { + Object modifier = iterator.next(); + if (modifier instanceof Annotation) { + Annotation annotation = (Annotation) modifier; + ITypeBinding resolvedType = annotation.resolveTypeBinding(); + String annotationType = resolvedType.getQualifiedName(); + if (annotationType != null && Annotations.SPRING_REQUEST_MAPPING.equals(annotationType)) { + return annotation; + } + } + } + } + + return null; + } + + private static IAnnotationBinding getAnnotationFromSupertypes(Annotation node, SpringIndexerJavaContext context) { + ASTNode parent = node.getParent() != null ? node.getParent().getParent() : null; + while (parent != null && !(parent instanceof TypeDeclaration)) { + parent = parent.getParent(); + } + + if (parent != null) { + TypeDeclaration type = (TypeDeclaration) parent; + ITypeBinding typeBinding = type.resolveBinding(); + if (typeBinding != null) { + return findFirstRequestMappingAnnotation(typeBinding); + } + } + + return null; + } + + private static IAnnotationBinding findFirstRequestMappingAnnotation(ITypeBinding start) { + if (start == null) { + return null; + } + + IAnnotationBinding found = getRequestMappingAnnotation(start); + if (found != null) { + return found; + } + else { + // search interfaces first + ITypeBinding[] interfaces = start.getInterfaces(); + if (interfaces != null) { + for (int i = 0; i < interfaces.length; i++) { + found = findFirstRequestMappingAnnotation(interfaces[i]); + if (found != null) { + return found; + } + } + } + + // search superclass second + ITypeBinding superclass = start.getSuperclass(); + if (superclass != null) { + return findFirstRequestMappingAnnotation(superclass); + } + + // nothing found + return null; + } + } + + private static IAnnotationBinding getRequestMappingAnnotation(ITypeBinding typeBinding) { + IAnnotationBinding[] annotations = typeBinding.getAnnotations(); + for (int i = 0; i < annotations.length; i++) { + if (annotations[i].getAnnotationType() != null && Annotations.SPRING_REQUEST_MAPPING.equals(annotations[i].getAnnotationType().getQualifiedName())) { + return annotations[i]; + } + } + return null; + } + +} 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 506eca18a1..28012b7da3 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 @@ -12,329 +12,57 @@ import java.util.Arrays; import java.util.Collection; -import java.util.Iterator; -import java.util.List; import java.util.Objects; -import java.util.Set; import java.util.stream.Stream; -import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.Annotation; -import org.eclipse.jdt.core.dom.Expression; -import org.eclipse.jdt.core.dom.IAnnotationBinding; -import org.eclipse.jdt.core.dom.IMemberValuePairBinding; import org.eclipse.jdt.core.dom.ITypeBinding; -import org.eclipse.jdt.core.dom.MemberValuePair; import org.eclipse.jdt.core.dom.MethodDeclaration; -import org.eclipse.jdt.core.dom.NormalAnnotation; -import org.eclipse.jdt.core.dom.SingleMemberAnnotation; -import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.WorkspaceSymbol; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.ide.vscode.boot.java.Annotations; -import org.springframework.ide.vscode.boot.java.beans.CachedBean; -import org.springframework.ide.vscode.boot.java.handlers.AbstractSymbolProvider; -import org.springframework.ide.vscode.boot.java.utils.ASTUtils; +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.protocol.spring.Bean; -import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement; +import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.text.TextDocument; /** * @author Martin Lippert */ -public class RequestMappingSymbolProvider extends AbstractSymbolProvider { - - private static final Set ATTRIBUTE_NAME_VALUE_PATH = Set.of("value", "path"); - private static final Set ATTRIBUTE_NAME_METHOD = Set.of("method"); - private static final Set ATTRIBUTE_NAME_CONSUMES = Set.of("consumes"); - private static final Set ATTRIBUTE_NAME_PRODUCES = Set.of("produces"); +public class RequestMappingSymbolProvider implements SymbolProvider { private static final Logger log = LoggerFactory.getLogger(RequestMappingSymbolProvider.class); @Override - protected void addSymbolsPass1(Annotation node, ITypeBinding annotationType, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { + public void addSymbols(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())); - String[] path = getPath(node, context); - String[] parentPath = getParentPath(node, context); - String[] methods = getMethod(node, context); - String[] contentTypes = getContentTypes(node, context); - String[] acceptTypes = getAcceptTypes(node, context); + String[] path = RequestMappingIndexer.getPath(node, context); + String[] parentPath = RequestMappingIndexer.getParentPath(node, context); + String[] methods = RequestMappingIndexer.getMethod(node, context); + String[] contentTypes = RequestMappingIndexer.getContentTypes(node, context); + String[] acceptTypes = RequestMappingIndexer.getAcceptTypes(node, context); 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 -> { - return combinePath(parent, p); + return RequestMappingIndexer.combinePath(parent, p); })) .forEach(p -> { // symbol WorkspaceSymbol symbol = RouteUtils.createRouteSymbol(location, p, methods, contentTypes, acceptTypes); context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), symbol)); - - // index element for request mapping - SpringIndexElement parent = getPotentialParentIndexNode(context); - if (parent != null) { - parent.addChild(new RequestMappingIndexElement(p, methods, contentTypes, acceptTypes, location.getRange(), symbol.getName())); - } - else { - context.getBeans().add(new CachedBean(doc.getUri(), new RequestMappingIndexElement(p, methods, contentTypes, acceptTypes, location.getRange(), symbol.getName()))); - } }); - } catch (Exception e) { + } catch (BadLocationException e) { log.error("problem occured while scanning for request mapping symbols from " + doc.getUri(), e); } } } - private SpringIndexElement getPotentialParentIndexNode(SpringIndexerJavaContext context) { - List beans = context.getBeans(); - - for (int i = beans.size() - 1; i >= 0; i--) { - CachedBean cachedBean = beans.get(i); - - if (!cachedBean.getDocURI().equals(context.getDocURI())) { - return null; - } - - if (cachedBean.getBean() instanceof Bean bean) { - return bean; - } - } - - return null; - } - - private String combinePath(String parent, String path) { - String separator = !parent.endsWith("/") && !path.startsWith("/") && !path.isEmpty() ? "/" : ""; - String resultPath = parent + separator + path; - - String result = resultPath.startsWith("/") ? resultPath : "/" + resultPath; - return result; - } - - private String[] getPath(Annotation node, SpringIndexerJavaContext context) { - String[] result = getAttributeValuesFromAnnotation(node, context, ATTRIBUTE_NAME_VALUE_PATH); - - if (result == null && node.isSingleMemberAnnotation()) { - SingleMemberAnnotation singleNode = (SingleMemberAnnotation) node; - Expression expression = singleNode.getValue(); - result = ASTUtils.getExpressionValueAsArray(expression, context::addDependency); - } - - return result != null ? result : new String[] { "" }; - } - - private String[] getParentPath(Annotation node, SpringIndexerJavaContext context) { - Annotation parentAnnotation = getAnnotationFromClassLevel(node); - if (parentAnnotation != null) { - return getPath(parentAnnotation, context); - } - else { - return getAttributeValuesFromSupertypes(node, context, ATTRIBUTE_NAME_VALUE_PATH); - } - } - - private String[] getAcceptTypes(Annotation node, SpringIndexerJavaContext context) { - return getAttributeValues(node, context, ATTRIBUTE_NAME_CONSUMES); - } - - private String[] getContentTypes(Annotation node, SpringIndexerJavaContext context) { - return getAttributeValues(node, context, ATTRIBUTE_NAME_PRODUCES); - } - - private String[] getMethod(Annotation node, SpringIndexerJavaContext context) { - // extract from annotation type - String[] methods = getRequestMethod(node); - - // extract from annotation params - if (methods == null) { - methods = getAttributeValues(node, context, ATTRIBUTE_NAME_METHOD); - } - return methods; - } - - private String[] getRequestMethod(Annotation annotation) { - ITypeBinding type = annotation.resolveTypeBinding(); - if (type != null) { - switch (type.getQualifiedName()) { - case Annotations.SPRING_GET_MAPPING: - return new String[] { "GET" }; - case Annotations.SPRING_POST_MAPPING: - return new String[] { "POST" }; - case Annotations.SPRING_DELETE_MAPPING: - return new String[] { "DELETE" }; - case Annotations.SPRING_PUT_MAPPING: - return new String[] { "PUT" }; - case Annotations.SPRING_PATCH_MAPPING: - return new String[] { "PATCH" }; - } - } - return null; - } - - private String[] getAttributeValues(Annotation node, SpringIndexerJavaContext context, Set attributeNames) { - String[] result = getAttributeValuesFromAnnotation(node, context, attributeNames); - - // extract from parent annotations - if (result == null) { - Annotation parentAnnotation = getAnnotationFromClassLevel(node); - if (parentAnnotation != null) { - result = getAttributeValuesFromAnnotation(parentAnnotation, context, attributeNames); - } - else { - result = getAttributeValuesFromSupertypes(node, context, attributeNames); - } - } - - return result; - } - - private String[] getAttributeValuesFromAnnotation(Annotation node, SpringIndexerJavaContext context, Set attributeNames) { - if (node.isNormalAnnotation()) { - NormalAnnotation normNode = (NormalAnnotation) node; - List values = normNode.values(); - for (Iterator iterator = values.iterator(); iterator.hasNext();) { - Object object = iterator.next(); - if (object instanceof MemberValuePair) { - MemberValuePair pair = (MemberValuePair) object; - String valueName = pair.getName().getIdentifier(); - if (valueName != null && attributeNames.contains(valueName)) { - Expression expression = pair.getValue(); - return ASTUtils.getExpressionValueAsArray(expression, context::addDependency); - } - } - } - } - return null; - } - - private String[] getAttributeValuesFromSupertypes(Annotation node, SpringIndexerJavaContext context, Set attributeNames) { - IAnnotationBinding annotationBinding = getAnnotationFromSupertypes(node, context); - IMemberValuePairBinding valuePair = getValuePair(annotationBinding, attributeNames); - - ASTUtils.MemberValuePairAndType result = ASTUtils.getValuesFromValuePair(valuePair); - - if (result != null) { - if (result.dereferencedType != null) { - context.addDependency(result.dereferencedType); - } - return result.values; - } - else { - return null; - } - } - - private IMemberValuePairBinding getValuePair(IAnnotationBinding annotationBinding, Set names) { - if (annotationBinding != null) { - IMemberValuePairBinding[] valuePairs = annotationBinding.getDeclaredMemberValuePairs(); - - if (valuePairs != null ) { - - for (int j = 0; j < valuePairs.length; j++) { - String valueName = valuePairs[j].getName(); - - if (valueName != null && names.contains(valueName)) { - return valuePairs[j]; - } - } - } - } - return null; - } - - private Annotation getAnnotationFromClassLevel(Annotation node) { - // lookup class level request mapping annotation - ASTNode parent = node.getParent() != null ? node.getParent().getParent() : null; - while (parent != null && !(parent instanceof TypeDeclaration)) { - parent = parent.getParent(); - } - - if (parent != null) { - TypeDeclaration type = (TypeDeclaration) parent; - List modifiers = type.modifiers(); - Iterator iterator = modifiers.iterator(); - while (iterator.hasNext()) { - Object modifier = iterator.next(); - if (modifier instanceof Annotation) { - Annotation annotation = (Annotation) modifier; - ITypeBinding resolvedType = annotation.resolveTypeBinding(); - String annotationType = resolvedType.getQualifiedName(); - if (annotationType != null && Annotations.SPRING_REQUEST_MAPPING.equals(annotationType)) { - return annotation; - } - } - } - } - - return null; - } - - private IAnnotationBinding getAnnotationFromSupertypes(Annotation node, SpringIndexerJavaContext context) { - ASTNode parent = node.getParent() != null ? node.getParent().getParent() : null; - while (parent != null && !(parent instanceof TypeDeclaration)) { - parent = parent.getParent(); - } - - if (parent != null) { - TypeDeclaration type = (TypeDeclaration) parent; - ITypeBinding typeBinding = type.resolveBinding(); - if (typeBinding != null) { - return findFirstRequestMappingAnnotation(typeBinding); - } - } - - return null; - } - - private IAnnotationBinding findFirstRequestMappingAnnotation(ITypeBinding start) { - if (start == null) { - return null; - } - - IAnnotationBinding found = getRequestMappingAnnotation(start); - if (found != null) { - return found; - } - else { - // search interfaces first - ITypeBinding[] interfaces = start.getInterfaces(); - if (interfaces != null) { - for (int i = 0; i < interfaces.length; i++) { - found = findFirstRequestMappingAnnotation(interfaces[i]); - if (found != null) { - return found; - } - } - } - - // search superclass second - ITypeBinding superclass = start.getSuperclass(); - if (superclass != null) { - return findFirstRequestMappingAnnotation(superclass); - } - - // nothing found - return null; - } - } - - private IAnnotationBinding getRequestMappingAnnotation(ITypeBinding typeBinding) { - IAnnotationBinding[] annotations = typeBinding.getAnnotations(); - for (int i = 0; i < annotations.length; i++) { - if (annotations[i].getAnnotationType() != null && Annotations.SPRING_REQUEST_MAPPING.equals(annotations[i].getAnnotationType().getQualifiedName())) { - return annotations[i]; - } - } - 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 2600b00c67..e87385ed48 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 @@ -35,9 +35,8 @@ import org.slf4j.LoggerFactory; 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.SpringIndexerJava.SCAN_PASS; import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext; -import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement; +import org.springframework.ide.vscode.commons.protocol.spring.Bean; import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -59,36 +58,34 @@ public static boolean isWebfluxRouterBean(MethodDeclaration method) { return false; } - public static SpringIndexElement[] createWebfluxElements(MethodDeclaration methodDeclaration, SpringIndexerJavaContext context, TextDocument doc, List childElements) { + public static void createWebfluxElements(Bean beanDefinition, MethodDeclaration methodDeclaration, SpringIndexerJavaContext context, TextDocument doc) { Block methodBody = methodDeclaration.getBody(); if (methodBody != null && methodBody.statements() != null && methodBody.statements().size() > 0) { - addSymbolsForRouterFunction(methodBody, context, doc, childElements); + addSymbolsForRouterFunction(beanDefinition, methodBody, context, doc); } - - return new SpringIndexElement[0]; } - public void addSymbols(MethodDeclaration methodDeclaration, SpringIndexerJavaContext context, TextDocument doc) { - Type returnType = methodDeclaration.getReturnType2(); - if (returnType != null) { - - ITypeBinding resolvedBinding = returnType.resolveBinding(); - - if (resolvedBinding != null && WebfluxUtils.ROUTER_FUNCTION_TYPE.equals(resolvedBinding.getBinaryName())) { - - Block methodBody = methodDeclaration.getBody(); - if (methodBody != null && methodBody.statements() != null && methodBody.statements().size() > 0) { - addSymbolsForRouterFunction(methodBody, context, doc, new ArrayList<>()); - } - else if (SCAN_PASS.ONE.equals(context.getPass())) { - context.getNextPassFiles().add(context.getFile()); - } - - } - } - } - - private static void addSymbolsForRouterFunction(Block methodBody, SpringIndexerJavaContext context, TextDocument doc, List indexElementsCollector) { +// public void addSymbols(MethodDeclaration methodDeclaration, SpringIndexerJavaContext context, TextDocument doc) { +// Type returnType = methodDeclaration.getReturnType2(); +// if (returnType != null) { +// +// ITypeBinding resolvedBinding = returnType.resolveBinding(); +// +// if (resolvedBinding != null && WebfluxUtils.ROUTER_FUNCTION_TYPE.equals(resolvedBinding.getBinaryName())) { +// +// Block methodBody = methodDeclaration.getBody(); +// if (methodBody != null && methodBody.statements() != null && methodBody.statements().size() > 0) { +// addSymbolsForRouterFunction(methodBody, context, doc, new ArrayList<>()); +// } +// else if (SCAN_PASS.ONE.equals(context.getPass())) { +// context.getNextPassFiles().add(context.getFile()); +// } +// +// } +// } +// } +// + private static void addSymbolsForRouterFunction(Bean beanDefinition, Block methodBody, SpringIndexerJavaContext context, TextDocument doc) { methodBody.accept(new ASTVisitor() { @Override @@ -96,7 +93,7 @@ public boolean visit(MethodInvocation node) { IMethodBinding methodBinding = node.resolveMethodBinding(); if (methodBinding != null && WebfluxUtils.isRouteMethodInvocation(methodBinding)) { - extractMappingSymbol(node, doc, context, indexElementsCollector); + extractMappingSymbol(beanDefinition, node, doc, context); } return super.visit(node); @@ -105,7 +102,7 @@ public boolean visit(MethodInvocation node) { }); } - protected static void extractMappingSymbol(MethodInvocation node, TextDocument doc, SpringIndexerJavaContext context, List indexElementsCollector) { + protected static void extractMappingSymbol(Bean beanDefinition, MethodInvocation node, TextDocument doc, SpringIndexerJavaContext context) { WebfluxRouteElement[] pathElements = extractPath(node, doc); WebfluxRouteElement[] httpMethods = extractMethods(node, doc); WebfluxRouteElement[] contentTypes = extractContentTypes(node, doc); @@ -134,8 +131,8 @@ protected static void extractMappingSymbol(MethodInvocation node, TextDocument d WebfluxHandlerMethodIndexElement handler = extractHandlerInformation(node, path, httpMethods, contentTypes, acceptTypes, location.getRange(), symbol.getName()); WebfluxRouteElementRangesIndexElement elements = extractElementsInformation(pathElements, httpMethods, contentTypes, acceptTypes); - if (handler != null) indexElementsCollector.add(handler); - if (elements != null) indexElementsCollector.add(elements); + if (handler != null) beanDefinition.addChild(handler); + if (elements != null) beanDefinition.addChild(elements); } catch (BadLocationException e) { log.error("bad location while extracting mapping symbol for " + doc.getUri(), e); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/DefaultSymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/DefaultSymbolProvider.java index d1ac07309e..97fabf74be 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/DefaultSymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/DefaultSymbolProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018 Pivotal, Inc. + * Copyright (c) 2018, 2025 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 @@ -15,6 +15,7 @@ import org.eclipse.lsp4j.SymbolKind; import org.eclipse.lsp4j.WorkspaceSymbol; import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.text.TextDocument; /** @@ -22,7 +23,7 @@ */ public class DefaultSymbolProvider { - public static WorkspaceSymbol provideDefaultSymbol(Annotation node, TextDocument doc) throws Exception { + public static WorkspaceSymbol provideDefaultSymbol(Annotation node, TextDocument doc) throws BadLocationException { WorkspaceSymbol symbol = new WorkspaceSymbol(node.toString(), SymbolKind.Interface, Either.forLeft(new Location(doc.getUri(), doc.toRange(node.getStartPosition(), node.getLength())))); return symbol; 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 7b29362793..929946e0c4 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 @@ -22,29 +22,37 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.boot.java.Annotations; -import org.springframework.ide.vscode.boot.java.beans.CachedBean; -import org.springframework.ide.vscode.boot.java.handlers.AbstractSymbolProvider; -import org.springframework.ide.vscode.commons.protocol.spring.SimpleSymbolElement; +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; /** * @author Martin Lippert */ -public class RestrictedDefaultSymbolProvider extends AbstractSymbolProvider { +public class RestrictedDefaultSymbolProvider implements SymbolProvider { private static final Logger log = LoggerFactory.getLogger(RestrictedDefaultSymbolProvider.class); @Override - protected void addSymbolsPass1(Annotation node, ITypeBinding typeBinding, - Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { + public void addSymbols(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)) { try { WorkspaceSymbol symbol = DefaultSymbolProvider.provideDefaultSymbol(node, doc); context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), symbol)); - context.getBeans().add(new CachedBean(context.getDocURI(), new SimpleSymbolElement(symbol))); - } catch (Exception e) { + +// SimpleSymbolElement symbolIndexElement = new SimpleSymbolElement(symbol); +// SpringIndexElement parentIndexElement = context.getNearestIndexElementForNode(node.getParent()); +// if (parentIndexElement != null) { +// parentIndexElement.addChild(symbolIndexElement); +// } +// else { +// context.getBeans().add(new CachedBean(context.getDocURI(), symbolIndexElement)); +// } +// context.setIndexElementForASTNode(node.getParent(), symbolIndexElement); + + } catch (BadLocationException e) { log.warn(e.getMessage()); } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringFactoriesIndexer.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringFactoriesIndexer.java index 880d70ee5d..668299ea5b 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringFactoriesIndexer.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringFactoriesIndexer.java @@ -40,7 +40,7 @@ import org.springframework.ide.vscode.boot.index.cache.IndexCache; import org.springframework.ide.vscode.boot.index.cache.IndexCacheKey; import org.springframework.ide.vscode.boot.java.beans.BeanUtils; -import org.springframework.ide.vscode.boot.java.beans.BeansSymbolProvider; +import org.springframework.ide.vscode.boot.java.beans.BeansIndexer; import org.springframework.ide.vscode.commons.java.IClasspathUtil; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.protocol.java.Classpath; @@ -142,7 +142,7 @@ private List computeSymbols(String docURI, String content) { String beanId = BeanUtils.getBeanNameFromType(simpleName); Range range = doc.toRange(new Region(pair.getOffset(), pair.getLength())); symbols.add(new WorkspaceSymbol( - BeansSymbolProvider.beanLabel(false, beanId, fqName, Paths.get(URI.create(docURI)).getFileName().toString()), + BeansIndexer.beanLabel(false, beanId, fqName, Paths.get(URI.create(docURI)).getFileName().toString()), SymbolKind.Interface, Either.forLeft(new Location(docURI, range)))); } catch (Exception e) { 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 4e10fce016..ce2d004485 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 @@ -73,7 +73,6 @@ import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector; import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem; import org.springframework.ide.vscode.commons.protocol.java.Classpath; -import org.springframework.ide.vscode.commons.protocol.spring.SimpleSymbolElement; import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement; import org.springframework.ide.vscode.commons.util.UriUtil; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -87,10 +86,6 @@ */ public class SpringIndexerJava implements SpringIndexer { - public static enum SCAN_PASS { - ONE, TWO - } - private static final Logger log = LoggerFactory.getLogger(SpringIndexerJava.class); // whenever the implementation of the indexer changes in a way that the stored data in the cache is no longer valid, @@ -308,7 +303,7 @@ public void accept(String docURI, Diagnostic diagnostic) { IProblemCollector problemCollector = problemCollectorCreator.apply(docRef, diagnosticsAggregator); SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, file, - lastModified, docRef, content, generatedSymbols, generatedBeans, problemCollector, SCAN_PASS.ONE, new ArrayList<>(), !ignoreMethodBodies); + lastModified, docRef, content, generatedSymbols, generatedBeans, problemCollector, new ArrayList<>(), !ignoreMethodBodies); scanAST(context, true); @@ -359,7 +354,7 @@ public void accept(ReconcileProblem problem) { AtomicReference docRef = new AtomicReference<>(); String file = UriUtil.toFileString(docURI); SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, file, - 0, docRef, content, generatedSymbols, generatedBeans, voidProblemCollector, SCAN_PASS.ONE, new ArrayList<>(), true); + 0, docRef, content, generatedSymbols, generatedBeans, voidProblemCollector, new ArrayList<>(), true); scanAST(context, false); @@ -396,7 +391,7 @@ public void accept(ReconcileProblem problem) { AtomicReference docRef = new AtomicReference<>(); String file = UriUtil.toFileString(docURI); SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, file, - 0, docRef, content, generatedSymbols, generatedBeans, voidProblemCollector, SCAN_PASS.ONE, new ArrayList<>(), true); + 0, docRef, content, generatedSymbols, generatedBeans, voidProblemCollector, new ArrayList<>(), true); scanAST(context, false); @@ -457,7 +452,7 @@ public void acceptAST(String sourceFilePath, CompilationUnit cu) { IProblemCollector problemCollector = problemCollectorCreator.apply(docRef, diagnosticsAggregator); SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, sourceFilePath, - lastModified, docRef, null, generatedSymbols, generatedBeans, problemCollector, SCAN_PASS.ONE, new ArrayList<>(), !ignoreMethodBodies); + lastModified, docRef, null, generatedSymbols, generatedBeans, problemCollector, new ArrayList<>(), !ignoreMethodBodies); scanAST(context, true); @@ -560,11 +555,11 @@ public void accept(String docURI, Diagnostic diagnostic) { for (int i = 0; i < chunks.size(); i++) { log.info("scan java files, AST parse, chunk {} for files: {}", i, javaFiles.length); - String[] pass2Files = scanFiles(project, annotations, chunks.get(i), generatedSymbols, generatedBeans, diagnosticsAggregator, SCAN_PASS.ONE); + String[] pass2Files = scanFiles(project, annotations, chunks.get(i), generatedSymbols, generatedBeans, diagnosticsAggregator, true); if (pass2Files.length > 0) { log.info("scan java files, AST parse, pass 2, chunk {} for files: {}", i, javaFiles.length); - scanFiles(project, annotations, pass2Files, generatedSymbols, generatedBeans, diagnosticsAggregator, SCAN_PASS.TWO); + scanFiles(project, annotations, pass2Files, generatedSymbols, generatedBeans, diagnosticsAggregator, false); } } @@ -602,13 +597,12 @@ public void accept(String docURI, Diagnostic diagnostic) { } private String[] scanFiles(IJavaProject project, AnnotationHierarchies annotations, String[] javaFiles, List generatedSymbols, List generatedBeans, - BiConsumer diagnosticsAggregator, SCAN_PASS pass) throws Exception { + BiConsumer diagnosticsAggregator, boolean ignoreMethodBodies) throws Exception { PercentageProgressTask progressTask = this.progressService.createPercentageProgressTask(INDEX_FILES_TASK_ID + project.getElementName(), javaFiles.length, "Spring Tools: Indexing Java Sources for '" + project.getElementName() + "'"); List nextPassFiles = new ArrayList<>(); - final boolean ignoreMethodBodies = SCAN_PASS.ONE.equals(pass); FileASTRequestor requestor = new FileASTRequestor() { @@ -622,7 +616,7 @@ public void acceptAST(String sourceFilePath, CompilationUnit cu) { IProblemCollector problemCollector = problemCollectorCreator.apply(docRef, diagnosticsAggregator); SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, sourceFilePath, - lastModified, docRef, null, generatedSymbols, generatedBeans, problemCollector, pass, nextPassFiles, !ignoreMethodBodies); + lastModified, docRef, null, generatedSymbols, generatedBeans, problemCollector, nextPassFiles, !ignoreMethodBodies); scanAST(context, true); progressTask.increment(); @@ -655,69 +649,95 @@ private void addEmptyDiagnostics(Map> diagnosticsByDoc, } private void scanAST(final SpringIndexerJavaContext context, boolean includeReconcile) { - context.getCu().accept(new ASTVisitor() { - - @Override - public boolean visit(TypeDeclaration node) { - try { - context.addScannedType(node.resolveBinding()); - extractSymbolInformation(node, context); - } - catch (Exception 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); - } - - @Override - public boolean visit(MethodDeclaration node) { - try { - extractSymbolInformation(node, context); - } - catch (Exception e) { - log.error("error extracting symbol information in project '" + context.getProject().getElementName() + "' - for docURI '" + context.getDocURI() + "' - on node: " + node.toString(), e); + try { + context.getCu().accept(new ASTVisitor() { + + @Override + public boolean visit(TypeDeclaration node) { + try { + context.addScannedType(node.resolveBinding()); + extractSymbolInformation(node, context); + } + catch (RequiredCompleteAstException e) { + throw e; + } + catch (Exception 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); } - return super.visit(node); - } - - @Override - public boolean visit(SingleMemberAnnotation node) { - try { - extractSymbolInformation(node, context); + + @Override + public boolean visit(MethodDeclaration node) { + try { + extractSymbolInformation(node, context); + } + catch (RequiredCompleteAstException e) { + throw e; + } + catch (Exception 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); } - catch (Exception e) { - log.error("error extracting symbol information in project '" + context.getProject().getElementName() + "' - for docURI '" + context.getDocURI() + "' - on node: " + node.toString(), e); + + @Override + public boolean visit(SingleMemberAnnotation node) { + try { + extractSymbolInformation(node, context); + } + catch (RequiredCompleteAstException e) { + throw e; + } + catch (Exception 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); } - - return super.visit(node); - } - - @Override - public boolean visit(NormalAnnotation node) { - try { - extractSymbolInformation(node, context); + + @Override + public boolean visit(NormalAnnotation node) { + try { + extractSymbolInformation(node, context); + } + catch (RequiredCompleteAstException e) { + throw e; + } + catch (Exception 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); } - catch (Exception e) { - log.error("error extracting symbol information in project '" + context.getProject().getElementName() + "' - for docURI '" + context.getDocURI() + "' - on node: " + node.toString(), e); + + @Override + public boolean visit(MarkerAnnotation node) { + try { + extractSymbolInformation(node, context); + } + catch (RequiredCompleteAstException e) { + throw e; + } + catch (Exception 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); } - - return super.visit(node); + }); + } + catch (RequiredCompleteAstException e) { + if (!context.isFullAst()) { + context.getNextPassFiles().add(context.getFile()); + context.resetDocumentRelatedElements(context.getDocURI()); } - - @Override - public boolean visit(MarkerAnnotation node) { - try { - extractSymbolInformation(node, context); - } - catch (Exception 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); + else { + log.error("Complete AST required but it is complete already. Analyzing ", context.getDocURI()); } - }); - + } + if (includeReconcile) { reconcile(context); } @@ -754,12 +774,12 @@ public void accept(ReconcileProblem problem) { reconciler.reconcile(context.getProject(), URI.create(context.getDocURI()), context.getCu(), problemCollector, context.isFullAst()); problemCollector.endCollecting(); } catch (RequiredCompleteAstException e) { - if (context.getPass() == SCAN_PASS.TWO) { - problemCollector.endCollecting(); - log.error("Complete AST required but it is complete already. Parsing ", context.getDocURI()); - } else { + if (!context.isFullAst()) { // Let problems be found in the next pass, don't add the problems to the aggregate problems collector to not duplicate them with the next pass context.getNextPassFiles().add(context.getFile()); + } else { + problemCollector.endCollecting(); + log.error("Complete AST required but it is complete already. Parsing ", context.getDocURI()); } } } @@ -814,7 +834,16 @@ private void extractSymbolInformation(Annotation node, final SpringIndexerJavaCo WorkspaceSymbol symbol = provideDefaultSymbol(node, context); if (symbol != null) { context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), symbol)); - context.getBeans().add(new CachedBean(context.getDocURI(), new SimpleSymbolElement(symbol))); + +// SimpleSymbolElement symbolIndexElement = new SimpleSymbolElement(symbol); +// SpringIndexElement parentIndexElement = context.getNearestIndexElementForNode(node.getParent()); +// if (parentIndexElement != null) { +// parentIndexElement.addChild(symbolIndexElement); +// } +// else { +// context.getBeans().add(new CachedBean(context.getDocURI(), symbolIndexElement)); +// } +// context.setIndexElementForASTNode(node.getParent(), symbolIndexElement); } } @@ -837,6 +866,9 @@ private WorkspaceSymbol provideDefaultSymbol(Annotation node, final SpringIndexe } } } + catch (RequiredCompleteAstException e) { + throw e; + } catch (Exception e) { log.error("error creating default symbol in project '" + context.getProject().getElementName() + "' - for docURI '" + context.getDocURI() + "' - on node: " + node.toString(), e); } 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 index 99d426bbb7..5b326111b5 100644 --- 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2023 Pivotal, Inc. + * Copyright (c) 2017, 2025 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -11,6 +11,7 @@ package org.springframework.ide.vscode.boot.java.utils; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; @@ -18,7 +19,6 @@ import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.ITypeBinding; import org.springframework.ide.vscode.boot.java.beans.CachedBean; -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.languageserver.reconcile.IProblemCollector; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -38,14 +38,12 @@ public class SpringIndexerJavaContext { private final List generatedSymbols; private final List beans; private final IProblemCollector getProblemCollector; - private final SCAN_PASS pass; private final List nextPassFiles; private final boolean fullAst; private final Set dependencies = new HashSet<>(); private final Set scannedTypes = new HashSet<>(); - - + public SpringIndexerJavaContext( IJavaProject project, CompilationUnit cu, @@ -57,7 +55,6 @@ public SpringIndexerJavaContext( List generatedSymbols, List beans, IProblemCollector problemCollector, - SCAN_PASS pass, List nextPassFiles, boolean fullAst ) { @@ -72,7 +69,6 @@ public SpringIndexerJavaContext( this.generatedSymbols = generatedSymbols; this.getProblemCollector = problemCollector; this.beans = beans; - this.pass = pass; this.nextPassFiles = nextPassFiles; this.fullAst = fullAst; } @@ -113,10 +109,6 @@ public List getBeans() { return beans; } - public SCAN_PASS getPass() { - return pass; - } - public List getNextPassFiles() { return nextPassFiles; } @@ -154,4 +146,22 @@ public IProblemCollector getProblemCollector() { public boolean isFullAst() { return fullAst; } + + public void resetDocumentRelatedElements(String docURI) { + Iterator beansIterator = beans.iterator(); + while (beansIterator.hasNext()) { + if (beansIterator.next().getDocURI().equals(docURI)) { + beansIterator.remove(); + } + } + + Iterator symbolsIterator = generatedSymbols.iterator(); + while (symbolsIterator.hasNext()) { + if (symbolsIterator.next().getDocURI().equals(docURI)) { + symbolsIterator.remove(); + } + } + + } + } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringIndexStructureTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringIndexStructureTest.java new file mode 100644 index 0000000000..74f5628640 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringIndexStructureTest.java @@ -0,0 +1,148 @@ +/******************************************************************************* + * Copyright (c) 2025 Broadcom + * 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: + * Broadcom - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.index.test; + +import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; + +import java.io.File; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.lsp4j.TextDocumentIdentifier; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Import; +import org.springframework.ide.vscode.boot.app.SpringSymbolIndex; +import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; +import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf; +import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex; +import org.springframework.ide.vscode.boot.java.requestmapping.RequestMappingIndexElement; +import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; +import org.springframework.ide.vscode.commons.protocol.spring.Bean; +import org.springframework.ide.vscode.commons.protocol.spring.DocumentElement; +import org.springframework.ide.vscode.commons.protocol.spring.SimpleSymbolElement; +import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement; +import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness; +import org.springframework.ide.vscode.project.harness.ProjectsHarness; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * @author Martin Lippert + */ +@ExtendWith(SpringExtension.class) +@BootLanguageServerTest +@Import(SymbolProviderTestConf.class) +public class SpringIndexStructureTest { + + @Autowired private BootLanguageServerHarness harness; + @Autowired private JavaProjectFinder projectFinder; + @Autowired private SpringMetamodelIndex springIndex; + @Autowired private SpringSymbolIndex indexer; + + private static final String PROJECT_NAME = "test-spring-index-structure"; + + private File directory; + + @BeforeEach + public void setup() throws Exception { + harness.intialize(null); + + directory = new File(ProjectsHarness.class.getResource("/test-projects/" + PROJECT_NAME + "/").toURI()); + + String projectDir = directory.toURI().toString(); + + // trigger project creation + projectFinder.find(new TextDocumentIdentifier(projectDir)).get(); + + CompletableFuture initProject = indexer.waitOperation(); + initProject.get(55555555, TimeUnit.SECONDS); + } + + @Test + @Disabled + void testStructureMainClass() { + String docUri = directory.toPath().resolve("src/main/java/org/test/MainClass.java").toUri().toString(); + + /* + * @SpringBootApplication (mainClass) - bean node + * - @Value("server.port") - default node + * - @Bean (bean1) - bean node + */ + + // document node + DocumentElement document = springIndex.getDocument(docUri); + List docChildren = document.getChildren(); + assertEquals(1, docChildren.size()); + + // mainClass - bean node + Bean[] mainClassBean = springIndex.getBeansWithName(PROJECT_NAME, "mainClass"); + assertEquals(1, mainClassBean.length); + assertEquals("mainClass", mainClassBean[0].getName()); + assertSame(mainClassBean[0], docChildren.get(0)); + + // mainClass children + List mainChildren = mainClassBean[0].getChildren(); + assertEquals(2, mainChildren.size()); + + SpringIndexElement valueFieldNode = mainChildren.get(0); + SpringIndexElement beanMethodNode = mainChildren.get(1); + + assertTrue(valueFieldNode instanceof SimpleSymbolElement); + assertTrue(beanMethodNode instanceof Bean); + + SimpleSymbolElement valueField = (SimpleSymbolElement) valueFieldNode; + Bean beanMethod = (Bean) beanMethodNode; + + assertEquals("bean1", beanMethod.getName()); + assertEquals("@Value(\"server.port\")", valueField.getDocumentSymbol().getName()); + } + + @Test + void testStructureRestController() { + String docUri = directory.toPath().resolve("src/main/java/org/test/RestControllerExample.java").toUri().toString(); + + /* + * @RestController (mainClass) - bean node + * - /owners/find -- GET -> @GetMapping("/owners/find") + * - /owners/new -- POST -> @PostMapping("/owners/new") + */ + + // document node + DocumentElement document = springIndex.getDocument(docUri); + List docChildren = document.getChildren(); + assertEquals(1, docChildren.size()); + + // rest controller node + Bean[] mainClassBean = springIndex.getBeansWithName(PROJECT_NAME, "restControllerExample"); + assertEquals(1, mainClassBean.length); + assertEquals("restControllerExample", mainClassBean[0].getName()); + assertSame(mainClassBean[0], docChildren.get(0)); + + // rest controller children + List mainChildren = mainClassBean[0].getChildren(); + assertEquals(3, mainChildren.size()); + + RequestMappingIndexElement getMappingNode = (RequestMappingIndexElement) mainChildren.get(0); + RequestMappingIndexElement postMappingNode = (RequestMappingIndexElement) mainChildren.get(1); + RequestMappingIndexElement genericMappingNode = (RequestMappingIndexElement) mainChildren.get(2); + + assertEquals("/owners/find", getMappingNode.getPath()); + assertEquals("/owners/new", postMappingNode.getPath()); + assertEquals("/owners/{ownerId}/edit", genericMappingNode.getPath()); + } + +} diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/events/test/SpringIndexerEventsTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/events/test/SpringIndexerEventsTest.java index e5938f88ef..d98f486fcc 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/events/test/SpringIndexerEventsTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/events/test/SpringIndexerEventsTest.java @@ -190,10 +190,10 @@ void testEventPublisherIndexElements() throws Exception { Bean[] beans = springIndex.getBeansOfDocument(docUri); assertEquals(1, beans.length); - Bean listenerComponentBean = Arrays.stream(beans).filter(bean -> bean.getName().equals("customEventPublisher")).findFirst().get(); - assertEquals("com.example.events.demo.CustomEventPublisher", listenerComponentBean.getType()); + Bean publisherComponentBean = Arrays.stream(beans).filter(bean -> bean.getName().equals("customEventPublisher")).findFirst().get(); + assertEquals("com.example.events.demo.CustomEventPublisher", publisherComponentBean.getType()); - List children = listenerComponentBean.getChildren(); + List children = publisherComponentBean.getChildren(); assertEquals(1, children.size()); assertTrue(children.get(0) instanceof EventPublisherIndexElement); @@ -226,4 +226,15 @@ void testEventPublisherWithEventTypeHierarchyIndexElements() throws Exception { assertFalse(eventTypesFromHierarchy.contains("java.lang.String")); } + @Test + void testEventPublisherWithMoreSymbols() throws Exception { + String docUri = directory.toPath().resolve("src/main/java/com/example/events/demo/CustomEventPublisherWithAdditionalElements.java").toUri().toString(); + + SpringIndexerHarness.assertDocumentSymbols(indexer, docUri, + SpringIndexerHarness.symbol("@Qualifier(\"qualifier\")", "@Qualifier(\"qualifier\")"), + SpringIndexerHarness.symbol("@Component", "@+ 'customEventPublisherWithAdditionalElements' (@Component) CustomEventPublisherWithAdditionalElements"), + SpringIndexerHarness.symbol("this.publisher.publishEvent(new CustomEvent())", "@EventPublisher (CustomEvent)") + ); + } + } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingDependentConstantChangedTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingDependentConstantChangedTest.java index 44c5c0becf..39819a23f4 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingDependentConstantChangedTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingDependentConstantChangedTest.java @@ -85,7 +85,7 @@ void testSimpleRequestMappingSymbolFromConstantInDifferentClass() throws Excepti String docUri = directory.resolve("src/main/java/org/test/SimpleMappingClassWithConstantInDifferentClass.java").toUri().toString(); String constantsUri = directory.resolve("src/main/java/org/test/Constants.java").toUri().toString(); List symbols = indexer.getSymbols(docUri); - assertEquals(1, symbols.size()); + assertEquals(2, symbols.size()); assertSymbol(docUri, "@/path/from/constant", "@RequestMapping(Constants.REQUEST_MAPPING_PATH)"); TestFileScanListener fileScanListener = new TestFileScanListener(); @@ -99,7 +99,7 @@ void testSimpleRequestMappingSymbolFromConstantInDifferentClass() throws Excepti fileScanListener.assertScannedUri(docUri, 1); symbols = indexer.getSymbols(docUri); - assertSymbolCount(1, symbols); + assertSymbolCount(2, symbols); assertSymbol(docUri, "@/changed-path", "@RequestMapping(Constants.REQUEST_MAPPING_PATH)"); } @@ -108,7 +108,7 @@ void testSimpleRequestMappingSymbolFromConstantInDifferentClassViaMultipleFilesU String docUri = directory.resolve("src/main/java/org/test/SimpleMappingClassWithConstantInDifferentClass.java").toUri().toString(); String constantsUri = directory.resolve("src/main/java/org/test/Constants.java").toUri().toString(); List symbols = indexer.getSymbols(docUri); - assertEquals(1, symbols.size()); + assertEquals(2, symbols.size()); assertSymbol(docUri, "@/path/from/constant", "@RequestMapping(Constants.REQUEST_MAPPING_PATH)"); TestFileScanListener fileScanListener = new TestFileScanListener(); @@ -122,7 +122,7 @@ void testSimpleRequestMappingSymbolFromConstantInDifferentClassViaMultipleFilesU fileScanListener.assertScannedUri(docUri, 1); symbols = indexer.getSymbols(docUri); - assertSymbolCount(1, symbols); + assertSymbolCount(2, symbols); assertSymbol(docUri, "@/changed-path", "@RequestMapping(Constants.REQUEST_MAPPING_PATH)"); } @@ -132,14 +132,14 @@ void testRequestMappingSymbolFromConstantChained() throws Exception { String chainConstantsUri_2 = directory.resolve("src/main/java/org/test/ChainElement2.java").toUri().toString(); List symbols = indexer.getSymbols(docUri); - assertEquals(1, symbols.size()); + assertEquals(2, symbols.size()); assertSymbol(docUri, "@/path/from/chain", "@RequestMapping(ChainElement1.MAPPING_PATH_1)"); replaceInFile(chainConstantsUri_2, "path/from/chain", "/changed-path"); indexer.updateDocument(chainConstantsUri_2, null, "triggered by test code").get(); symbols = indexer.getSymbols(docUri); - assertSymbolCount(1, symbols); + assertSymbolCount(2, symbols); assertSymbol(docUri, "@/path/from/chain", "@RequestMapping(ChainElement1.MAPPING_PATH_1)"); // You would expect here that the symbol got updated from "path/from/chain" to the changed value "/changed-path", @@ -159,12 +159,12 @@ void testCyclicalDependency() throws Exception { { List symbols = indexer.getSymbols(pingUri); - assertSymbolCount(1, symbols); + assertSymbolCount(2, symbols); assertSymbol(pingUri, "@/pong -- GET", "@GetMapping(PongConstantRequestMapping.PONG)"); } { List symbols = indexer.getSymbols(pongUri); - assertSymbolCount(1, symbols); + assertSymbolCount(2, symbols); assertSymbol(pongUri, "@/ping -- GET", "@GetMapping(PingConstantRequestMapping.PING)"); } @@ -173,12 +173,12 @@ void testCyclicalDependency() throws Exception { { List symbols = indexer.getSymbols(pingUri); - assertSymbolCount(1, symbols); + assertSymbolCount(2, symbols); assertSymbol(pingUri, "@/pong -- GET", "@GetMapping(PongConstantRequestMapping.PONG)"); } { List symbols = indexer.getSymbols(pongUri); - assertSymbolCount(1, symbols); + assertSymbolCount(2, symbols); assertSymbol(pongUri, "@/changed -- GET", "@GetMapping(PingConstantRequestMapping.PING)"); } } @@ -192,12 +192,12 @@ void testCyclicalDependencyViaMultipleFilesUpdate() throws Exception { { List symbols = indexer.getSymbols(pingUri); - assertSymbolCount(1, symbols); + assertSymbolCount(2, symbols); assertSymbol(pingUri, "@/pong -- GET", "@GetMapping(PongConstantRequestMapping.PONG)"); } { List symbols = indexer.getSymbols(pongUri); - assertSymbolCount(1, symbols); + assertSymbolCount(2, symbols); assertSymbol(pongUri, "@/ping -- GET", "@GetMapping(PingConstantRequestMapping.PING)"); } @@ -206,12 +206,12 @@ void testCyclicalDependencyViaMultipleFilesUpdate() throws Exception { { List symbols = indexer.getSymbols(pingUri); - assertSymbolCount(1, symbols); + assertSymbolCount(2, symbols); assertSymbol(pingUri, "@/pong -- GET", "@GetMapping(PongConstantRequestMapping.PONG)"); } { List symbols = indexer.getSymbols(pongUri); - assertSymbolCount(1, symbols); + assertSymbolCount(2, symbols); assertSymbol(pongUri, "@/changed -- GET", "@GetMapping(PingConstantRequestMapping.PING)"); } } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingSymbolProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingSymbolProviderTest.java index 6d72468ddd..8ae8aad7d4 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingSymbolProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingSymbolProviderTest.java @@ -79,11 +79,15 @@ public void setup() throws Exception { CompletableFuture initProject = indexer.waitOperation(); initProject.get(5, TimeUnit.SECONDS); } + + private List getSymbols(String docUri) { + return indexer.getWorkspaceSymbolsFromSymbolIndex(docUri); + } @Test void testSimpleRequestMappingSymbol() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/SimpleMappingClass.java").toUri().toString(); - List symbols = indexer.getSymbols(docUri); + List symbols = getSymbols(docUri); assertEquals(2, symbols.size()); assertTrue(containsSymbol(symbols, "@/greeting", docUri, 8, 1, 8, 29)); } @@ -110,9 +114,9 @@ void testRequestMappingIndexElements() throws Exception { void testSimpleRequestMappingSymbolFromConstantInDifferentClass() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/SimpleMappingClassWithConstantInDifferentClass.java").toUri().toString(); String constantsUri = directory.toPath().resolve("src/main/java/org/test/Constants.java").toUri().toString(); - List symbols = indexer.getSymbols(docUri); - assertEquals(1, symbols.size()); - assertTrue(containsSymbol(symbols, "@/path/from/constant", docUri, 6, 1, 6, 48)); + List symbols = getSymbols(docUri); + assertEquals(2, symbols.size()); + assertTrue(containsSymbol(symbols, "@/path/from/constant", docUri, 8, 1, 8, 48)); //Verify whether dependency tracker logics works properly for this example. SpringIndexerJavaDependencyTracker dt = indexer.getJavaIndexer().getDependencyTracker(); @@ -135,9 +139,9 @@ void testSimpleRequestMappingSymbolFromConstantInDifferentClass() throws Excepti void testUpdateDocumentWithConstantFromDifferentClass() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/SimpleMappingClassWithConstantInDifferentClass.java").toUri().toString(); String constantsUri = directory.toPath().resolve("src/main/java/org/test/Constants.java").toUri().toString(); - List symbols = indexer.getSymbols(docUri); - assertEquals(1, symbols.size()); - assertTrue(containsSymbol(symbols, "@/path/from/constant", docUri, 6, 1, 6, 48)); + List symbols = getSymbols(docUri); + assertEquals(2, symbols.size()); + assertTrue(containsSymbol(symbols, "@/path/from/constant", docUri, 8, 1, 8, 48)); //Verify whether dependency tracker logics works properly for this example. SpringIndexerJavaDependencyTracker dt = indexer.getJavaIndexer().getDependencyTracker(); @@ -190,9 +194,9 @@ void testCyclicalRequestMappingDependency() throws Exception { @Test void testSimpleRequestMappingSymbolFromConstantInSameClass() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/SimpleMappingClassWithConstantInSameClass.java").toUri().toString(); - List symbols = indexer.getSymbols(docUri); - assertEquals(1, symbols.size()); - assertTrue(containsSymbol(symbols, "@/request/mapping/path/from/same/class/constant", docUri, 8, 1, 8, 52)); + List symbols = getSymbols(docUri); + assertEquals(2, symbols.size()); + assertTrue(containsSymbol(symbols, "@/request/mapping/path/from/same/class/constant", docUri, 10, 1, 10, 52)); SpringIndexerJavaDependencyTracker dt = indexer.getJavaIndexer().getDependencyTracker(); assertEquals(ImmutableSet.of(), dt.getAllDependencies().get(UriUtil.toFileString(docUri))); @@ -201,9 +205,9 @@ void testSimpleRequestMappingSymbolFromConstantInSameClass() throws Exception { @Test void testSimpleRequestMappingSymbolFromConstantInBinaryType() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/SimpleMappingClassWithConstantFromBinaryType.java").toUri().toString(); - List symbols = indexer.getSymbols(docUri); - assertEquals(1, symbols.size()); - assertTrue(containsSymbol(symbols, "@/(inferred)", docUri, 7, 1, 7, 53)); + List symbols = getSymbols(docUri); + assertEquals(2, symbols.size()); + assertTrue(containsSymbol(symbols, "@/(inferred)", docUri, 9, 1, 9, 53)); SpringIndexerJavaDependencyTracker dt = indexer.getJavaIndexer().getDependencyTracker(); assertEquals(ImmutableSet.of(), dt.getAllDependencies().get(UriUtil.toFileString(docUri))); @@ -212,79 +216,79 @@ void testSimpleRequestMappingSymbolFromConstantInBinaryType() throws Exception { @Test void testParentRequestMappingSymbol() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/ParentMappingClass.java").toUri().toString(); - List symbols = indexer.getSymbols(docUri); - assertEquals(1, symbols.size()); - assertTrue(containsSymbol(symbols, "@/parent/greeting -- GET", docUri, 8, 1, 8, 51)); + List symbols = getSymbols(docUri); + assertEquals(2, symbols.size()); + assertTrue(containsSymbol(symbols, "@/parent/greeting -- GET", docUri, 11, 1, 11, 51)); } @Test void testEmptyPathWithParentRequestMappingSymbol() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/ParentMappingClass2.java").toUri().toString(); - List symbols = indexer.getSymbols(docUri); - assertEquals(1, symbols.size()); - assertTrue(containsSymbol(symbols, "@/parent2 -- GET,POST,DELETE", docUri, 11, 1, 11, 16)); + List symbols = getSymbols(docUri); + assertEquals(2, symbols.size()); + assertTrue(containsSymbol(symbols, "@/parent2 -- GET,POST,DELETE", docUri, 13, 1, 13, 16)); } @Test void testParentRequestMappingSymbolWithPathAttribute() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/ParentMappingClass3.java").toUri().toString(); - List symbols = indexer.getSymbols(docUri); - assertEquals(1, symbols.size()); - assertTrue(containsSymbol(symbols, "@/parent3/greeting -- GET", docUri, 8, 1, 8, 51)); + List symbols = getSymbols(docUri); + assertEquals(2, symbols.size()); + assertTrue(containsSymbol(symbols, "@/parent3/greeting -- GET", docUri, 11, 1, 11, 51)); } @Test void testMappingPathFromSuperclass() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/inheritance/SubclassWithMappingFromParent.java").toUri().toString(); - List symbols = indexer.getSymbols(docUri); - assertEquals(1, symbols.size()); - assertTrue(containsSymbol(symbols, "@/superclasspath/", docUri, 6, 1, 6, 21)); + List symbols = getSymbols(docUri); + assertEquals(2, symbols.size()); + assertTrue(containsSymbol(symbols, "@/superclasspath/", docUri, 8, 1, 8, 21)); } @Test void testMappingPathFromSuperclassWithConstant() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/inheritance/SubclassWithMappingFromParentWithConstant.java").toUri().toString(); - List symbols = indexer.getSymbols(docUri); - assertEquals(1, symbols.size()); - assertTrue(containsSymbol(symbols, "@/path/from/constant/", docUri, 6, 1, 6, 21)); + List symbols = getSymbols(docUri); + assertEquals(2, symbols.size()); + assertTrue(containsSymbol(symbols, "@/path/from/constant/", docUri, 8, 1, 8, 21)); } @Test void testMappingPathFromSuperclassWithStringConcatenation() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/inheritance/SubclassWithMappingFromParentWithStringConcatenation.java").toUri().toString(); - List symbols = indexer.getSymbols(docUri); - assertEquals(1, symbols.size()); - assertTrue(containsSymbol(symbols, "@/superpath/subclass", docUri, 6, 1, 6, 34)); + List symbols = getSymbols(docUri); + assertEquals(2, symbols.size()); + assertTrue(containsSymbol(symbols, "@/superpath/subclass", docUri, 8, 1, 8, 34)); } @Test void testMappingPathFromSuperclassWithStringConcatenationPerAttribute() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/inheritance/SubclassWithMappingFromParentWithStringConcatenationPerAttribute.java").toUri().toString(); - List symbols = indexer.getSymbols(docUri); - assertEquals(1, symbols.size()); - assertTrue(containsSymbol(symbols, "@/superpath/subclass", docUri, 6, 1, 6, 42)); + List symbols = getSymbols(docUri); + assertEquals(2, symbols.size()); + assertTrue(containsSymbol(symbols, "@/superpath/subclass", docUri, 8, 1, 8, 42)); } @Test void testMappingPathFromSuperclassWithMethodsAndPathAttribute() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/inheritance/SubclassWithMappingFromParentWithMethods.java").toUri().toString(); - List symbols = indexer.getSymbols(docUri); - assertEquals(1, symbols.size()); - assertTrue(containsSymbol(symbols, "@/superclasspath -- POST,PUT - Accept: testconsume - Content-Type: text/plain", docUri, 6, 1, 6, 16)); + List symbols = getSymbols(docUri); + assertEquals(2, symbols.size()); + assertTrue(containsSymbol(symbols, "@/superclasspath -- POST,PUT - Accept: testconsume - Content-Type: text/plain", docUri, 8, 1, 8, 16)); } @Test void testMappingPathFromMultiLevelClassHierarchy() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/inheritance/SuperControllerLevel4.java").toUri().toString(); - List symbols = indexer.getSymbols(docUri); - assertEquals(1, symbols.size()); - assertTrue(containsSymbol(symbols, "@/path-level2/final-subclass-path", docUri, 6, 1, 6, 39)); + List symbols = getSymbols(docUri); + assertEquals(2, symbols.size()); + assertTrue(containsSymbol(symbols, "@/path-level2/final-subclass-path", docUri, 8, 1, 8, 39)); } @Test void testMappingPathFromSuperInterfaceEvenIfSuperclassContainsMappingPath() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/inheritance/ControllerAsSubclassAndInterfaceHierarchy.java").toUri().toString(); - List symbols = indexer.getSymbols(docUri); + List symbols = getSymbols(docUri); assertEquals(2, symbols.size()); assertTrue(containsSymbol(symbols, "@/superinterface-path/last-path-segment -- GET - Accept: testconsume - Content-Type: text/plain", docUri, 8, 1, 8, 33)); } @@ -317,101 +321,101 @@ void testMapoingIndexElementsWithDetails() throws Exception { @Test void testMultiRequestMappingSymbol() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/MultiRequestMappingClass.java").toUri().toString(); - List symbols = indexer.getSymbols(docUri); - assertEquals(2, symbols.size()); - assertTrue(containsSymbol(symbols, "@/hello1", docUri, 6, 1, 6, 44)); - assertTrue(containsSymbol(symbols, "@/hello2", docUri, 6, 1, 6, 44)); + List symbols = getSymbols(docUri); + assertEquals(3, symbols.size()); + assertTrue(containsSymbol(symbols, "@/hello1", docUri, 8, 1, 8, 44)); + assertTrue(containsSymbol(symbols, "@/hello2", docUri, 8, 1, 8, 44)); } @Test void testGetMappingSymbol() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/RequestMethodClass.java").toUri().toString(); - List symbols = indexer.getSymbols(docUri); - assertTrue(containsSymbol(symbols, "@/getData -- GET", docUri, 13, 1, 13, 24)); + List symbols = getSymbols(docUri); + assertTrue(containsSymbol(symbols, "@/getData -- GET", docUri, 16, 1, 16, 24)); } @Test void testGetMappingSymbolWithoutPath() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/RequestMethodClass.java").toUri().toString(); - List symbols = indexer.getSymbols(docUri); - assertTrue(containsSymbol(symbols, "@/ -- GET", docUri, 41, 1, 41, 16)); + List symbols = getSymbols(docUri); + assertTrue(containsSymbol(symbols, "@/ -- GET", docUri, 44, 1, 44, 16)); } @Test void testGetMappingSymbolWithoutAnything() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/RequestMethodClass.java").toUri().toString(); - List symbols = indexer.getSymbols(docUri); - assertTrue(containsSymbol(symbols, "@/ -- GET", docUri, 45, 1, 45, 14)); + List symbols = getSymbols(docUri); + assertTrue(containsSymbol(symbols, "@/ -- GET", docUri, 48, 1, 48, 14)); } @Test void testDeleteMappingSymbol() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/RequestMethodClass.java").toUri().toString(); - List symbols = indexer.getSymbols(docUri); - assertTrue(containsSymbol(symbols, "@/deleteData -- DELETE", docUri, 21, 1, 21, 30)); + List symbols = getSymbols(docUri); + assertTrue(containsSymbol(symbols, "@/deleteData -- DELETE", docUri, 24, 1, 24, 30)); } @Test void testPostMappingSymbol() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/RequestMethodClass.java").toUri().toString(); - List symbols = indexer.getSymbols(docUri); - assertTrue(containsSymbol(symbols, "@/postData -- POST", docUri, 25, 1, 25, 26)); + List symbols = getSymbols(docUri); + assertTrue(containsSymbol(symbols, "@/postData -- POST", docUri, 28, 1, 28, 26)); } @Test void testPutMappingSymbol() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/RequestMethodClass.java").toUri().toString(); - List symbols = indexer.getSymbols(docUri); - assertTrue(containsSymbol(symbols, "@/putData -- PUT", docUri, 17, 1, 17, 24)); + List symbols = getSymbols(docUri); + assertTrue(containsSymbol(symbols, "@/putData -- PUT", docUri, 20, 1, 20, 24)); } @Test void testPatchMappingSymbol() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/RequestMethodClass.java").toUri().toString(); - List symbols = indexer.getSymbols(docUri); - assertTrue(containsSymbol(symbols, "@/patchData -- PATCH", docUri, 29, 1, 29, 28)); + List symbols = getSymbols(docUri); + assertTrue(containsSymbol(symbols, "@/patchData -- PATCH", docUri, 32, 1, 32, 28)); } @Test void testGetRequestMappingSymbol() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/RequestMethodClass.java").toUri().toString(); - List symbols = indexer.getSymbols(docUri); - assertTrue(containsSymbol(symbols, "@/getHello -- GET", docUri, 33, 1, 33, 61)); + List symbols = getSymbols(docUri); + assertTrue(containsSymbol(symbols, "@/getHello -- GET", docUri, 36, 1, 36, 61)); } @Test void testMultiRequestMethodMappingSymbol() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/RequestMethodClass.java").toUri().toString(); - List symbols = indexer.getSymbols(docUri); - assertTrue(containsSymbol(symbols, "@/postAndPutHello -- POST,PUT", docUri, 37, 1, 37, 76)); + List symbols = getSymbols(docUri); + assertTrue(containsSymbol(symbols, "@/postAndPutHello -- POST,PUT", docUri, 40, 1, 40, 76)); } @Test void testMediaTypes() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/RequestMappingMediaTypes.java").toUri().toString(); - List symbols = indexer.getSymbols(docUri); - assertEquals(7, symbols.size()); - assertTrue(containsSymbol(symbols, "@/consume1 -- HEAD - Accept: testconsume", docUri, 8, 1, 8, 90)); - assertTrue(containsSymbol(symbols, "@/consume2 - Accept: text/plain", docUri, 13, 1, 13, 73)); - assertTrue(containsSymbol(symbols, "@/consume3 - Accept: text/plain,testconsumetype", docUri, 18, 1, 18, 94)); - assertTrue(containsSymbol(symbols, "@/produce1 - Content-Type: testproduce", docUri, 23, 1, 23, 60)); - assertTrue(containsSymbol(symbols, "@/produce2 - Content-Type: text/plain", docUri, 28, 1, 28, 73)); - assertTrue(containsSymbol(symbols, "@/produce3 - Content-Type: text/plain,testproducetype", docUri, 33, 1, 33, 94)); - assertTrue(containsSymbol(symbols, "@/everything - Accept: application/json,text/plain,testconsume - Content-Type: application/json", docUri, 38, 1, 38, 170)); + List symbols = getSymbols(docUri); + assertEquals(8, symbols.size()); + assertTrue(containsSymbol(symbols, "@/consume1 -- HEAD - Accept: testconsume", docUri, 10, 1, 10, 90)); + assertTrue(containsSymbol(symbols, "@/consume2 - Accept: text/plain", docUri, 15, 1, 15, 73)); + assertTrue(containsSymbol(symbols, "@/consume3 - Accept: text/plain,testconsumetype", docUri, 20, 1, 20, 94)); + assertTrue(containsSymbol(symbols, "@/produce1 - Content-Type: testproduce", docUri, 25, 1, 25, 60)); + assertTrue(containsSymbol(symbols, "@/produce2 - Content-Type: text/plain", docUri, 30, 1, 30, 73)); + assertTrue(containsSymbol(symbols, "@/produce3 - Content-Type: text/plain,testproducetype", docUri, 35, 1, 35, 94)); + assertTrue(containsSymbol(symbols, "@/everything - Accept: application/json,text/plain,testconsume - Content-Type: application/json", docUri, 40, 1, 40, 170)); } @Test void testPathWithConcatenatedString() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/MappingsWithConcatenatedStrings.java").toUri().toString(); - List symbols = indexer.getSymbols(docUri); - assertTrue(containsSymbol(symbols, "@/path1/path2 -- GET", docUri, 13, 1, 13, 33)); + List symbols = getSymbols(docUri); + assertTrue(containsSymbol(symbols, "@/path1/path2 -- GET", docUri, 16, 1, 16, 33)); } @Test void testPathWithConcatenatedStringAndConstantInvolved() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/MappingsWithConcatenatedStrings.java").toUri().toString(); - List symbols = indexer.getSymbols(docUri); - assertTrue(containsSymbol(symbols, "@/path1/path/from/constant -- GET", docUri, 17, 1, 17, 56)); + List symbols = getSymbols(docUri); + assertTrue(containsSymbol(symbols, "@/path1/path/from/constant -- GET", docUri, 20, 1, 20, 56)); } private boolean containsSymbol(List symbols, String name, String uri, int startLine, int startCHaracter, int endLine, int endCharacter) { diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-events-indexing/src/main/java/com/example/events/demo/CustomEventPublisherWithAdditionalElements.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-events-indexing/src/main/java/com/example/events/demo/CustomEventPublisherWithAdditionalElements.java new file mode 100644 index 0000000000..1cb04bfd0d --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-events-indexing/src/main/java/com/example/events/demo/CustomEventPublisherWithAdditionalElements.java @@ -0,0 +1,21 @@ +package com.example.events.demo; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Component; + +@Qualifier("qualifier") +@Component +public class CustomEventPublisherWithAdditionalElements { + + private ApplicationEventPublisher publisher; + + public CustomEventPublisherWithAdditionalElements(ApplicationEventPublisher publisher) { + this.publisher = publisher; + } + + public void foo() { + this.publisher.publishEvent(new CustomEvent()); + } + +} diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/ChainedRequestMappingPathOverMultipleClasses.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/ChainedRequestMappingPathOverMultipleClasses.java index a69992b598..87e9b2e550 100644 --- a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/ChainedRequestMappingPathOverMultipleClasses.java +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/ChainedRequestMappingPathOverMultipleClasses.java @@ -1,7 +1,9 @@ package org.test; +import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; +@Controller public class ChainedRequestMappingPathOverMultipleClasses { @RequestMapping(ChainElement1.MAPPING_PATH_1) diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/MappingsWithConcatenatedStrings.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/MappingsWithConcatenatedStrings.java index b98e1d07d9..930e0c8c3a 100644 --- a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/MappingsWithConcatenatedStrings.java +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/MappingsWithConcatenatedStrings.java @@ -9,6 +9,9 @@ import org.springframework.web.bind.annotation.RequestMethod; import static org.springframework.web.bind.annotation.RequestMethod.PUT; +import org.springframework.stereotype.Controller; + +@Controller public class MappingsWithConcatenatedStrings { @GetMapping("/path1" + "/path2") diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/MultiRequestMappingClass.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/MultiRequestMappingClass.java index dcf605ffe2..c0bd91dce6 100644 --- a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/MultiRequestMappingClass.java +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/MultiRequestMappingClass.java @@ -1,7 +1,9 @@ package org.test; +import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; +@Controller public class MultiRequestMappingClass { @RequestMapping(path= {"/hello1","hello2"}) diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/ParentMappingClass.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/ParentMappingClass.java index 9db23cde01..b41e50770b 100644 --- a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/ParentMappingClass.java +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/ParentMappingClass.java @@ -3,6 +3,9 @@ import org.springframework.web.bind.annotation.RequestMapping; import static org.springframework.web.bind.annotation.RequestMethod.*; +import org.springframework.stereotype.Controller; + +@Controller @RequestMapping(value = "parent", method = {GET,POST,DELETE}) public class ParentMappingClass { diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/ParentMappingClass2.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/ParentMappingClass2.java index 36058394f7..6d0ce5d608 100644 --- a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/ParentMappingClass2.java +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/ParentMappingClass2.java @@ -4,8 +4,10 @@ import static org.springframework.web.bind.annotation.RequestMethod.GET; import static org.springframework.web.bind.annotation.RequestMethod.POST; +import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; +@Controller @RequestMapping(value = "parent2", method = {GET,POST,DELETE}) public class ParentMappingClass2 { diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/ParentMappingClass3.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/ParentMappingClass3.java index 94551d1fff..14ff57a701 100644 --- a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/ParentMappingClass3.java +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/ParentMappingClass3.java @@ -3,6 +3,9 @@ import org.springframework.web.bind.annotation.RequestMapping; import static org.springframework.web.bind.annotation.RequestMethod.*; +import org.springframework.stereotype.Controller; + +@Controller @RequestMapping(path = "parent3", method = {GET,POST,DELETE}) public class ParentMappingClass3 { diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/PingConstantRequestMapping.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/PingConstantRequestMapping.java index 692f9836ef..67cff28885 100644 --- a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/PingConstantRequestMapping.java +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/PingConstantRequestMapping.java @@ -1,7 +1,9 @@ package org.test; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +@RestController public class PingConstantRequestMapping { public final static String PING = "/ping"; diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/PongConstantRequestMapping.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/PongConstantRequestMapping.java index 92dce76216..e2dbb78685 100644 --- a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/PongConstantRequestMapping.java +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/PongConstantRequestMapping.java @@ -1,7 +1,9 @@ package org.test; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +@RestController public class PongConstantRequestMapping { public static final String PONG="/pong"; diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/RequestMappingMediaTypes.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/RequestMappingMediaTypes.java index 60a29bd366..cde3ce7c57 100644 --- a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/RequestMappingMediaTypes.java +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/RequestMappingMediaTypes.java @@ -1,9 +1,11 @@ package org.test; import org.springframework.http.MediaType; +import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +@Controller public class RequestMappingMediaTypes { @RequestMapping(path="/consume1", consumes = "testconsume", method= {RequestMethod.HEAD}) diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/RequestMethodClass.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/RequestMethodClass.java index 27de8543d9..878e5e1df7 100644 --- a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/RequestMethodClass.java +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/RequestMethodClass.java @@ -9,6 +9,9 @@ import org.springframework.web.bind.annotation.RequestMethod; import static org.springframework.web.bind.annotation.RequestMethod.PUT; +import org.springframework.stereotype.Controller; + +@Controller public class RequestMethodClass { @GetMapping("/getData") diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/SimpleMappingClassWithConstantFromBinaryType.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/SimpleMappingClassWithConstantFromBinaryType.java index 70e6da2968..bf0ee3e9dd 100644 --- a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/SimpleMappingClassWithConstantFromBinaryType.java +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/SimpleMappingClassWithConstantFromBinaryType.java @@ -2,7 +2,9 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.stereotype.Controller; +@Controller public class SimpleMappingClassWithConstantFromBinaryType { @RequestMapping(AbstractBeanDefinition.INFER_METHOD) diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/SimpleMappingClassWithConstantInDifferentClass.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/SimpleMappingClassWithConstantInDifferentClass.java index 069ccc3cd5..6fa5715b68 100644 --- a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/SimpleMappingClassWithConstantInDifferentClass.java +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/SimpleMappingClassWithConstantInDifferentClass.java @@ -1,7 +1,9 @@ package org.test; +import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; +@Controller public class SimpleMappingClassWithConstantInDifferentClass { @RequestMapping(Constants.REQUEST_MAPPING_PATH) diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/SimpleMappingClassWithConstantInSameClass.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/SimpleMappingClassWithConstantInSameClass.java index 9fd2a968fa..15d41bdb69 100644 --- a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/SimpleMappingClassWithConstantInSameClass.java +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/SimpleMappingClassWithConstantInSameClass.java @@ -1,7 +1,9 @@ package org.test; +import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; +@Controller public class SimpleMappingClassWithConstantInSameClass { private static final String REQUEST_MAPPING_PATH_IN_SAME_CLASS = "/request/mapping/path/from/same/class/constant"; diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/inheritance/SubclassWithMappingFromParent.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/inheritance/SubclassWithMappingFromParent.java index f5553362a2..ce163f2924 100644 --- a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/inheritance/SubclassWithMappingFromParent.java +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/inheritance/SubclassWithMappingFromParent.java @@ -1,7 +1,9 @@ package org.test.inheritance; +import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; +@Controller public class SubclassWithMappingFromParent extends SuperclassWithMappingPath { @RequestMapping("/") diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/inheritance/SubclassWithMappingFromParentWithConstant.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/inheritance/SubclassWithMappingFromParentWithConstant.java index e748c5ed05..19d4f277eb 100644 --- a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/inheritance/SubclassWithMappingFromParentWithConstant.java +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/inheritance/SubclassWithMappingFromParentWithConstant.java @@ -1,7 +1,9 @@ package org.test.inheritance; +import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; +@Controller public class SubclassWithMappingFromParentWithConstant extends SuperclassWithMappingPathFromConstant { @RequestMapping("/") diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/inheritance/SubclassWithMappingFromParentWithMethods.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/inheritance/SubclassWithMappingFromParentWithMethods.java index 01f69c44c6..be8e7665ca 100644 --- a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/inheritance/SubclassWithMappingFromParentWithMethods.java +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/inheritance/SubclassWithMappingFromParentWithMethods.java @@ -1,7 +1,9 @@ package org.test.inheritance; +import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; +@Controller public class SubclassWithMappingFromParentWithMethods extends SuperclassWithMappingPathAndMethods { @RequestMapping diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/inheritance/SubclassWithMappingFromParentWithStringConcatenation.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/inheritance/SubclassWithMappingFromParentWithStringConcatenation.java index 8b6724bcbd..7be4ef3d34 100644 --- a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/inheritance/SubclassWithMappingFromParentWithStringConcatenation.java +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/inheritance/SubclassWithMappingFromParentWithStringConcatenation.java @@ -1,7 +1,9 @@ package org.test.inheritance; +import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; +@Controller public class SubclassWithMappingFromParentWithStringConcatenation extends SuperclassWithMappingPathWithStringConcatenation { @RequestMapping("/sub" + "class") diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/inheritance/SubclassWithMappingFromParentWithStringConcatenationPerAttribute.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/inheritance/SubclassWithMappingFromParentWithStringConcatenationPerAttribute.java index fa7afc6843..df8d267607 100644 --- a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/inheritance/SubclassWithMappingFromParentWithStringConcatenationPerAttribute.java +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/inheritance/SubclassWithMappingFromParentWithStringConcatenationPerAttribute.java @@ -1,7 +1,9 @@ package org.test.inheritance; +import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; +@Controller public class SubclassWithMappingFromParentWithStringConcatenationPerAttribute extends SuperclassWithMappingPathWithStringConcatenationPerAttribute { @RequestMapping(value = "/sub" + "class") diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/inheritance/SuperControllerLevel4.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/inheritance/SuperControllerLevel4.java index 284fec30d8..191b83f1ac 100644 --- a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/inheritance/SuperControllerLevel4.java +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/inheritance/SuperControllerLevel4.java @@ -1,7 +1,9 @@ package org.test.inheritance; +import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; +@Controller public class SuperControllerLevel4 extends SuperControllerLevel3 { @RequestMapping("final-subclass-path") diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-index-structure/.mvn/wrapper/maven-wrapper.properties b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-index-structure/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000000..654af46a70 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-index-structure/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-index-structure/mvnw b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-index-structure/mvnw new file mode 100755 index 0000000000..d7c358e5a2 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-index-structure/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-index-structure/mvnw.cmd b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-index-structure/mvnw.cmd new file mode 100644 index 0000000000..6f779cff20 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-index-structure/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-index-structure/pom.xml b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-index-structure/pom.xml new file mode 100644 index 0000000000..cc29a6b8c5 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-index-structure/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + com.example + test-spring-index-structure + 0.0.1-SNAPSHOT + jar + + + org.springframework.boot + spring-boot-starter-parent + 3.4.3 + + + + + 5.0.0 + UTF-8 + UTF-8 + 17 + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-test + test + + + + diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-index-structure/src/main/java/org/test/BeanClass1.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-index-structure/src/main/java/org/test/BeanClass1.java new file mode 100644 index 0000000000..4e4f76fb55 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-index-structure/src/main/java/org/test/BeanClass1.java @@ -0,0 +1,5 @@ +package org.test; + +public class BeanClass1 { + +} diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-index-structure/src/main/java/org/test/BeanClass2.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-index-structure/src/main/java/org/test/BeanClass2.java new file mode 100644 index 0000000000..45b3c3cba3 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-index-structure/src/main/java/org/test/BeanClass2.java @@ -0,0 +1,5 @@ +package org.test; + +public class BeanClass2 { + +} diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-index-structure/src/main/java/org/test/MainClass.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-index-structure/src/main/java/org/test/MainClass.java new file mode 100644 index 0000000000..d788945cdc --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-index-structure/src/main/java/org/test/MainClass.java @@ -0,0 +1,23 @@ +package org.test; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +@SpringBootApplication +public class MainClass { + + @Value("server.port") + private String serverport; + + public static void main(String[] args) throws Exception { + SpringApplication.run(MainClass.class, args); + } + + @Bean + BeanClass1 bean1() { + return new BeanClass1(); + } + +} diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-index-structure/src/main/java/org/test/RestControllerExample.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-index-structure/src/main/java/org/test/RestControllerExample.java new file mode 100644 index 0000000000..2198c6deb5 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-index-structure/src/main/java/org/test/RestControllerExample.java @@ -0,0 +1,54 @@ +package org.test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.InitBinder; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; + +import jakarta.validation.Valid; + +@RestController +public class RestControllerExample { + + @Autowired(required = false) + private String serverPort; + + @InitBinder + public void setAllowedFields(WebDataBinder dataBinder) { + dataBinder.setDisallowedFields("id"); + } + + @ModelAttribute("owner") + public String findOwner(@PathVariable(required = false) Integer ownerId) { + return "example"; + } + + @GetMapping("/owners/find") + public String initFindForm() { + return "owners/findOwners"; + } + + @PostMapping("/owners/new") + public String processCreationForm(String owner, BindingResult result, RedirectAttributes redirectAttributes) { + if (result.hasErrors()) { + redirectAttributes.addFlashAttribute("error", "There was an error in creating the owner."); + return "something"; + } + + return "redirect:/owners/"; + } + + @RequestMapping(path = "/owners/{ownerId}/edit", method = RequestMethod.POST) + public String processUpdateOwnerForm(@Valid String owner, BindingResult result, @PathVariable("ownerId") int ownerId) { + return "redirect:/owners/{ownerId}"; + } + +}