From bc5ddc9201280e5d8fcfdfe533bed8bddc0e8cad Mon Sep 17 00:00:00 2001 From: Martin Lippert Date: Thu, 20 Jun 2024 11:01:01 +0200 Subject: [PATCH] GH-1261: add definition and reference features for qualifier annotations --- .../BootJavaLanguageServerComponents.java | 42 +++--- .../beans/QualifierReferencesProvider.java | 96 +++++++++++++ .../handlers/BootJavaReferencesHandler.java | 9 +- .../boot/java/handlers/ReferenceProvider.java | 5 +- .../boot/java/utils/SpringIndexerJava.java | 5 +- .../ValuePropertyReferencesProvider.java | 3 +- .../test/QualifierDefinitionProviderTest.java | 75 +++-------- .../test/QualifierReferencesProviderTest.java | 127 ++++++++++++++++++ .../ValuePropertyReferenceFinderTest.java | 18 +-- 9 files changed, 282 insertions(+), 98 deletions(-) create mode 100644 headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/QualifierReferencesProvider.java create mode 100644 headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/QualifierReferencesProviderTest.java diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java index 5985d179c..48f539ed2 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java @@ -24,8 +24,10 @@ import org.springframework.context.ApplicationContext; import org.springframework.ide.vscode.boot.app.BootJavaConfig; import org.springframework.ide.vscode.boot.app.BootLanguageServerParams; import org.springframework.ide.vscode.boot.app.SpringSymbolIndex; +import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex; import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchyAwareLookup; import org.springframework.ide.vscode.boot.java.autowired.AutowiredHoverProvider; +import org.springframework.ide.vscode.boot.java.beans.QualifierReferencesProvider; import org.springframework.ide.vscode.boot.java.conditionals.ConditionalsLiveHoverProvider; import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeActionProvider; import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeLensEngine; @@ -126,7 +128,18 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent SimpleWorkspaceService workspaceService = server.getWorkspaceService(); SimpleTextDocumentService documents = server.getTextDocumentService(); - ReferencesHandler referencesHandler = createReferenceHandler(server, projectFinder); + SpringSymbolIndex springSymbolIndex = appContext.getBean(SpringSymbolIndex.class); + SpringMetamodelIndex springIndex = appContext.getBean(SpringMetamodelIndex.class); + BootJavaConfig config = appContext.getBean(BootJavaConfig.class); + SourceLinks sourceLinks = appContext.getBean(SourceLinks.class); + liveDataService = appContext.getBean(SpringProcessConnectorService.class); + SpringProcessLiveDataProvider liveDataProvider = appContext.getBean(SpringProcessLiveDataProvider.class); + + reconcileEngine = appContext.getBean(BootJavaReconcileEngine.class); + codeActionProvider = appContext.getBean(BootJavaCodeActionProvider.class); + + + ReferencesHandler referencesHandler = createReferenceHandler(server, projectFinder, springIndex, springSymbolIndex); documents.onReferences(referencesHandler); // @@ -134,30 +147,22 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent // // central live data components (to coordinate live data flow) - liveDataService = appContext.getBean(SpringProcessConnectorService.class); // connect the live data provider with the hovers (for data extraction and live updates) - SpringProcessLiveDataProvider liveDataProvider = appContext.getBean(SpringProcessLiveDataProvider.class); - SourceLinks sourceLinks = appContext.getBean(SourceLinks.class); hoverProvider = createHoverHandler(projectFinder, sourceLinks, liveDataProvider); new SpringProcessLiveHoverUpdater(server, hoverProvider, projectFinder, liveDataProvider); - BootJavaConfig config = appContext.getBean(BootJavaConfig.class); - // deal with locally running processes and their connections SpringProcessConnectorLocal liveDataLocalProcessConnector = new SpringProcessConnectorLocal(liveDataService, projectObserver, config); // create and handle commands new SpringProcessCommandHandler(server, liveDataService, liveDataLocalProcessConnector, appContext.getBeansOfType(SpringProcessConnectorRemote.class).values()); - - SpringSymbolIndex indexer = appContext.getBean(SpringSymbolIndex.class); - docSymbolProvider = params -> indexer.getSymbols(params.getTextDocument().getUri()); + docSymbolProvider = params -> springSymbolIndex.getSymbols(params.getTextDocument().getUri()); - workspaceService.onWorkspaceSymbol(new BootJavaWorkspaceSymbolHandler(indexer, + workspaceService.onWorkspaceSymbol(new BootJavaWorkspaceSymbolHandler(springSymbolIndex, new LiveAppURLSymbolProvider(liveDataProvider))); - liveChangeDetectionWatchdog = new SpringLiveChangeDetectionWatchdog( this, server, @@ -166,15 +171,11 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent Duration.ofSeconds(5), sourceLinks); - codeLensHandler = createCodeLensEngine(indexer); + codeLensHandler = createCodeLensEngine(springSymbolIndex); - highlightsEngine = createDocumentHighlightEngine(indexer); + highlightsEngine = createDocumentHighlightEngine(springSymbolIndex); documents.onDocumentHighlight(highlightsEngine); - reconcileEngine = appContext.getBean(BootJavaReconcileEngine.class); - - codeActionProvider = appContext.getBean(BootJavaCodeActionProvider.class); - Map jdtSemanticTokensProviders = appContext.getBeansOfType(JdtSemanticTokensProvider.class); if (!jdtSemanticTokensProviders.isEmpty()) { semanticTokensHandler = new JdtSemanticTokensHandler(cuCache, projectFinder, jdtSemanticTokensProviders.values()); @@ -289,10 +290,11 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent return new BootJavaHoverProvider(this, javaProjectFinder, providers, liveDataProvider); } - protected ReferencesHandler createReferenceHandler(SimpleLanguageServer server, JavaProjectFinder projectFinder) { + protected ReferencesHandler createReferenceHandler(SimpleLanguageServer server, JavaProjectFinder projectFinder, + SpringMetamodelIndex index, SpringSymbolIndex symbolIndex) { Map providers = new HashMap<>(); - providers.put(Annotations.VALUE, - new ValuePropertyReferencesProvider(server)); + providers.put(Annotations.VALUE, new ValuePropertyReferencesProvider(server)); + providers.put(Annotations.QUALIFIER, new QualifierReferencesProvider(index, symbolIndex)); return new BootJavaReferencesHandler(this, projectFinder, providers); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/QualifierReferencesProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/QualifierReferencesProvider.java new file mode 100644 index 000000000..3315579cd --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/QualifierReferencesProvider.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * Copyright (c) 2024 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.beans; + +import java.util.Arrays; +import java.util.List; +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.ITypeBinding; +import org.eclipse.jdt.core.dom.MemberValuePair; +import org.eclipse.jdt.core.dom.StringLiteral; +import org.eclipse.lsp4j.Location; +import org.eclipse.lsp4j.WorkspaceSymbol; +import org.eclipse.lsp4j.jsonrpc.CancelChecker; +import org.springframework.ide.vscode.boot.app.SpringSymbolIndex; +import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex; +import org.springframework.ide.vscode.boot.java.handlers.ReferenceProvider; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.protocol.spring.Bean; +import org.springframework.ide.vscode.commons.util.text.TextDocument; + +/** + * @author Martin Lippert + */ +public class QualifierReferencesProvider implements ReferenceProvider { + + private final SpringMetamodelIndex springIndex; + private final SpringSymbolIndex symbolIndex; + + public QualifierReferencesProvider(SpringMetamodelIndex springIndex, SpringSymbolIndex symbolIndex) { + this.springIndex = springIndex; + this.symbolIndex = symbolIndex; + } + + @Override + public List provideReferences(CancelChecker cancelToken, IJavaProject project, ASTNode node, Annotation annotation, ITypeBinding type, int offset, TextDocument doc) { + + cancelToken.checkCanceled(); + + try { + // case: @Value("prefix<*>") + if (node instanceof StringLiteral && node.getParent() instanceof Annotation) { + if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) { + return provideReferences(project, ((StringLiteral) node).getLiteralValue()); + } + } + // case: @Value(value="prefix<*>") + else if (node instanceof StringLiteral && node.getParent() instanceof MemberValuePair + && "value".equals(((MemberValuePair)node.getParent()).getName().toString())) { + if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) { + return provideReferences(project, ((StringLiteral) node).getLiteralValue()); + } + } + } + catch (Exception e) { + e.printStackTrace(); + } + + return null; + } + + 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()); + + String exactPhrase1 = "@Qualifier(\"" + value + "\")"; + String exactPhrase2 = "@Qualifier(value=\"" + value + "\")"; + + // qualifier annotations + List qualifierSymbols1 = symbolIndex.getAllSymbols(exactPhrase1); + List qualifierSymbols2 = symbolIndex.getAllSymbols(exactPhrase2); + + Stream qualifierLocations = Stream.concat(qualifierSymbols1.stream(), qualifierSymbols2.stream()) + .filter(symbol -> symbol.getName().contains(exactPhrase1) || symbol.getName().contains(exactPhrase2)) + .map(symbol -> symbol.getLocation()) + .filter(location -> location.isLeft()) + .map(location -> location.getLeft()); + + return Stream.concat(qualifierLocations, beanLocations).toList(); + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaReferencesHandler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaReferencesHandler.java index 567b1e7cb..12b2f6d35 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaReferencesHandler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaReferencesHandler.java @@ -13,6 +13,7 @@ package org.springframework.ide.vscode.boot.java.handlers; import java.io.File; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.CancellationException; import java.util.stream.Stream; @@ -125,13 +126,19 @@ public class BootJavaReferencesHandler implements ReferencesHandler { if (annotationNode != null) { annotation = (Annotation) annotationNode; ITypeBinding type = annotation.resolveTypeBinding(); + if (type != null) { String qualifiedName = type.getQualifiedName(); + if (qualifiedName != null) { ReferenceProvider provider = this.referenceProviders.get(qualifiedName); + if (provider != null) { - return provider.provideReferences(cancelToken, node, annotation, type, offset, doc); + Optional projectOptional = projectFinder.find(doc.getId()); + if (projectOptional.isPresent()) { + return provider.provideReferences(cancelToken, projectOptional.get(), node, annotation, type, offset, doc); + } } } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/ReferenceProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/ReferenceProvider.java index 42d8a056c..0c92f6f7b 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/ReferenceProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/ReferenceProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2021 Pivotal, Inc. + * Copyright (c) 2017, 2024 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 @@ -17,6 +17,7 @@ import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.jsonrpc.CancelChecker; +import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.util.text.TextDocument; /** @@ -24,7 +25,7 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument; */ public interface ReferenceProvider { - List provideReferences(CancelChecker cancelToken, ASTNode node, Annotation annotation, + List provideReferences(CancelChecker cancelToken, IJavaProject project, ASTNode node, Annotation annotation, ITypeBinding type, int offset, TextDocument doc); } 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 8463b96cd..370d66cb4 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2023 Pivotal, Inc. + * Copyright (c) 2017, 2024 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 @@ -14,6 +14,7 @@ import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; +import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; @@ -903,7 +904,7 @@ public class SpringIndexerJava implements SpringIndexer { for (Path path : testJavaFiles) { File file = path.toFile(); URI docUri = UriUtil.toUri(file); - String content = FileUtils.readFileToString(file); + String content = FileUtils.readFileToString(file, Charset.defaultCharset()); scanFile(project, new DocumentDescriptor(docUri.toASCIIString(), file.lastModified()), content); } } catch (Exception e) { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValuePropertyReferencesProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValuePropertyReferencesProvider.java index 534ee3d51..e4b12effa 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValuePropertyReferencesProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValuePropertyReferencesProvider.java @@ -42,6 +42,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.boot.java.handlers.ReferenceProvider; import org.springframework.ide.vscode.boot.properties.BootPropertiesLanguageServerComponents; +import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; import org.springframework.ide.vscode.commons.util.text.TextDocument; import org.springframework.ide.vscode.commons.yaml.ast.YamlASTProvider; @@ -70,7 +71,7 @@ public class ValuePropertyReferencesProvider implements ReferenceProvider { } @Override - public List provideReferences(CancelChecker cancelToken, ASTNode node, Annotation annotation, + public List provideReferences(CancelChecker cancelToken, IJavaProject project, ASTNode node, Annotation annotation, ITypeBinding type, int offset, TextDocument doc) { cancelToken.checkCanceled(); diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/QualifierDefinitionProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/QualifierDefinitionProviderTest.java index 72826a51e..9ce251846 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/QualifierDefinitionProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/QualifierDefinitionProviderTest.java @@ -92,64 +92,21 @@ public class QualifierDefinitionProviderTest { editor.assertLinkTargets("bean1", List.of(expectedLocation)); } -// @Test -// public void testMultipleDependsOnBeanDefinitionLink() 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.context.annotation.DependsOn; -// -// @Component -// @DependsOn({"bean1", "bean2"}) -// public class TestDependsOnClass { -// }""", 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, -// beans[0].getLocation().getRange(), beans[0].getLocation().getRange(), -// null); -// -// editor.assertLinkTargets("bean1", List.of(expectedLocation)); -// } -// -// @Test -// public void testDependsOnWithMultipleBeanDefinitionLinks() 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.context.annotation.DependsOn; -// -// @Component -// @DependsOn("bean1") -// public class TestDependsOnClass { -// }""", tempJavaDocUri); -// -// String expectedDefinitionUri = directory.toPath().resolve("src/main/java/org/test/MainClass.java").toUri().toString(); -// -// List beansOfDoc = new ArrayList<>(List.of(springIndex.getBeansOfDocument(expectedDefinitionUri))); -// beansOfDoc.add(new Bean("bean1", "type", new Location(expectedDefinitionUri, new Range(new Position(20, 1), new Position(20, 10))), null, null, null)); -// springIndex.updateBeans(project.getElementName(), expectedDefinitionUri, beansOfDoc.toArray(new Bean[0])); -// -// Bean[] beans = springIndex.getBeansWithName(project.getElementName(), "bean1"); -// assertEquals(2, beans.length); -// -// LocationLink expectedLocation1 = new LocationLink(expectedDefinitionUri, -// beans[0].getLocation().getRange(), beans[0].getLocation().getRange(), -// null); -// -// LocationLink expectedLocation2 = new LocationLink(expectedDefinitionUri, -// beans[1].getLocation().getRange(), beans[1].getLocation().getRange(), -// null); -// -// editor.assertLinkTargets("bean1", List.of(expectedLocation1, expectedLocation2)); -// } + @Test + public void testQualifierRefersToRandomQualifierWithoutDefinitionLink() 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.beans.factory.annotation.Qualifier; + + @Component + @Qualifier("qualifier") + public class TestDependsOnClass { + }""", tempJavaDocUri); + + editor.assertNoLinkTargets("qualifier"); + } } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/QualifierReferencesProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/QualifierReferencesProviderTest.java new file mode 100644 index 000000000..3c03ce90b --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/QualifierReferencesProviderTest.java @@ -0,0 +1,127 @@ +/******************************************************************************* + * Copyright (c) 2024 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.beans.test; + +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.lsp4j.Location; +import org.eclipse.lsp4j.LocationLink; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.TextDocumentIdentifier; +import org.junit.jupiter.api.BeforeEach; +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.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; +import org.springframework.ide.vscode.commons.protocol.spring.Bean; +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; +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 QualifierReferencesProviderTest { + + @Autowired private BootLanguageServerHarness harness; + @Autowired private JavaProjectFinder projectFinder; + @Autowired private SpringMetamodelIndex springIndex; + @Autowired private SpringSymbolIndex indexer; + + private File directory; + private IJavaProject project; + + @BeforeEach + public void setup() throws Exception { + harness.intialize(null); + + directory = new File(ProjectsHarness.class.getResource("/test-projects/test-spring-indexing/").toURI()); + + String projectDir = directory.toURI().toString(); + project = projectFinder.find(new TextDocumentIdentifier(projectDir)).get(); + + CompletableFuture initProject = indexer.waitOperation(); + initProject.get(5, TimeUnit.SECONDS); + } + + @Test + public void testQualifierRefersToBean() 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.beans.factory.annotation.Qualifier; + + @Component + @Qualifier("be<*>an1") + public class TestDependsOnClass { + }""", 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); + + Location expectedLocation = new Location(expectedDefinitionUri, + beans[0].getLocation().getRange()); + + List references = editor.getReferences(); + assertEquals(1, references.size()); + + Location foundLocation = references.get(0); + assertEquals(expectedLocation, foundLocation); + } + + @Test + public void testQualifierRefersToOtherQualifier() 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.beans.factory.annotation.Qualifier; + + @Component + @Qualifier("qualif<*>ier") + public class TestDependsOnClass { + }""", tempJavaDocUri); + + String expectedDefinitionUri = directory.toPath().resolve("src/main/java/org/test/injections/ConfigurationWithInjectionsAndAnnotations.java").toUri().toString(); + + Location expectedLocation = new Location(expectedDefinitionUri, + new Range(new Position(14, 0), new Position(14, 23))); + + List references = editor.getReferences(); + assertEquals(1, references.size()); + + Location foundLocation = references.get(0); + assertEquals(foundLocation, expectedLocation); + } + +} diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/references/test/ValuePropertyReferenceFinderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/references/test/ValuePropertyReferenceFinderTest.java index 13f600b1b..e94c99205 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/references/test/ValuePropertyReferenceFinderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/references/test/ValuePropertyReferenceFinderTest.java @@ -33,12 +33,9 @@ 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.Annotations; import org.springframework.ide.vscode.boot.java.value.ValuePropertyReferencesProvider; -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.Bean; 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; @@ -57,15 +54,10 @@ public class ValuePropertyReferenceFinderTest { @Autowired private BootLanguageServerHarness harness; @Autowired private JavaProjectFinder projectFinder; - @Autowired private SpringMetamodelIndex springIndex; @Autowired private SpringSymbolIndex indexer; private File directory; - private IJavaProject project; - private Bean[] indexedBeans; private String tempJavaDocUri; - private Bean bean1; - private Bean bean2; @BeforeEach public void setup() throws Exception { @@ -74,7 +66,7 @@ public class ValuePropertyReferenceFinderTest { directory = new File(ProjectsHarness.class.getResource("/test-projects/test-spring-indexing/").toURI()); String projectDir = directory.toURI().toString(); - project = projectFinder.find(new TextDocumentIdentifier(projectDir)).get(); + projectFinder.find(new TextDocumentIdentifier(projectDir)).get(); tempJavaDocUri = directory.toPath().resolve("src/main/java/org/test/TempClass.java").toUri().toString(); @@ -233,10 +225,10 @@ public class ValuePropertyReferenceFinderTest { @Component """ + completionLine + "\n" + - """ - public class TestDependsOnClass { - } - """; + """ + public class TestDependsOnClass { + } + """; Editor editor = harness.newEditor(LanguageId.JAVA, editorContent, tempJavaDocUri); List references = editor.getReferences();