diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java index 3d6573365..cd13f1390 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java @@ -67,7 +67,7 @@ import org.springframework.ide.vscode.boot.java.reconcilers.JavaReconciler; import org.springframework.ide.vscode.boot.java.reconcilers.JdtAstReconciler; import org.springframework.ide.vscode.boot.java.reconcilers.JdtReconciler; import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache; -import org.springframework.ide.vscode.boot.java.value.PropertyValueAnnotationDefProvider; +import org.springframework.ide.vscode.boot.java.value.ValueDefinitionProvider; import org.springframework.ide.vscode.boot.jdt.ls.JavaProjectsService; import org.springframework.ide.vscode.boot.jdt.ls.JdtLsProjectCache; import org.springframework.ide.vscode.boot.metadata.AdHocSpringPropertyIndexProvider; @@ -395,7 +395,7 @@ public class BootLanguageServerBootApp { @Bean JavaDefinitionHandler javaDefinitionHandler(CompilationUnitCache cuCache, JavaProjectFinder projectFinder, SpringMetamodelIndex springIndex) { return new JavaDefinitionHandler(cuCache, projectFinder, List.of( - new PropertyValueAnnotationDefProvider(), + new ValueDefinitionProvider(), new DependsOnDefinitionProvider(springIndex), new QualifierDefinitionProvider(springIndex))); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/PropertyValueAnnotationDefProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValueDefinitionProvider.java similarity index 61% rename from headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/PropertyValueAnnotationDefProvider.java rename to headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValueDefinitionProvider.java index 80caa4533..780f1540b 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/PropertyValueAnnotationDefProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValueDefinitionProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 VMware, Inc. + * Copyright (c) 2023, 2024 VMware, 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 @@ -10,7 +10,10 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.value; +import java.nio.file.Paths; +import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -42,9 +45,9 @@ import org.yaml.snakeyaml.nodes.Node; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; -public class PropertyValueAnnotationDefProvider implements IJavaDefinitionProvider { +public class ValueDefinitionProvider implements IJavaDefinitionProvider { - private static final Logger log = LoggerFactory.getLogger(PropertyValueAnnotationDefProvider.class); + private static final Logger log = LoggerFactory.getLogger(ValueDefinitionProvider.class); private static final String PARAM_VALUE = "value"; private static final String PARAM_NAME = "name"; @@ -77,61 +80,76 @@ public class PropertyValueAnnotationDefProvider implements IJavaDefinitionProvid ); @Override - public List getDefinitions(CancelChecker cancelToken, IJavaProject project, CompilationUnit cu, - ASTNode n) { + public List getDefinitions(CancelChecker cancelToken, IJavaProject project, CompilationUnit cu, ASTNode n) { + if (n instanceof StringLiteral) { StringLiteral valueNode = (StringLiteral) n; - String propertyKey = null; - ASTNode parent = valueNode.getParent(); - if (parent instanceof Annotation) { - Annotation a = (Annotation) parent; - IAnnotationBinding binding = a.resolveAnnotationBinding(); - if (binding != null && binding.getAnnotationType() != null) { - PropertyKeyExtractor propertyExtractor = annotationToPropertyKeyExtractor.get(binding.getAnnotationType().getQualifiedName()); - if (propertyExtractor != null) { - propertyKey = propertyExtractor.extract(a, null, valueNode); - } + String literalValue = valueNode.getLiteralValue(); + if (literalValue != null) { + if (literalValue.startsWith("classpath")) { + return getDefinitionForClasspathResource(project, cu, valueNode, literalValue); } - } else if (parent instanceof MemberValuePair - && parent.getParent() instanceof Annotation) { - MemberValuePair pair = (MemberValuePair) parent; - Annotation a = (Annotation) parent.getParent(); - IAnnotationBinding binding = a.resolveAnnotationBinding(); - if (binding != null && binding.getAnnotationType() != null) { - PropertyKeyExtractor propertyExtractor = annotationToPropertyKeyExtractor.get(binding.getAnnotationType().getQualifiedName()); - if (propertyExtractor != null) { - propertyKey = propertyExtractor.extract(a, pair, valueNode); - } + else { + return getDefinitionForProperty(project, cu, valueNode); } } - - if (propertyKey != null) { - Builder builder = ImmutableList.builder(); - Map targetRanges = new HashMap<>(); - - Position startPosition = new Position(cu.getLineNumber(valueNode.getStartPosition()) - 1, - cu.getColumnNumber(valueNode.getStartPosition())); - Position endPosition = new Position( - cu.getLineNumber(valueNode.getStartPosition() + valueNode.getLength()) - 1, - cu.getColumnNumber(valueNode.getStartPosition() + valueNode.getLength())); - Range originRange = new Range(startPosition, endPosition); - - - for (Location location : findValueReferences(project, propertyKey, targetRanges)) { - LocationLink ll = new LocationLink(); - ll.setTargetUri(location.getUri()); - ll.setTargetSelectionRange(location.getRange()); - ll.setTargetRange(targetRanges.get(location)); - ll.setOriginSelectionRange(originRange); - builder.add(ll); - } - return builder.build(); - } } return Collections.emptyList(); } + private List getDefinitionForProperty(IJavaProject project, CompilationUnit cu, StringLiteral valueNode) { + String propertyKey = null; + + ASTNode parent = valueNode.getParent(); + if (parent instanceof Annotation) { + Annotation a = (Annotation) parent; + IAnnotationBinding binding = a.resolveAnnotationBinding(); + if (binding != null && binding.getAnnotationType() != null) { + PropertyKeyExtractor propertyExtractor = annotationToPropertyKeyExtractor.get(binding.getAnnotationType().getQualifiedName()); + if (propertyExtractor != null) { + propertyKey = propertyExtractor.extract(a, null, valueNode); + } + } + } else if (parent instanceof MemberValuePair + && parent.getParent() instanceof Annotation) { + MemberValuePair pair = (MemberValuePair) parent; + Annotation a = (Annotation) parent.getParent(); + IAnnotationBinding binding = a.resolveAnnotationBinding(); + if (binding != null && binding.getAnnotationType() != null) { + PropertyKeyExtractor propertyExtractor = annotationToPropertyKeyExtractor.get(binding.getAnnotationType().getQualifiedName()); + if (propertyExtractor != null) { + propertyKey = propertyExtractor.extract(a, pair, valueNode); + } + } + } + + if (propertyKey != null) { + Builder builder = ImmutableList.builder(); + Map targetRanges = new HashMap<>(); + + Position startPosition = new Position(cu.getLineNumber(valueNode.getStartPosition()) - 1, + cu.getColumnNumber(valueNode.getStartPosition())); + Position endPosition = new Position( + cu.getLineNumber(valueNode.getStartPosition() + valueNode.getLength()) - 1, + cu.getColumnNumber(valueNode.getStartPosition() + valueNode.getLength())); + Range originRange = new Range(startPosition, endPosition); + + + for (Location location : findValueReferences(project, propertyKey, targetRanges)) { + LocationLink ll = new LocationLink(); + ll.setTargetUri(location.getUri()); + ll.setTargetSelectionRange(location.getRange()); + ll.setTargetRange(targetRanges.get(location)); + ll.setOriginSelectionRange(originRange); + builder.add(ll); + } + return builder.build(); + } + + return Collections.emptyList(); + } + private List findValueReferences(IJavaProject project, String propertyKey, Map targetRanges) { Builder links = ImmutableList.builder(); IClasspathUtil.getClasspathResourcesFullPaths(project.getClasspath()).forEach(path -> { @@ -229,4 +247,42 @@ public class PropertyValueAnnotationDefProvider implements IJavaDefinitionProvid String extract(Annotation a, MemberValuePair pair, StringLiteral v); } + private List getDefinitionForClasspathResource(IJavaProject project, CompilationUnit cu, StringLiteral valueNode, String literalValue) { + literalValue = literalValue.substring("classpath:".length()); + + String[] resources = findResources(project, literalValue); + + List result = new ArrayList<>(); + + for (String resource : resources) { + String uri = "file://" + resource; + + Position startPosition = new Position(cu.getLineNumber(valueNode.getStartPosition()) - 1, + cu.getColumnNumber(valueNode.getStartPosition())); + Position endPosition = new Position( + cu.getLineNumber(valueNode.getStartPosition() + valueNode.getLength()) - 1, + cu.getColumnNumber(valueNode.getStartPosition() + valueNode.getLength())); + Range nodeRange = new Range(startPosition, endPosition); + + LocationLink locationLink = new LocationLink(uri, + new Range(new Position(0, 0), new Position(0, 0)), new Range(new Position(0, 0), new Position(0, 0)), + nodeRange); + + result.add(locationLink); + } + + return result; + } + + private String[] findResources(IJavaProject project, String resource) { + String[] resources = IClasspathUtil.getClasspathResourcesFullPaths(project.getClasspath()) + .filter(path -> path.toString().endsWith(resource)) + .map(path -> path.toString()) + .toArray(String[]::new); + + return resources; + } + + + } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/PropertyValueAnnotationDefProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/ValueDefinitionProviderTest.java similarity index 89% rename from headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/PropertyValueAnnotationDefProviderTest.java rename to headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/ValueDefinitionProviderTest.java index 24d991bc7..b851e0554 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/PropertyValueAnnotationDefProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/ValueDefinitionProviderTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 VMware, Inc. + * Copyright (c) 2023, 2024 VMware, 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 @@ -40,7 +40,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; @ExtendWith(SpringExtension.class) @BootLanguageServerTest @Import({ AdHocPropertyHarnessTestConf.class, ValueCompletionTest.TestConf.class }) -public class PropertyValueAnnotationDefProviderTest { +public class ValueDefinitionProviderTest { @Autowired private BootLanguageServerHarness harness; @@ -207,4 +207,29 @@ public class PropertyValueAnnotationDefProviderTest { editor.assertLinkTargets("prop", List.of(expectedLocation)); } + + @Test + void testFindClasspathResource() throws Exception { + Path randomResourceFilePath = projectFile("src/main/resources/random-resource.md", ""); + Editor editor = harness.newEditor(LanguageId.JAVA, """ + package org.test; + + import org.springframework.beans.factory.annotation.Value; + + public class TestValueCompletion { + + @Value("classpath:random-resource.md") + private String value1; + }"""); + + LocationLink expectedLocation = new LocationLink(randomResourceFilePath.toUri().toASCIIString(), + new Range(new Position(0, 0), new Position(0, 0)), + new Range(new Position(0, 0), new Position(0, 0)), + new Range(new Position(6, 8), new Position(6, 38))); + + editor.assertLinkTargets("classpath:random-resource.md", List.of(expectedLocation)); + } + + + }