From c2b50b6eb66038bb372d97cf3fde7db5d448907e Mon Sep 17 00:00:00 2001 From: Martin Lippert Date: Thu, 27 Mar 2025 11:57:30 +0100 Subject: [PATCH] added bean method container to index for bean methods without a configuration class around --- .../spring/BeanMethodContainerElement.java | 44 +++++++++++++++ .../vscode/boot/java/beans/BeansIndexer.java | 5 +- .../java/beans/ComponentSymbolProvider.java | 55 +++++++++++++------ .../test/SpringIndexViaLSPMethodTest.java | 4 +- .../test/SpringMetamodelIndexerBeansTest.java | 31 +++++++++++ .../test/SpringMetamodelIndexingTest.java | 2 +- .../test/BeanMethodsWithoutConfiguration.java | 17 ++++++ 7 files changed, 137 insertions(+), 21 deletions(-) create mode 100644 headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/BeanMethodContainerElement.java create mode 100644 headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-indexing/src/main/java/org/test/BeanMethodsWithoutConfiguration.java diff --git a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/BeanMethodContainerElement.java b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/BeanMethodContainerElement.java new file mode 100644 index 000000000..4e762e946 --- /dev/null +++ b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/spring/BeanMethodContainerElement.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * 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.commons.protocol.spring; + +import org.eclipse.lsp4j.Location; + +/** + * This index element is meant to capture bean elements from @Bean annotated + * methods where the containing class is not a configuration class (e.g. Feign config + * classes) + * + * This container element is meant for internal use and not to be displayed + * on the UI, therefore this is not a symbol element. + * + * @author Martin Lippert + */ +public class BeanMethodContainerElement extends AbstractSpringIndexElement { + + private final Location location; + private final String type; + + public BeanMethodContainerElement(Location location, String type) { + super(); + this.location = location; + this.type = type; + } + + public Location getLocation() { + return location; + } + + public String getType() { + return type; + } + +} 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 index 3860c2103..5d3597497 100644 --- 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 @@ -35,6 +35,7 @@ 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; @@ -49,7 +50,7 @@ public class BeansIndexer { private static final Logger log = LoggerFactory.getLogger(BeansIndexer.class); - public static void indexBeanMethod(Bean configBean, Annotation node, SpringIndexerJavaContext context, TextDocument doc) { + public static void indexBeanMethod(SpringIndexElement parentNode, Annotation node, SpringIndexerJavaContext context, TextDocument doc) { if (node == null) return; ASTNode parent = node.getParent(); @@ -89,7 +90,7 @@ public class BeansIndexer { WebfluxRouterSymbolProvider.createWebfluxElements(beanDefinition, method, context, doc); } - configBean.addChild(beanDefinition); + parentNode.addChild(beanDefinition); } catch (BadLocationException e) { log.error("", e); 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 0c8a1c3ed..2f0dfac82 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 @@ -52,6 +52,7 @@ import org.springframework.ide.vscode.boot.java.utils.DefaultSymbolProvider; 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.BeanMethodContainerElement; import org.springframework.ide.vscode.commons.protocol.spring.BeanRegistrarElement; import org.springframework.ide.vscode.commons.protocol.spring.DefaultValues; import org.springframework.ide.vscode.commons.protocol.spring.InjectionPoint; @@ -201,28 +202,49 @@ public class ComponentSymbolProvider implements SymbolProvider { } } - private void indexBeanMethods(Bean bean, TypeDeclaration type, ITypeBinding annotationType, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { + private void indexBeanMethods(final 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) { + + SpringIndexElement parent = bean; + + if (bean == null) { + try { + + Location location = new Location(doc.getUri(), doc.toRange(type.getName().getStartPosition(), type.getName().getLength())); + String typeName = type.resolveBinding().getQualifiedName(); + parent = new BeanMethodContainerElement(location, typeName); + + } catch (BadLocationException e) { + log.error("error while looking up position for type: " + type.toString(), e); 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); - } + } + else if (!bean.isConfiguration()) { + return; + } + + 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(parent, annotation, context, doc); } } } + + if (bean == null && parent.getChildren().size() > 0) { + context.getBeans().add(new CachedBean(context.getDocURI(), parent)); + } } private void indexEventListeners(Bean bean, TypeDeclaration type, ITypeBinding annotationType, Collection metaAnnotations, SpringIndexerJavaContext context, TextDocument doc) { @@ -329,6 +351,7 @@ public class ComponentSymbolProvider implements SymbolProvider { if (!isComponment) { indexEventListenerInterfaceImplementation(null, typeDeclaration, context, doc); indexBeanRegistrarImplementation(null, typeDeclaration, context, doc); + indexBeanMethods(null, typeDeclaration, null, null, context, doc); } } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringIndexViaLSPMethodTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringIndexViaLSPMethodTest.java index 71092f8b0..ea1df7338 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringIndexViaLSPMethodTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringIndexViaLSPMethodTest.java @@ -84,7 +84,7 @@ public class SpringIndexViaLSPMethodTest { List beans = result.get(5, TimeUnit.SECONDS); assertNotNull(beans); - assertEquals(26, beans.size()); + assertEquals(SpringMetamodelIndexingTest.NO_OF_EXPECTED_BEANS, beans.size()); } @Test @@ -98,7 +98,7 @@ public class SpringIndexViaLSPMethodTest { List beans = result.get(5, TimeUnit.SECONDS); assertNotNull(beans); - assertEquals(25, beans.size()); + assertEquals(SpringMetamodelIndexingTest.NO_OF_EXPECTED_BEANS - 1, beans.size()); } @Test diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringMetamodelIndexerBeansTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringMetamodelIndexerBeansTest.java index 1ef7b3ac0..ffd98eeed 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringMetamodelIndexerBeansTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringMetamodelIndexerBeansTest.java @@ -40,9 +40,12 @@ import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFin import org.springframework.ide.vscode.commons.protocol.spring.AnnotationAttributeValue; 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.BeanMethodContainerElement; import org.springframework.ide.vscode.commons.protocol.spring.DefaultValues; +import org.springframework.ide.vscode.commons.protocol.spring.DocumentElement; import org.springframework.ide.vscode.commons.protocol.spring.InjectionPoint; import org.springframework.ide.vscode.commons.protocol.spring.SpringIndexElement; +import org.springframework.ide.vscode.commons.protocol.spring.SymbolElement; import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness; import org.springframework.ide.vscode.project.harness.ProjectsHarness; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -544,6 +547,34 @@ public class SpringMetamodelIndexerBeansTest { assertEquals("prof2", profileAttributeValues[1].getName()); assertEquals(new Location(beans[0].getLocation().getUri(), new Range(new Position(9, 19), new Position(9, 26))), profileAttributeValues[1].getLocation()); } + + @Test + void testBeanMethodsWithoutConfiguration() { + String docUri = directory.toPath().resolve("src/main/java/org/test/BeanMethodsWithoutConfiguration.java").toUri().toString(); + + DocumentElement document = springIndex.getDocument(docUri); + List docChildren = document.getChildren(); + assertEquals(1, docChildren.size()); + assertTrue(docChildren.get(0) instanceof BeanMethodContainerElement); + + BeanMethodContainerElement container = (BeanMethodContainerElement) docChildren.get(0); + assertEquals(docUri, container.getLocation().getUri()); + assertEquals("org.test.BeanMethodsWithoutConfiguration", container.getType()); + assertFalse(container instanceof SymbolElement); + + List beans = container.getChildren(); + assertEquals(2, beans.size()); + + Bean bean1 = (Bean) beans.get(0); + Bean bean2 = (Bean) beans.get(1); + + assertEquals("beanWithoutConfig", bean1.getName()); + assertEquals("beanWithoutConfig2", bean2.getName()); + + Bean[] beansFound = springIndex.getBeansWithName("test-spring-indexing", "beanWithoutConfig"); + assertEquals(1, beansFound.length); + assertSame(bean1, beansFound[0]); + } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringMetamodelIndexingTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringMetamodelIndexingTest.java index 002e17d4b..4d27ed661 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringMetamodelIndexingTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/index/test/SpringMetamodelIndexingTest.java @@ -46,7 +46,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; @Import(SymbolProviderTestConf.class) public class SpringMetamodelIndexingTest { - private static final int NO_OF_EXPECTED_BEANS = 26; + public static final int NO_OF_EXPECTED_BEANS = 28; @Autowired private BootLanguageServerHarness harness; @Autowired private JavaProjectFinder projectFinder; diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-indexing/src/main/java/org/test/BeanMethodsWithoutConfiguration.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-indexing/src/main/java/org/test/BeanMethodsWithoutConfiguration.java new file mode 100644 index 000000000..0fe1a8569 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-indexing/src/main/java/org/test/BeanMethodsWithoutConfiguration.java @@ -0,0 +1,17 @@ +package org.test; + +import org.springframework.context.annotation.Bean; + +public class BeanMethodsWithoutConfiguration { + + @Bean + public BeanClass1 beanWithoutConfig() { + return new BeanClass1(); + } + + @Bean + public BeanClass2 beanWithoutConfig2() { + return new BeanClass2(); + } + +}