From 54d7ee49f3afef0a7632fff8346b349d26caaf21 Mon Sep 17 00:00:00 2001 From: Martin Lippert Date: Thu, 10 Oct 2024 15:15:07 +0200 Subject: [PATCH] GH-1381: support Go To Definition for ConditionalOnMissingBean, focus in name attribute, and support value arrays --- .../ConditionalOnBeanDefinitionProvider.java | 57 ++++++- ...nditionalOnBeanDefinitionProviderTest.java | 161 +++++++++++++++++- 2 files changed, 207 insertions(+), 11 deletions(-) diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ConditionalOnBeanDefinitionProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ConditionalOnBeanDefinitionProvider.java index 23f09a1b3..76da16f74 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ConditionalOnBeanDefinitionProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ConditionalOnBeanDefinitionProvider.java @@ -19,6 +19,8 @@ import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.IAnnotationBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.MemberValuePair; import org.eclipse.jdt.core.dom.StringLiteral; import org.eclipse.lsp4j.LocationLink; import org.eclipse.lsp4j.TextDocumentIdentifier; @@ -51,19 +53,62 @@ public class ConditionalOnBeanDefinitionProvider implements IJavaDefinitionProvi if (parent != null && parent instanceof Annotation) { Annotation a = (Annotation) parent; IAnnotationBinding binding = a.resolveAnnotationBinding(); - if (binding != null && binding.getAnnotationType() != null && Annotations.CONDITIONAL_ON_BEAN.equals(binding.getAnnotationType().getQualifiedName())) { - String beanName = valueNode.getLiteralValue(); - if (beanName != null && beanName.length() > 0) { - return findBeansWithName(project, beanName); - } + if (binding != null) { + ITypeBinding annotationType = binding.getAnnotationType(); + if (annotationType != null) { + String annotationTypeQualifiedName = annotationType.getQualifiedName(); + + if (Annotations.CONDITIONAL_ON_BEAN.equals(annotationTypeQualifiedName) + || Annotations.CONDITIONAL_ON_MISSING_BEAN.equals(annotationTypeQualifiedName)) { + + return getDefinitions(project, valueNode); + } + } } } } return Collections.emptyList(); } - private List findBeansWithName(IJavaProject project, String beanName) { + private List getDefinitions(IJavaProject project, StringLiteral valueNode) { + String value = valueNode.getLiteralValue(); + + if (value != null && value.length() > 0) { + return getDefinitionsForValue(project, valueNode, value); + } + else { + return Collections.emptyList(); + } + } + + private List getDefinitionsForValue(IJavaProject project, StringLiteral valueNode, String value) { + ASTNode parent = valueNode.getParent(); + if (parent != null && !(parent instanceof MemberValuePair)) { + parent = parent.getParent(); + } + + if (parent != null && parent instanceof MemberValuePair) { + MemberValuePair pair = (MemberValuePair) parent; + String name = pair.getName().toString(); + + if ("name".equals(name)) { + return findBeansWithName(project, value); + } + else if ("type".equals(name) ||"ignoredType".equals(name)) { + return findBeanTypesWithName(project, value); + } + } + + return Collections.emptyList(); + } + + private List findBeanTypesWithName(IJavaProject project, String value) { + // TODO + return Collections.emptyList(); + } + + private List findBeansWithName(IJavaProject project, String beanName) { Bean[] beans = this.springIndex.getBeansWithName(project.getElementName(), beanName); return Arrays.stream(beans) diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/conditionalonbean/test/ConditionalOnBeanDefinitionProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/conditionalonbean/test/ConditionalOnBeanDefinitionProviderTest.java index 7c1c39f59..8fbc40148 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/conditionalonbean/test/ConditionalOnBeanDefinitionProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/conditionalonbean/test/ConditionalOnBeanDefinitionProviderTest.java @@ -79,24 +79,109 @@ public class ConditionalOnBeanDefinitionProviderTest { @Configuration public class TestConditionalOnBeanCompletion { - @ConditionalOnBean(name="bean1") + @ConditionalOnBean(name = "bean1") @Bean public void method() { } }""", tempJavaDocUri); - String expectedDefinitionUri = directory.toPath().resolve("src/main/java/org/test/MainClass.java").toUri().toString(); - Bean[] beans = springIndex.getBeansWithName(project.getElementName(), "bean1"); assertEquals(1, beans.length); - LocationLink expectedLocation = new LocationLink(expectedDefinitionUri, + LocationLink expectedLocation = new LocationLink(beans[0].getLocation().getUri(), beans[0].getLocation().getRange(), beans[0].getLocation().getRange(), null); editor.assertLinkTargets("bean1", List.of(expectedLocation)); } + @Test + public void testConditionalOnMissingBeanWithNameRefersToBeanDefinitionLink() throws Exception { + String tempJavaDocUri = directory.toPath().resolve("src/main/java/org/test/TempClass.java").toUri().toString(); + + Editor editor = harness.newEditor(LanguageId.JAVA, """ + package org.test; + + import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + + @Configuration + public class TestConditionalOnBeanCompletion { + @ConditionalOnMissingBean(name = "bean1") + @Bean + public void method() { + } + }""", tempJavaDocUri); + + Bean[] beans = springIndex.getBeansWithName(project.getElementName(), "bean1"); + assertEquals(1, beans.length); + + LocationLink expectedLocation = new LocationLink(beans[0].getLocation().getUri(), + beans[0].getLocation().getRange(), beans[0].getLocation().getRange(), + null); + + editor.assertLinkTargets("bean1", List.of(expectedLocation)); + } + + @Test + public void testConditionalOnBeanWithNameArrayRefersToBeanDefinitionLink() throws Exception { + String tempJavaDocUri = directory.toPath().resolve("src/main/java/org/test/TempClass.java").toUri().toString(); + + Editor editor = harness.newEditor(LanguageId.JAVA, """ + package org.test; + + import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + + @Configuration + public class TestConditionalOnBeanCompletion { + @ConditionalOnBean(name = {"bean1", "bean2"}) + @Bean + public void method() { + } + }""", tempJavaDocUri); + + Bean[] beans = springIndex.getBeansWithName(project.getElementName(), "bean2"); + assertEquals(1, beans.length); + + LocationLink expectedLocation = new LocationLink(beans[0].getLocation().getUri(), + beans[0].getLocation().getRange(), beans[0].getLocation().getRange(), + null); + + editor.assertLinkTargets("bean2", List.of(expectedLocation)); + } + + @Test + public void testConditionalOnMissingBeanWithNameArrayRefersToBeanDefinitionLink() throws Exception { + String tempJavaDocUri = directory.toPath().resolve("src/main/java/org/test/TempClass.java").toUri().toString(); + + Editor editor = harness.newEditor(LanguageId.JAVA, """ + package org.test; + + import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + + @Configuration + public class TestConditionalOnBeanCompletion { + @ConditionalOnMissingBean(name = {"bean1", "bean2"}) + @Bean + public void method() { + } + }""", tempJavaDocUri); + + Bean[] beans = springIndex.getBeansWithName(project.getElementName(), "bean2"); + assertEquals(1, beans.length); + + LocationLink expectedLocation = new LocationLink(beans[0].getLocation().getUri(), + beans[0].getLocation().getRange(), beans[0].getLocation().getRange(), + null); + + editor.assertLinkTargets("bean2", List.of(expectedLocation)); + } + @Test public void testConditionalOnBeanRefersToRandomBeanWithoutDefinitionLink() throws Exception { String tempJavaDocUri = directory.toPath().resolve("src/main/java/org/test/TempClass.java").toUri().toString(); @@ -110,7 +195,7 @@ public class ConditionalOnBeanDefinitionProviderTest { @Configuration public class TestConditionalOnBeanCompletion { - @ConditionalOnBean(name="bean5") + @ConditionalOnBean(name = "bean5") @Bean public void method() { } @@ -119,4 +204,70 @@ public class ConditionalOnBeanDefinitionProviderTest { editor.assertNoLinkTargets("bean5"); } + @Test + public void testConditionalOnMissingBeanRefersToRandomBeanWithoutDefinitionLink() throws Exception { + String tempJavaDocUri = directory.toPath().resolve("src/main/java/org/test/TempClass.java").toUri().toString(); + + Editor editor = harness.newEditor(LanguageId.JAVA, """ + package org.test; + + import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + + @Configuration + public class TestConditionalOnBeanCompletion { + @ConditionalOnMissingBean(name = "bean5") + @Bean + public void method() { + } + }""", tempJavaDocUri); + + editor.assertNoLinkTargets("bean5"); + } + + @Test + public void testConditionalOnBeanUsesTypeInsteadOfBeanNameSoNoDefinitionLink() throws Exception { + String tempJavaDocUri = directory.toPath().resolve("src/main/java/org/test/TempClass.java").toUri().toString(); + + Editor editor = harness.newEditor(LanguageId.JAVA, """ + package org.test; + + import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + + @Configuration + public class TestConditionalOnBeanCompletion { + @ConditionalOnBean(type = "bean1") + @Bean + public void method() { + } + }""", tempJavaDocUri); + + editor.assertNoLinkTargets("bean1"); + } + + @Test + public void testConditionalOnMissingBeanUsesTypeInsteadOfBeanNameSoNoDefinitionLink() throws Exception { + String tempJavaDocUri = directory.toPath().resolve("src/main/java/org/test/TempClass.java").toUri().toString(); + + Editor editor = harness.newEditor(LanguageId.JAVA, """ + package org.test; + + import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; + import org.springframework.context.annotation.Bean; + import org.springframework.context.annotation.Configuration; + + @Configuration + public class TestConditionalOnBeanCompletion { + @ConditionalOnMissingBean(type = "bean1") + @Bean + public void method() { + } + }""", tempJavaDocUri); + + editor.assertNoLinkTargets("bean1"); + } + }