diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/NamedCompletionProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/NamedCompletionProvider.java index 7c6553f6b..6df53f2e1 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/NamedCompletionProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/NamedCompletionProvider.java @@ -39,9 +39,7 @@ public class NamedCompletionProvider implements AnnotationAttributeCompletionPro Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName()); - return Stream.concat( - findAllNamedValues(beans), - Arrays.stream(beans).map(bean -> bean.getName())) + return findAllNamedValues(beans) .distinct() .map(beanName -> new AnnotationAttributeProposal(beanName)) .collect(Collectors.toList()); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/NamedDefinitionProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/NamedDefinitionProvider.java index 2f5fce370..f0da790de 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/NamedDefinitionProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/NamedDefinitionProvider.java @@ -14,12 +14,14 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; +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.CompilationUnit; import org.eclipse.jdt.core.dom.IAnnotationBinding; import org.eclipse.jdt.core.dom.StringLiteral; +import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.LocationLink; import org.eclipse.lsp4j.TextDocumentIdentifier; import org.eclipse.lsp4j.jsonrpc.CancelChecker; @@ -64,12 +66,18 @@ public class NamedDefinitionProvider implements IJavaDefinitionProvider { } private List findBeansWithName(IJavaProject project, String beanName) { - Bean[] beans = this.springIndex.getBeansWithName(project.getElementName(), beanName); + Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName()); - return Arrays.stream(beans) - .map(bean -> { - return new LocationLink(bean.getLocation().getUri(), bean.getLocation().getRange(), bean.getLocation().getRange()); - }) + Stream namedLocationFromBeans = Arrays.stream(beans) + // annotations from beans themselves + .flatMap(bean -> Arrays.stream(bean.getAnnotations())) + .filter(annotation -> Annotations.NAMED_ANNOTATIONS.contains(annotation.getAnnotationType())) + .filter(annotation -> annotation.getAttributes() != null && annotation.getAttributes().containsKey("value") && annotation.getAttributes().get("value").length == 1) + .filter(annotation -> annotation.getAttributes().get("value")[0].getName().equals(beanName)) + .map(annotation -> annotation.getAttributes().get("value")[0].getLocation()); + + return namedLocationFromBeans + .map(location -> new LocationLink(location.getUri(), location.getRange(), location.getRange())) .collect(Collectors.toList()); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/NamedReferencesProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/NamedReferencesProvider.java index 527774fc5..7bfca2981 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/NamedReferencesProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/NamedReferencesProvider.java @@ -71,11 +71,6 @@ public class NamedReferencesProvider implements ReferenceProvider { private List provideReferences(IJavaProject project, String value) { Bean[] beans = this.springIndex.getBeansOfProject(project.getElementName()); - // beans with name - Stream beanLocations = Arrays.stream(beans) - .filter(bean -> bean.getName().equals(value)) - .map(bean -> bean.getLocation()); - Stream namedLocationFromBeans = Arrays.stream(beans) // annotations from beans themselves .flatMap(bean -> Arrays.stream(bean.getAnnotations())) @@ -95,7 +90,7 @@ public class NamedReferencesProvider implements ReferenceProvider { .filter(annotation -> annotation.getAttributes().get("value")[0].getName().equals(value)) .map(annotation -> annotation.getAttributes().get("value")[0].getLocation()); - return Stream.concat(beanLocations, Stream.concat(namedLocationFromBeans, namedLocationsFromInjectionPoints)).toList(); + return Stream.concat(namedLocationFromBeans, namedLocationsFromInjectionPoints).toList(); } } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/NamedCompletionProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/NamedCompletionProviderTest.java index 31afb3b13..a673c14fe 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/NamedCompletionProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/NamedCompletionProviderTest.java @@ -95,53 +95,53 @@ public class NamedCompletionProviderTest { } @Test - public void testQualifierCompletionWithoutQuotesWithoutPrefix() throws Exception { - assertCompletions("@Named(<*>)", new String[] {"named1", "named2", "bean1", "bean2"}, 0, "@Named(\"named1\"<*>)"); + public void testNamedCompletionWithoutQuotesWithoutPrefix() throws Exception { + assertCompletions("@Named(<*>)", new String[] {"named1", "named2"}, 0, "@Named(\"named1\"<*>)"); } @Test - public void testQualifierCompletionWithoutQuotesWithPrefix() throws Exception { - assertCompletions("@Named(be<*>)", 2, "@Named(\"bean1\"<*>)"); + public void testNamedCompletionWithoutQuotesWithPrefix() throws Exception { + assertCompletions("@Named(be<*>)", 0, null); } @Test - public void testQualifierCompletionWithoutQuotesWithPrefixFromExistingQualifier() throws Exception { + public void testNamedCompletionWithoutQuotesWithPrefixFromExistingQualifier() throws Exception { assertCompletions("@Named(na<*>)", new String[] {"named1", "named2"}, 0, "@Named(\"named1\"<*>)"); } @Test - public void testQualifierCompletionWithoutQuotesWithAttributeName() throws Exception { - assertCompletions("@Named(value=<*>)", 4, "@Named(value=\"named1\"<*>)"); + public void testNamedCompletionWithoutQuotesWithAttributeName() throws Exception { + assertCompletions("@Named(value=<*>)", 2, "@Named(value=\"named1\"<*>)"); } @Test - public void testQualifierCompletionInsideOfQuotesWithoutPrefix() throws Exception { - assertCompletions("@Named(\"<*>\")", 4, "@Named(\"named1<*>\")"); + public void testNamedCompletionInsideOfQuotesWithoutPrefix() throws Exception { + assertCompletions("@Named(\"<*>\")", 2, "@Named(\"named1<*>\")"); } @Test - public void testQualifierCompletionInsideOfQuotesWithPrefix() throws Exception { - assertCompletions("@Named(\"be<*>\")", 2, "@Named(\"bean1<*>\")"); + public void testNAmedCompletionInsideOfQuotesWithPrefix() throws Exception { + assertCompletions("@Named(\"na<*>\")", 2, "@Named(\"named1<*>\")"); } @Test - public void testQualifierCompletionInsideOfQuotesWithPrefixButWithoutMatches() throws Exception { + public void testNamedCompletionInsideOfQuotesWithPrefixButWithoutMatches() throws Exception { assertCompletions("@Named(\"XXX<*>\")", 0, null); } @Test - public void testQualifierCompletionOutsideOfAnnotation1() throws Exception { + public void testNamedCompletionOutsideOfAnnotation1() throws Exception { assertCompletions("@Named(\"XXX\")<*>", 0, null); } @Test - public void testQualifierCompletionOutsideOfAnnotation2() throws Exception { + public void testNamedCompletionOutsideOfAnnotation2() throws Exception { assertCompletions("@Named<*>(\"XXX\")", 0, null); } @Test - public void testQualifierCompletionInsideOfQuotesWithPrefixAndReplacedPostfix() throws Exception { - assertCompletions("@Named(\"be<*>xxx\")", 2, "@Named(\"bean1<*>\")"); + public void testNamedCompletionInsideOfQuotesWithPrefixAndReplacedPostfix() throws Exception { + assertCompletions("@Named(\"na<*>xxx\")", 2, "@Named(\"named1<*>\")"); } private void assertCompletions(String completionLine, int noOfExpectedCompletions, String expectedCompletedLine) throws Exception { diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/NamedDefinitionProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/NamedDefinitionProviderTest.java index 61c900ede..a42101dd0 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/NamedDefinitionProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/NamedDefinitionProviderTest.java @@ -10,10 +10,9 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.beans.test; -import static org.junit.Assert.assertEquals; - import java.io.File; import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; @@ -33,6 +32,7 @@ import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf; import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; +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.util.text.LanguageId; @@ -62,6 +62,8 @@ public class NamedDefinitionProviderTest { private String tempJavaDocUri1; private String tempJavaDocUri2; private String tempJavaDocUri; + private Location locationNamedAnnotation1; + private Location locationNamedAnnotation2; @BeforeEach public void setup() throws Exception { @@ -79,8 +81,14 @@ public class NamedDefinitionProviderTest { tempJavaDocUri1 = directory.toPath().resolve("src/main/java/org/test/TempClass1.java").toUri().toString(); tempJavaDocUri2 = directory.toPath().resolve("src/main/java/org/test/TempClass2.java").toUri().toString(); - bean1 = new Bean("bean1", "type1", new Location(tempJavaDocUri1, new Range(new Position(1,1), new Position(1, 20))), null, null, new AnnotationMetadata[] {}); - bean2 = new Bean("bean2", "type2", new Location(tempJavaDocUri2, new Range(new Position(1,1), new Position(1, 20))), null, null, new AnnotationMetadata[] {}); + locationNamedAnnotation1 = new Location(tempJavaDocUri1, new Range(new Position(1, 10), new Position(1, 20))); + locationNamedAnnotation2 = new Location(tempJavaDocUri2, new Range(new Position(2, 10), new Position(2, 20))); + + AnnotationMetadata annotationBean1 = new AnnotationMetadata("jakarta.inject.Named", false, null, Map.of("value", new AnnotationAttributeValue[] {new AnnotationAttributeValue("named1", locationNamedAnnotation1)})); + AnnotationMetadata annotationBean2 = new AnnotationMetadata("jakarta.inject.Named", false, null, Map.of("value", new AnnotationAttributeValue[] {new AnnotationAttributeValue("named2", locationNamedAnnotation2)})); + + bean1 = new Bean("bean1", "type1", new Location(tempJavaDocUri1, new Range(new Position(1,1), new Position(1, 20))), null, null, new AnnotationMetadata[] {annotationBean1}); + bean2 = new Bean("bean2", "type2", new Location(tempJavaDocUri2, new Range(new Position(1,1), new Position(1, 20))), null, null, new AnnotationMetadata[] {annotationBean2}); springIndex.updateBeans(project.getElementName(), new Bean[] {bean1, bean2}); } @@ -92,18 +100,15 @@ public class NamedDefinitionProviderTest { import jakarta.inject.Named; - @Named("bean1") + @Named("named1") public class TestDependsOnClass { }""", tempJavaDocUri); - Bean[] beans = springIndex.getBeansWithName(project.getElementName(), "bean1"); - assertEquals(1, beans.length); - - LocationLink expectedLocation = new LocationLink(tempJavaDocUri1, - beans[0].getLocation().getRange(), beans[0].getLocation().getRange(), + LocationLink expectedLocation = new LocationLink(locationNamedAnnotation1.getUri(), + locationNamedAnnotation1.getRange(), locationNamedAnnotation1.getRange(), null); - editor.assertLinkTargets("bean1", List.of(expectedLocation)); + editor.assertLinkTargets("named1", List.of(expectedLocation)); } @Test @@ -117,19 +122,16 @@ public class NamedDefinitionProviderTest { @Component public class TestDependsOnClass { - public void setDependency(@Named("bean1") Object bean) { + public void setDependency(@Named("named1") Object bean) { } }""", tempJavaDocUri); - Bean[] beans = springIndex.getBeansWithName(project.getElementName(), "bean1"); - assertEquals(1, beans.length); - - LocationLink expectedLocation = new LocationLink(tempJavaDocUri1, - beans[0].getLocation().getRange(), beans[0].getLocation().getRange(), + LocationLink expectedLocation = new LocationLink(locationNamedAnnotation1.getUri(), + locationNamedAnnotation1.getRange(), locationNamedAnnotation1.getRange(), null); - editor.assertLinkTargets("bean1", List.of(expectedLocation)); + editor.assertLinkTargets("named1", List.of(expectedLocation)); } @Test @@ -143,12 +145,12 @@ public class NamedDefinitionProviderTest { @Component public class TestDependsOnClass { - public void setDependency(@Named("XXX") Object bean1) { + public void setDependency(@Named("bean1") Object bean1) { } }""", tempJavaDocUri1); - editor.assertNoLinkTargets("XXX"); + editor.assertNoLinkTargets("bean1"); } } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/NamedReferencesProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/NamedReferencesProviderTest.java index 2059bffdf..6d0c2a52f 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/NamedReferencesProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/NamedReferencesProviderTest.java @@ -11,14 +11,15 @@ package org.springframework.ide.vscode.boot.java.beans.test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.io.File; import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; -import org.apache.commons.lang3.ArrayUtils; import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.Range; @@ -34,8 +35,10 @@ import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf; import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; +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.InjectionPoint; import org.springframework.ide.vscode.commons.util.text.LanguageId; import org.springframework.ide.vscode.languageserver.testharness.Editor; import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness; @@ -61,6 +64,11 @@ public class NamedReferencesProviderTest { private String tempJavaDocUri1; private String tempJavaDocUri; + private String tempJavaDocUri2; + private Location locationNamedAnnotation1; + private Location locationNamedAnnotation2; + private Bean bean2; + private Location point1NamedAnnotationLocation; @BeforeEach public void setup() throws Exception { @@ -74,17 +82,47 @@ public class NamedReferencesProviderTest { CompletableFuture initProject = indexer.waitOperation(); initProject.get(5, TimeUnit.SECONDS); - tempJavaDocUri = directory.toPath().resolve("src/main/java/org/test/TestDependsOnClass.java").toUri().toString(); + tempJavaDocUri = directory.toPath().resolve("src/main/java/org/test/TestDependsOnClass.java").toUri().toString(); tempJavaDocUri1 = directory.toPath().resolve("src/main/java/org/test/TempClass1.java").toUri().toString(); + tempJavaDocUri2 = directory.toPath().resolve("src/main/java/org/test/TempClass2.java").toUri().toString(); - bean1 = new Bean("bean1", "type1", new Location(tempJavaDocUri1, new Range(new Position(1,1), new Position(1, 20))), null, null, new AnnotationMetadata[] {}); + locationNamedAnnotation1 = new Location(tempJavaDocUri1, new Range(new Position(1, 10), new Position(1, 20))); + locationNamedAnnotation2 = new Location(tempJavaDocUri2, new Range(new Position(2, 10), new Position(2, 20))); - Bean[] beans = ArrayUtils.add(springIndex.getBeansOfProject(project.getElementName()), bean1); - springIndex.updateBeans(project.getElementName(), beans); + AnnotationMetadata annotationBean1 = new AnnotationMetadata("jakarta.inject.Named", false, null, Map.of("value", new AnnotationAttributeValue[] {new AnnotationAttributeValue("named1", locationNamedAnnotation1)})); + AnnotationMetadata annotationBean2 = new AnnotationMetadata("jakarta.inject.Named", false, null, Map.of("value", new AnnotationAttributeValue[] {new AnnotationAttributeValue("named2", locationNamedAnnotation2)})); + + point1NamedAnnotationLocation = new Location(tempJavaDocUri1, new Range(new Position(20, 10), new Position(20, 20))); + AnnotationMetadata point1Metadata = new AnnotationMetadata("jakarta.inject.Named", false, null, Map.of("value", new AnnotationAttributeValue[] {new AnnotationAttributeValue("namedAtPoint1", point1NamedAnnotationLocation)})); + + InjectionPoint point1 = new InjectionPoint("point1", "type1", null, new AnnotationMetadata[] {point1Metadata}); + + bean1 = new Bean("bean1", "type1", new Location(tempJavaDocUri1, new Range(new Position(1,1), new Position(1, 20))), new InjectionPoint[] {point1}, null, new AnnotationMetadata[] {annotationBean1}); + bean2 = new Bean("bean2", "type2", new Location(tempJavaDocUri2, new Range(new Position(1,1), new Position(1, 20))), null, null, new AnnotationMetadata[] {annotationBean2}); + + springIndex.updateBeans(project.getElementName(), new Bean[] {bean1, bean2}); } @Test - public void testNamedRefersToBean() throws Exception { + public void testNamedRefersToNamedBean() throws Exception { + Editor editor = harness.newEditor(LanguageId.JAVA, """ + package org.test; + + import jakarta.inject.Named; + + @Named("na<*>med1") + public class TestDependsOnClass { + }""", tempJavaDocUri); + + List references = editor.getReferences(); + assertEquals(1, references.size()); + + Location foundLocation = references.get(0); + assertEquals(locationNamedAnnotation1, foundLocation); + } + + @Test + public void testNamedNotRefersToPureSpringBean() throws Exception { Editor editor = harness.newEditor(LanguageId.JAVA, """ package org.test; @@ -94,43 +132,26 @@ public class NamedReferencesProviderTest { public class TestDependsOnClass { }""", tempJavaDocUri); - Bean[] beans = springIndex.getBeansWithName(project.getElementName(), "bean1"); - assertEquals(1, beans.length); - - Location expectedLocation = new Location(tempJavaDocUri1, - beans[0].getLocation().getRange()); - List references = editor.getReferences(); - assertEquals(1, references.size()); - - Location foundLocation = references.get(0); - assertEquals(expectedLocation, foundLocation); + assertNull(references); } @Test - public void testNamedRefersToOtherNamedValues() throws Exception { + public void testNamedRefersToNamedInjectionPoints() throws Exception { Editor editor = harness.newEditor(LanguageId.JAVA, """ package org.test; import jakarta.inject.Named; - @Named("specificFin<*>der") + @Named("namedAt<*>Point1") public class TestDependsOnClass { }""", tempJavaDocUri); - String expectedDefinitionUri1 = directory.toPath().resolve("src/main/java/org/test/jakarta/SimpleMovieLister.java").toUri().toString(); - Location expectedLocation1 = new Location(expectedDefinitionUri1, - new Range(new Position(27, 45), new Position(27, 61))); - - String expectedDefinitionUri2 = directory.toPath().resolve("src/main/java/org/test/javax/SimpleMovieLister.java").toUri().toString(); - Location expectedLocation2 = new Location(expectedDefinitionUri2, - new Range(new Position(27, 45), new Position(27, 61))); - List references = editor.getReferences(); - assertEquals(2, references.size()); + assertEquals(1, references.size()); - assertTrue(references.contains(expectedLocation1)); - assertTrue(references.contains(expectedLocation2)); + Location foundLocation = references.get(0); + assertEquals(point1NamedAnnotationLocation, foundLocation); } }