From 67789b730e1937d6a9d9c26622090387d5820e78 Mon Sep 17 00:00:00 2001 From: Martin Lippert Date: Tue, 3 Jun 2025 11:23:42 +0200 Subject: [PATCH] GH-1572: do not treat functional beans different anymore Fixes GH-1572 --- .../vscode/boot/java/beans/BeansIndexer.java | 27 ++-------- .../boot/java/beans/BeansSymbolProvider.java | 48 +----------------- .../java/beans/ComponentSymbolProvider.java | 12 ++--- ...ConfigurationPropertiesSymbolProvider.java | 2 +- .../java/utils/SpringFactoriesIndexer.java | 2 +- .../test/SpringIndexerFunctionBeansTest.java | 49 ++++++++----------- .../ScannedFunctionClassWithAnnotation.java | 15 ++++++ 7 files changed, 48 insertions(+), 107 deletions(-) create mode 100644 headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-indexing-beans/src/main/java/org/test/ScannedFunctionClassWithAnnotation.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 index 34cddedbe..296257d47 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 @@ -20,8 +20,6 @@ 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; @@ -29,7 +27,6 @@ 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; @@ -65,7 +62,6 @@ public class BeansIndexer { } } - boolean isFunction = isFunctionBean(method); ITypeBinding beanType = getBeanType(method); String markerString = getAnnotations(method); @@ -73,7 +69,7 @@ public class BeansIndexer { try { Location location = new Location(doc.getUri(), doc.toRange(nameAndRegion.getT2())); - String beanLabel = beanLabel(isFunction, nameAndRegion.getT1(), beanType.getName(), "@Bean" + markerString); + String beanLabel = beanLabel(nameAndRegion.getT1(), beanType.getName(), "@Bean" + markerString); InjectionPoint[] injectionPoints = ASTUtils.findInjectionPoints(method, doc); @@ -96,10 +92,10 @@ public class BeansIndexer { } } - public static String beanLabel(boolean isFunctionBean, String beanName, String beanType, String markerString) { + public static String beanLabel(String beanName, String beanType, String markerString) { StringBuilder symbolLabel = new StringBuilder(); symbolLabel.append('@'); - symbolLabel.append(isFunctionBean ? '>' : '+'); + symbolLabel.append('+'); symbolLabel.append(' '); symbolLabel.append('\''); symbolLabel.append(beanName); @@ -116,23 +112,6 @@ public class BeansIndexer { 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(); 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 342a76095..004642fab 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 @@ -11,13 +11,11 @@ package org.springframework.ide.vscode.boot.java.beans; import java.util.Collection; -import java.util.Set; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MethodDeclaration; -import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.SymbolKind; import org.eclipse.lsp4j.WorkspaceSymbol; @@ -25,13 +23,8 @@ import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider; -import org.springframework.ide.vscode.boot.java.utils.ASTUtils; import org.springframework.ide.vscode.boot.java.utils.CachedSymbol; -import org.springframework.ide.vscode.boot.java.utils.FunctionUtils; import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext; -import org.springframework.ide.vscode.commons.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; @@ -56,8 +49,6 @@ public class BeansSymbolProvider implements SymbolProvider { MethodDeclaration method = (MethodDeclaration) parent; if (BeansIndexer.isMethodAbstract(method)) return; - boolean isFunction = BeansIndexer.isFunctionBean(method); - ITypeBinding beanType = BeansIndexer.getBeanType(method); String markerString = BeansIndexer.getAnnotations(method); @@ -66,7 +57,7 @@ public class BeansSymbolProvider implements SymbolProvider { Location location = new Location(doc.getUri(), doc.toRange(nameAndRegion.getT2())); WorkspaceSymbol symbol = new WorkspaceSymbol( - BeansIndexer.beanLabel(isFunction, nameAndRegion.getT1(), beanType.getName(), "@Bean" + markerString), + BeansIndexer.beanLabel(nameAndRegion.getT1(), beanType.getName(), "@Bean" + markerString), SymbolKind.Interface, Either.forLeft(location) ); @@ -79,41 +70,4 @@ public class BeansSymbolProvider implements SymbolProvider { } } - @Override - public void addSymbols(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) { - indexFunctionBeans(typeDeclaration, context, doc); - } - - private void indexFunctionBeans(TypeDeclaration typeDeclaration, SpringIndexerJavaContext context, TextDocument doc) { - ITypeBinding functionBean = FunctionUtils.getFunctionBean(typeDeclaration, doc); - if (functionBean != null) { - try { - String beanName = BeanUtils.getBeanName(typeDeclaration); - ITypeBinding beanType = functionBean; - Location beanLocation = new Location(doc.getUri(), doc.toRange(ASTUtils.nodeRegion(doc, typeDeclaration.getName()))); - - WorkspaceSymbol symbol = new WorkspaceSymbol( - BeansIndexer.beanLabel(true, beanName, beanType.getName(), null), - SymbolKind.Interface, - Either.forLeft(beanLocation)); - - context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), symbol)); - - ITypeBinding concreteBeanType = typeDeclaration.resolveBinding(); - Set supertypes = ASTUtils.findSupertypes(concreteBeanType); - - Collection annotationsOnTypeDeclaration = ASTUtils.getAnnotations(typeDeclaration); - AnnotationMetadata[] annotations = ASTUtils.getAnnotationsMetadata(annotationsOnTypeDeclaration, doc); - - InjectionPoint[] injectionPoints = ASTUtils.findInjectionPoints(typeDeclaration, doc); - - Bean beanDefinition = new Bean(beanName, concreteBeanType.getQualifiedName(), beanLocation, injectionPoints, supertypes, annotations, false, symbol.getName()); - context.getBeans().add(new CachedBean(context.getDocURI(), 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 f7dea414f..9363565b9 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 @@ -104,7 +104,7 @@ public class ComponentSymbolProvider implements SymbolProvider { Location location = new Location(doc.getUri(), doc.toRange(node.getStartPosition(), node.getLength())); WorkspaceSymbol symbol = new WorkspaceSymbol( - beanLabel("+", annotationTypeName, metaAnnotationNames, beanName, beanType.getName()), SymbolKind.Interface, + beanLabel(annotationTypeName, metaAnnotationNames, beanName, beanType.getName()), SymbolKind.Interface, Either.forLeft(location)); boolean isConfiguration = Annotations.CONFIGURATION.equals(annotationType.getQualifiedName()) @@ -166,7 +166,7 @@ public class ComponentSymbolProvider implements SymbolProvider { Location location = new Location(doc.getUri(), doc.toRange(node.getStartPosition(), node.getLength())); WorkspaceSymbol symbol = new WorkspaceSymbol( - beanLabel("+", annotationTypeName, metaAnnotationNames, beanName, beanType.getName()), SymbolKind.Interface, + beanLabel(annotationTypeName, metaAnnotationNames, beanName, beanType.getName()), SymbolKind.Interface, Either.forLeft(location)); boolean isConfiguration = Annotations.CONFIGURATION.equals(annotationType.getQualifiedName()) @@ -583,7 +583,7 @@ public class ComponentSymbolProvider implements SymbolProvider { Location location = new Location(doc.getUri(), doc.toRange(node.getStartPosition(), node.getLength())); WorkspaceSymbol symbol = new WorkspaceSymbol( - beanLabel("+", null, null, beanName, beanType), + beanLabel(null, null, beanName, beanType), SymbolKind.Class, Either.forLeft(location)); context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), symbol)); @@ -597,10 +597,10 @@ public class ComponentSymbolProvider implements SymbolProvider { parentNode.addChild(bean); } - public static String beanLabel(String searchPrefix, String annotationTypeName, Collection metaAnnotationNames, String beanName, String beanType) { + public static String beanLabel(String annotationTypeName, Collection metaAnnotationNames, String beanName, String beanType) { StringBuilder symbolLabel = new StringBuilder(); - symbolLabel.append("@"); - symbolLabel.append(searchPrefix); + symbolLabel.append('@'); + symbolLabel.append('+'); symbolLabel.append(' '); symbolLabel.append('\''); symbolLabel.append(beanName); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ConfigurationPropertiesSymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ConfigurationPropertiesSymbolProvider.java index 71f9b9f97..7c1627122 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ConfigurationPropertiesSymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ConfigurationPropertiesSymbolProvider.java @@ -88,7 +88,7 @@ public class ConfigurationPropertiesSymbolProvider implements SymbolProvider { Location location = new Location(doc.getUri(), doc.toRange(node.getStartPosition(), node.getLength())); WorkspaceSymbol symbol = new WorkspaceSymbol( - ComponentSymbolProvider.beanLabel("+", annotationTypeName, metaAnnotationNames, beanName, typeBinding.getName()), SymbolKind.Interface, + ComponentSymbolProvider.beanLabel(annotationTypeName, metaAnnotationNames, beanName, typeBinding.getName()), SymbolKind.Interface, Either.forLeft(location)); boolean isConfiguration = false; // otherwise, the ComponentSymbolProvider takes care of the bean definiton for this type 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 f7f75b7ac..6e07da852 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 @@ -154,7 +154,7 @@ public class SpringFactoriesIndexer implements SpringIndexer { Location location = new Location(docURI, range); WorkspaceSymbol symbol = new WorkspaceSymbol( - BeansIndexer.beanLabel(false, beanId, fqName, Paths.get(URI.create(docURI)).getFileName().toString()), + BeansIndexer.beanLabel(beanId, fqName, Paths.get(URI.create(docURI)).getFileName().toString()), SymbolKind.Interface, Either.forLeft(location)); diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerFunctionBeansTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerFunctionBeansTest.java index abdfd9347..98aedcec6 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerFunctionBeansTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerFunctionBeansTest.java @@ -68,7 +68,7 @@ public class SpringIndexerFunctionBeansTest { String docUri = directory.toPath().resolve("src/main/java/org/test/FunctionClass.java").toUri().toString(); SpringIndexerHarness.assertDocumentSymbols(indexer, docUri, SpringIndexerHarness.symbol("@Configuration", "@+ 'functionClass' (@Configuration <: @Component) FunctionClass"), - SpringIndexerHarness.symbol("@Bean", "@> 'uppercase' (@Bean) Function") + SpringIndexerHarness.symbol("@Bean", "@+ 'uppercase' (@Bean) Function") ); Bean[] beans = springIndex.getBeansOfDocument(docUri); @@ -84,64 +84,57 @@ public class SpringIndexerFunctionBeansTest { @Test void testScanSimpleFunctionClass() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/ScannedFunctionClass.java").toUri().toString(); + + SpringIndexerHarness.assertDocumentSymbols(indexer, docUri); + assertEquals(0, springIndex.getBeansOfDocument(docUri).length); + } + + @Test + void testScanSimpleFunctionClassWithComponentAnnotation() throws Exception { + String docUri = directory.toPath().resolve("src/main/java/org/test/ScannedFunctionClassWithAnnotation.java").toUri().toString(); + SpringIndexerHarness.assertDocumentSymbols(indexer, docUri, - SpringIndexerHarness.symbol("ScannedFunctionClass", "@> 'scannedFunctionClass' Function") + SpringIndexerHarness.symbol("@Component", "@+ 'scannedFunctionClassWithAnnotation' (@Component) ScannedFunctionClassWithAnnotation") ); Bean[] beans = springIndex.getBeansOfDocument(docUri); assertEquals(1, beans.length); - Bean functionClassBean = Arrays.stream(beans).filter(bean -> bean.getName().equals("scannedFunctionClass")).findFirst().get(); + Bean functionClassBean = Arrays.stream(beans).filter(bean -> bean.getName().equals("scannedFunctionClassWithAnnotation")).findFirst().get(); - assertEquals("org.test.ScannedFunctionClass", functionClassBean.getType()); + assertEquals("org.test.ScannedFunctionClassWithAnnotation", functionClassBean.getType()); } @Test void testScanSpecializedFunctionClass() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/FunctionFromSpecializedClass.java").toUri().toString(); - SpringIndexerHarness.assertDocumentSymbols(indexer, docUri, - SpringIndexerHarness.symbol("FunctionFromSpecializedClass", "@> 'functionFromSpecializedClass' Function") - ); - Bean[] beans = springIndex.getBeansOfDocument(docUri); - assertEquals(1, beans.length); - - Bean functionClassBean = Arrays.stream(beans).filter(bean -> bean.getName().equals("functionFromSpecializedClass")).findFirst().get(); - - assertEquals("org.test.FunctionFromSpecializedClass", functionClassBean.getType()); + SpringIndexerHarness.assertDocumentSymbols(indexer, docUri); + assertEquals(0, springIndex.getBeansOfDocument(docUri).length); } @Test void testScanSpecializedFunctionInterface() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/FunctionFromSpecializedInterface.java").toUri().toString(); - SpringIndexerHarness.assertDocumentSymbols(indexer, docUri, - SpringIndexerHarness.symbol("FunctionFromSpecializedInterface", "@> 'functionFromSpecializedInterface' Function") - ); - Bean[] beans = springIndex.getBeansOfDocument(docUri); - assertEquals(1, beans.length); - - Bean functionClassBean = Arrays.stream(beans).filter(bean -> bean.getName().equals("functionFromSpecializedInterface")).findFirst().get(); - - assertEquals("org.test.FunctionFromSpecializedInterface", functionClassBean.getType()); + SpringIndexerHarness.assertDocumentSymbols(indexer, docUri); + assertEquals(0, springIndex.getBeansOfDocument(docUri).length); } @Test void testNoSymbolForAbstractClasses() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/SpecializedFunctionClass.java").toUri().toString(); + SpringIndexerHarness.assertDocumentSymbols(indexer, docUri); - - Bean[] beans = springIndex.getBeansOfDocument(docUri); - assertEquals(0, beans.length); + assertEquals(0, springIndex.getBeansOfDocument(docUri).length); } @Test void testNoSymbolForSubInterfaces() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/SpecializedFunctionInterface.java").toUri().toString(); - SpringIndexerHarness.assertDocumentSymbols(indexer, docUri); - Bean[] beans = springIndex.getBeansOfDocument(docUri); - assertEquals(0, beans.length); + SpringIndexerHarness.assertDocumentSymbols(indexer, docUri); + assertEquals(0, springIndex.getBeansOfDocument(docUri).length); } } diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-indexing-beans/src/main/java/org/test/ScannedFunctionClassWithAnnotation.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-indexing-beans/src/main/java/org/test/ScannedFunctionClassWithAnnotation.java new file mode 100644 index 000000000..e6b5e25ac --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-indexing-beans/src/main/java/org/test/ScannedFunctionClassWithAnnotation.java @@ -0,0 +1,15 @@ +package org.test; + +import java.util.function.Function; + +import org.springframework.stereotype.Component; + +@Component +public class ScannedFunctionClassWithAnnotation implements Function { + + @Override + public String apply(String t) { + return t.toUpperCase(); + } + +}