Navigate to property value definition from @Value value attribute
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2018, 2022 Pivotal, Inc.
|
||||
* Copyright (c) 2018, 2023 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.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
@@ -165,5 +166,21 @@ public class IClasspathUtil {
|
||||
})
|
||||
.collect(CollectorUtil.toImmutableList());
|
||||
}
|
||||
|
||||
public static Stream<Path> getClasspathResourcesFullPaths(IClasspath classpath) {
|
||||
return IClasspathUtil.getSourceFolders(classpath)
|
||||
.flatMap(folder -> {
|
||||
try {
|
||||
return Files.walk(folder.toPath())
|
||||
.filter(path -> Files.isRegularFile(path))
|
||||
.filter(path -> {
|
||||
String fileName = path.getFileName().toString();
|
||||
return !fileName.endsWith(".java") && !fileName.endsWith(".class");
|
||||
});
|
||||
} catch (IOException e) {
|
||||
return Stream.empty();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2020 Pivotal, Inc.
|
||||
* Copyright (c) 2020, 2023 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
|
||||
@@ -92,7 +92,7 @@ public class BootJavaCompletionEngineConfigurer {
|
||||
Map<String, CompletionProvider> providers = new HashMap<>();
|
||||
|
||||
providers.put(org.springframework.ide.vscode.boot.java.scope.Constants.SPRING_SCOPE, new ScopeCompletionProcessor());
|
||||
providers.put(org.springframework.ide.vscode.boot.java.value.Constants.SPRING_VALUE, new ValueCompletionProcessor(javaProjectFinder, indexProvider, adHocProperties));
|
||||
providers.put(Annotations.VALUE, new ValueCompletionProcessor(javaProjectFinder, indexProvider, adHocProperties));
|
||||
providers.put(Annotations.REPOSITORY, new DataRepositoryCompletionProcessor());
|
||||
|
||||
return new BootJavaCompletionEngine(cuCache, providers, snippetManager);
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.ide.vscode.boot.common.PropertyCompletionFactory;
|
||||
import org.springframework.ide.vscode.boot.common.RelaxedNameConfig;
|
||||
import org.springframework.ide.vscode.boot.java.JavaDefinitionHandler;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeActionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaProjectReconcilerScheduler;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaReconcileEngine;
|
||||
@@ -63,6 +64,7 @@ import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
|
||||
import org.springframework.ide.vscode.boot.java.utils.SymbolCache;
|
||||
import org.springframework.ide.vscode.boot.java.utils.SymbolCacheOnDisc;
|
||||
import org.springframework.ide.vscode.boot.java.utils.SymbolCacheVoid;
|
||||
import org.springframework.ide.vscode.boot.java.value.PropertyValueAnnotationDefProvider;
|
||||
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;
|
||||
@@ -361,4 +363,9 @@ public class BootLanguageServerBootApp {
|
||||
recipeRepoOpt.orElse(null), projectFinder, server);
|
||||
}
|
||||
|
||||
@Bean
|
||||
JavaDefinitionHandler javaDefinitionHandler(CompilationUnitCache cuCache, JavaProjectFinder projectFinder) {
|
||||
return new JavaDefinitionHandler(cuCache, projectFinder, List.of(new PropertyValueAnnotationDefProvider()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2022 Pivotal, Inc.
|
||||
* Copyright (c) 2017, 2023 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
|
||||
@@ -53,5 +53,7 @@ public class Annotations {
|
||||
public static final String CONDITIONAL_ON_JAVA = "org.springframework.boot.autoconfigure.condition.ConditionalOnJava";
|
||||
public static final String CONDITIONAL_ON_JNDI = "org.springframework.boot.autoconfigure.condition.ConditionalOnJndi";
|
||||
public static final String CONDITIONAL_ON_SINGLE_CANDIDATE = "org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate";
|
||||
|
||||
public static final String VALUE = "org.springframework.beans.factory.annotation.Value";
|
||||
|
||||
}
|
||||
|
||||
@@ -262,7 +262,7 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
|
||||
BeanInjectedIntoHoverProvider beanInjectedIntoHoverProvider = new BeanInjectedIntoHoverProvider(sourceLinks);
|
||||
ConditionalsLiveHoverProvider conditionalsLiveHoverProvider = new ConditionalsLiveHoverProvider();
|
||||
|
||||
providers.put(org.springframework.ide.vscode.boot.java.value.Constants.SPRING_VALUE, valueHoverProvider);
|
||||
providers.put(Annotations.VALUE, valueHoverProvider);
|
||||
|
||||
providers.put(Annotations.SPRING_REQUEST_MAPPING, requestMappingHoverProvider);
|
||||
providers.put(Annotations.SPRING_GET_MAPPING, requestMappingHoverProvider);
|
||||
@@ -301,7 +301,7 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
|
||||
|
||||
protected ReferencesHandler createReferenceHandler(SimpleLanguageServer server, JavaProjectFinder projectFinder) {
|
||||
Map<String, ReferenceProvider> providers = new HashMap<>();
|
||||
providers.put(org.springframework.ide.vscode.boot.java.value.Constants.SPRING_VALUE,
|
||||
providers.put(Annotations.VALUE,
|
||||
new ValuePropertyReferencesProvider(server));
|
||||
|
||||
return new BootJavaReferencesHandler(this, projectFinder, providers);
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2023 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
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* VMware, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.lsp4j.LocationLink;
|
||||
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
|
||||
public interface IJavaDefinitionProvider {
|
||||
|
||||
List<LocationLink> getDefinitions(CancelChecker cancelToken, IJavaProject project, CompilationUnit cu, ASTNode n);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2023 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
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* VMware, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.NodeFinder;
|
||||
import org.eclipse.lsp4j.DefinitionParams;
|
||||
import org.eclipse.lsp4j.LocationLink;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
|
||||
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.DefinitionHandler;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.LanguageSpecific;
|
||||
import org.springframework.ide.vscode.commons.util.text.LanguageId;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableList.Builder;
|
||||
|
||||
public class JavaDefinitionHandler implements DefinitionHandler, LanguageSpecific {
|
||||
|
||||
private CompilationUnitCache cuCache;
|
||||
private JavaProjectFinder projectFinder;
|
||||
private Collection<IJavaDefinitionProvider> providers;
|
||||
|
||||
public JavaDefinitionHandler(CompilationUnitCache cuCache, JavaProjectFinder projectFinder,
|
||||
Collection<IJavaDefinitionProvider> providers) {
|
||||
this.cuCache = cuCache;
|
||||
this.projectFinder = projectFinder;
|
||||
this.providers = providers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<LanguageId> supportedLanguages() {
|
||||
return Collections.singleton(LanguageId.JAVA);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LocationLink> handle(CancelChecker cancelToken, DefinitionParams definitionParams) {
|
||||
TextDocumentIdentifier doc = definitionParams.getTextDocument();
|
||||
IJavaProject project = projectFinder.find(doc).orElse(null);
|
||||
if (project != null) {
|
||||
URI docUri = URI.create(doc.getUri());
|
||||
return cuCache.withCompilationUnit(project, docUri, cu -> {
|
||||
Builder<LocationLink> builder = ImmutableList.builder();
|
||||
int start = cu.getPosition(definitionParams.getPosition().getLine() + 1, definitionParams.getPosition().getCharacter());
|
||||
ASTNode node = NodeFinder.perform(cu, start, 0);
|
||||
for (IJavaDefinitionProvider provider : providers) {
|
||||
if (cancelToken.isCanceled()) {
|
||||
break;
|
||||
}
|
||||
builder.addAll(provider.getDefinitions(cancelToken, project, cu, node));
|
||||
}
|
||||
return builder.build();
|
||||
});
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,9 +26,9 @@ import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.boot.app.BootJavaConfig;
|
||||
import org.springframework.ide.vscode.boot.java.Annotations;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.SpelExpressionReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
|
||||
import org.springframework.ide.vscode.boot.java.value.Constants;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
|
||||
@@ -66,8 +66,8 @@ public class JdtReconciler implements JavaReconciler {
|
||||
|
||||
this.reconcilers = new AnnotationReconciler[] {
|
||||
|
||||
new AnnotationParamReconciler(Constants.SPRING_VALUE, null, "#{", "}", spelExpressionReconciler),
|
||||
new AnnotationParamReconciler(Constants.SPRING_VALUE, "value", "#{", "}", spelExpressionReconciler),
|
||||
new AnnotationParamReconciler(Annotations.VALUE, null, "#{", "}", spelExpressionReconciler),
|
||||
new AnnotationParamReconciler(Annotations.VALUE, "value", "#{", "}", spelExpressionReconciler),
|
||||
|
||||
new AnnotationParamReconciler(SPRING_CACHEABLE, "key", "", "", spelExpressionReconciler),
|
||||
new AnnotationParamReconciler(SPRING_CACHEABLE, "condition", "", "", spelExpressionReconciler),
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.value;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class Constants {
|
||||
|
||||
public static final String SPRING_VALUE = "org.springframework.beans.factory.annotation.Value";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2023 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
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* VMware, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.value;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
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.FieldDeclaration;
|
||||
import org.eclipse.jdt.core.dom.IAnnotationBinding;
|
||||
import org.eclipse.jdt.core.dom.MemberValuePair;
|
||||
import org.eclipse.jdt.core.dom.StringLiteral;
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.LocationLink;
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.boot.java.Annotations;
|
||||
import org.springframework.ide.vscode.boot.java.IJavaDefinitionProvider;
|
||||
import org.springframework.ide.vscode.boot.properties.BootPropertiesLanguageServerComponents;
|
||||
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.yaml.snakeyaml.nodes.Node;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableList.Builder;
|
||||
|
||||
public class PropertyValueAnnotationDefProvider implements IJavaDefinitionProvider {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(PropertyValueAnnotationDefProvider.class);
|
||||
|
||||
@Override
|
||||
public List<LocationLink> 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 && isApplicableValueAnnotation((Annotation) parent)) {
|
||||
propertyKey = extractPropertyKey(valueNode.getLiteralValue());
|
||||
} else if (parent instanceof MemberValuePair
|
||||
&& "value".equals(((MemberValuePair) parent).getName().getIdentifier())
|
||||
&& parent.getParent() instanceof Annotation
|
||||
&& isApplicableValueAnnotation((Annotation) parent.getParent())) {
|
||||
propertyKey = extractPropertyKey(valueNode.getLiteralValue());
|
||||
}
|
||||
|
||||
if (propertyKey != null) {
|
||||
Builder<LocationLink> builder = ImmutableList.builder();
|
||||
Map<Location, Range> 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 static boolean isApplicableValueAnnotation(Annotation a) {
|
||||
IAnnotationBinding binding = a.resolveAnnotationBinding();
|
||||
return binding != null && Annotations.VALUE.equals(binding.getAnnotationType().getQualifiedName())
|
||||
&& a.getParent() instanceof FieldDeclaration;
|
||||
}
|
||||
|
||||
private List<Location> findValueReferences(IJavaProject project, String propertyKey, Map<Location, Range> targetRanges) {
|
||||
Builder<Location> links = ImmutableList.builder();
|
||||
IClasspathUtil.getClasspathResourcesFullPaths(project.getClasspath()).forEach(path -> {
|
||||
if (ValuePropertyReferencesProvider.isPropertiesFile(path)) {
|
||||
String filePath = path.toString();
|
||||
if (filePath.endsWith(BootPropertiesLanguageServerComponents.PROPERTIES)) {
|
||||
links.addAll(ValuePropertyReferencesProvider.findReferencesInPropertiesFile(path.toFile(), propertyKey, (pair, doc) -> {
|
||||
try {
|
||||
int line = doc.getLineOfOffset(pair.getValue().getOffset());
|
||||
int startInLine = pair.getValue().getOffset() - doc.getLineOffset(line);
|
||||
int endInLine = startInLine + (pair.getValue().getLength());
|
||||
|
||||
Position start = new Position();
|
||||
start.setLine(line);
|
||||
start.setCharacter(startInLine);
|
||||
|
||||
Position end = new Position();
|
||||
end.setLine(line);
|
||||
end.setCharacter(endInLine);
|
||||
|
||||
Range range = new Range();
|
||||
range.setStart(start);
|
||||
range.setEnd(end);
|
||||
|
||||
Location location = new Location(path.toUri().toASCIIString(), range);
|
||||
|
||||
targetRanges.put(location, doc.toRange(pair.getOffset(), pair.getLength()));
|
||||
|
||||
return Optional.of(location);
|
||||
} catch (Exception e) {
|
||||
log.error("", e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
for (String yml : BootPropertiesLanguageServerComponents.YML) {
|
||||
if (filePath.endsWith(yml)) {
|
||||
links.addAll(ValuePropertyReferencesProvider.findReferencesInYMLFile(path.toFile(), propertyKey, nodeTuple -> {
|
||||
// property key node is found. Get the value node
|
||||
Node valueNode = nodeTuple.getValueNode();
|
||||
Position valueStart = new Position();
|
||||
valueStart.setLine(valueNode.getStartMark().getLine());
|
||||
valueStart.setCharacter(valueNode.getStartMark().getColumn());
|
||||
|
||||
Position valueEnd = new Position();
|
||||
valueEnd.setLine(valueNode.getEndMark().getLine());
|
||||
valueEnd.setCharacter(valueNode.getEndMark().getColumn());
|
||||
|
||||
Range range = new Range();
|
||||
range.setStart(valueStart);
|
||||
range.setEnd(valueEnd);
|
||||
|
||||
Location location = new Location(path.toUri().toASCIIString(), new Range(valueStart, valueEnd));
|
||||
|
||||
Position keyStart = new Position(nodeTuple.getKeyNode().getStartMark().getLine(), nodeTuple.getKeyNode().getStartMark().getColumn());
|
||||
|
||||
targetRanges.put(location, new Range(keyStart, valueEnd));
|
||||
return Optional.of(location);
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return links.build();
|
||||
}
|
||||
|
||||
private static String extractPropertyKey(String s) {
|
||||
if (s.length() > 3 && (s.startsWith("${") || s.startsWith("#{")) && s.endsWith("}")) {
|
||||
return s.substring(2, s.length() - 1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2021 Pivotal, Inc.
|
||||
* Copyright (c) 2017, 2023 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
|
||||
@@ -13,6 +13,7 @@ package org.springframework.ide.vscode.boot.java.value;
|
||||
import static org.springframework.ide.vscode.commons.yaml.ast.NodeUtil.asScalar;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -20,6 +21,9 @@ import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -34,10 +38,11 @@ import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.WorkspaceFolder;
|
||||
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
|
||||
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.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.YamlASTProvider;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST;
|
||||
@@ -55,6 +60,8 @@ import org.yaml.snakeyaml.nodes.NodeTuple;
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class ValuePropertyReferencesProvider implements ReferenceProvider {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ValuePropertyReferencesProvider.class);
|
||||
|
||||
private SimpleLanguageServer languageServer;
|
||||
|
||||
@@ -133,7 +140,7 @@ public class ValuePropertyReferencesProvider implements ReferenceProvider {
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isPropertiesFile(Path path) {
|
||||
static boolean isPropertiesFile(Path path) {
|
||||
String fileName = path.getFileName().toString();
|
||||
|
||||
if (fileName.endsWith(BootPropertiesLanguageServerComponents.PROPERTIES)) {
|
||||
@@ -151,26 +158,46 @@ public class ValuePropertyReferencesProvider implements ReferenceProvider {
|
||||
private List<Location> findReferences(Path path, String propertyKey) {
|
||||
String filePath = path.toString();
|
||||
if (filePath.endsWith(BootPropertiesLanguageServerComponents.PROPERTIES)) {
|
||||
return findReferencesInPropertiesFile(filePath, propertyKey);
|
||||
return findReferencesInPropertiesFile(path.toFile(), propertyKey);
|
||||
} else {
|
||||
for (String yml : BootPropertiesLanguageServerComponents.YML) {
|
||||
if (filePath.endsWith(yml)) {
|
||||
return findReferencesInYMLFile(filePath, propertyKey);
|
||||
return findReferencesInYMLFile(path.toFile(), propertyKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new ArrayList<Location>();
|
||||
}
|
||||
|
||||
private List<Location> findReferencesInYMLFile(String filePath, String propertyKey) {
|
||||
private List<Location> findReferencesInYMLFile(File file, String propertyKey) {
|
||||
|
||||
return findReferencesInYMLFile(file, propertyKey, foundNodeTuple -> {
|
||||
Position start = new Position();
|
||||
Node foundNode = foundNodeTuple.getKeyNode();
|
||||
start.setLine(foundNode.getStartMark().getLine());
|
||||
start.setCharacter(foundNode.getStartMark().getColumn());
|
||||
|
||||
Position end = new Position();
|
||||
end.setLine(foundNode.getEndMark().getLine());
|
||||
end.setCharacter(foundNode.getEndMark().getColumn());
|
||||
|
||||
Range range = new Range();
|
||||
range.setStart(start);
|
||||
range.setEnd(end);
|
||||
|
||||
return Optional.of(new Location(file.toPath().toUri().toASCIIString(), range));
|
||||
});
|
||||
}
|
||||
|
||||
static List<Location> findReferencesInYMLFile(File file, String propertyKey, Function<NodeTuple, Optional<Location>> processor) {
|
||||
List<Location> foundLocations = new ArrayList<>();
|
||||
|
||||
try {
|
||||
String fileContent = FileUtils.readFileToString(new File(filePath));
|
||||
String fileContent = FileUtils.readFileToString(file);
|
||||
|
||||
YamlASTProvider parser = new YamlParser();
|
||||
|
||||
URI docURI = Paths.get(filePath).toUri();
|
||||
URI docURI = file.toURI();
|
||||
TextDocument doc = new TextDocument(docURI.toASCIIString(), null);
|
||||
doc.setText(fileContent);
|
||||
YamlFileAST ast = parser.getAST(doc);
|
||||
@@ -178,23 +205,13 @@ public class ValuePropertyReferencesProvider implements ReferenceProvider {
|
||||
List<Node> nodes = ast.getNodes();
|
||||
if (nodes != null && !nodes.isEmpty()) {
|
||||
for (Node node : nodes) {
|
||||
Node foundNode = findNode(node, "", propertyKey);
|
||||
if (foundNode != null) {
|
||||
|
||||
Position start = new Position();
|
||||
start.setLine(foundNode.getStartMark().getLine());
|
||||
start.setCharacter(foundNode.getStartMark().getColumn());
|
||||
|
||||
Position end = new Position();
|
||||
end.setLine(foundNode.getEndMark().getLine());
|
||||
end.setCharacter(foundNode.getEndMark().getColumn());
|
||||
|
||||
Range range = new Range();
|
||||
range.setStart(start);
|
||||
range.setEnd(end);
|
||||
|
||||
Location location = new Location(docURI.toASCIIString(), range);
|
||||
foundLocations.add(location);
|
||||
try {
|
||||
NodeTuple foundNodeTuple = findNode(node, "", propertyKey);
|
||||
if (foundNodeTuple != null) {
|
||||
processor.apply(foundNodeTuple).ifPresent(foundLocations::add);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -206,8 +223,8 @@ public class ValuePropertyReferencesProvider implements ReferenceProvider {
|
||||
|
||||
return foundLocations;
|
||||
}
|
||||
|
||||
protected Node findNode(Node node, String prefix, String propertyKey) {
|
||||
|
||||
protected static NodeTuple findNode(Node node, String prefix, String propertyKey) {
|
||||
if (node.getNodeId().equals(NodeId.mapping)) {
|
||||
for (NodeTuple entry : ((MappingNode)node).getValue()) {
|
||||
Node keyNode = entry.getKeyNode();
|
||||
@@ -216,10 +233,10 @@ public class ValuePropertyReferencesProvider implements ReferenceProvider {
|
||||
String combinedKey = prefix.length() > 0 ? prefix + "." + key : key;
|
||||
|
||||
if (combinedKey != null && combinedKey.equals(propertyKey)) {
|
||||
return keyNode;
|
||||
return entry;
|
||||
}
|
||||
else {
|
||||
Node recursive = findNode(entry.getValueNode(), combinedKey, propertyKey);
|
||||
NodeTuple recursive = findNode(entry.getValueNode(), combinedKey, propertyKey);
|
||||
if (recursive != null) {
|
||||
return recursive;
|
||||
}
|
||||
@@ -230,52 +247,59 @@ public class ValuePropertyReferencesProvider implements ReferenceProvider {
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<Location> findReferencesInPropertiesFile(String filePath, String propertyKey) {
|
||||
private List<Location> findReferencesInPropertiesFile(File file, String propertyKey) {
|
||||
|
||||
return findReferencesInPropertiesFile(file, propertyKey, (pair, doc) -> {
|
||||
try {
|
||||
int line = doc.getLineOfOffset(pair.getKey().getOffset());
|
||||
int startInLine = pair.getKey().getOffset() - doc.getLineOffset(line);
|
||||
int endInLine = startInLine + (pair.getKey().getLength());
|
||||
|
||||
Position start = new Position();
|
||||
start.setLine(line);
|
||||
start.setCharacter(startInLine);
|
||||
|
||||
Position end = new Position();
|
||||
end.setLine(line);
|
||||
end.setCharacter(endInLine);
|
||||
|
||||
Range range = new Range();
|
||||
range.setStart(start);
|
||||
range.setEnd(end);
|
||||
|
||||
return Optional.of(new Location(file.toPath().toUri().toASCIIString(), range));
|
||||
} catch (Exception e) {
|
||||
log.error("", e);
|
||||
return Optional.empty();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static List<Location> findReferencesInPropertiesFile(File file, String propertyKey, BiFunction<KeyValuePair, TextDocument, Optional<Location>> processor) {
|
||||
List<Location> foundLocations = new ArrayList<>();
|
||||
|
||||
try {
|
||||
String fileContent = FileUtils.readFileToString(new File(filePath));
|
||||
|
||||
String fileContent = FileUtils.readFileToString(file);
|
||||
|
||||
Parser parser = new AntlrParser();
|
||||
ParseResults parseResults = parser.parse(fileContent);
|
||||
|
||||
|
||||
if (parseResults != null && parseResults.ast != null) {
|
||||
parseResults.ast.getNodes(KeyValuePair.class).forEach(pair -> {
|
||||
if (pair.getKey() != null && pair.getKey().decode().equals(propertyKey)) {
|
||||
URI docURI = Paths.get(filePath).toUri();
|
||||
TextDocument doc = new TextDocument(docURI.toASCIIString(), null);
|
||||
TextDocument doc = new TextDocument(file.toURI().toASCIIString(), null);
|
||||
doc.setText(fileContent);
|
||||
|
||||
try {
|
||||
int line = doc.getLineOfOffset(pair.getKey().getOffset());
|
||||
int startInLine = pair.getKey().getOffset() - doc.getLineOffset(line);
|
||||
int endInLine = startInLine + (pair.getKey().getLength());
|
||||
|
||||
Position start = new Position();
|
||||
start.setLine(line);
|
||||
start.setCharacter(startInLine);
|
||||
|
||||
Position end = new Position();
|
||||
end.setLine(line);
|
||||
end.setCharacter(endInLine);
|
||||
|
||||
Range range = new Range();
|
||||
range.setStart(start);
|
||||
range.setEnd(end);
|
||||
|
||||
Location location = new Location(docURI.toASCIIString(), range);
|
||||
foundLocations.add(location);
|
||||
|
||||
} catch (BadLocationException e) {
|
||||
e.printStackTrace();
|
||||
processor.apply(pair, doc).ifPresent(foundLocations::add);
|
||||
} catch (Exception e) {
|
||||
log.error("", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
log.error("", e);
|
||||
}
|
||||
|
||||
return foundLocations;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2023 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
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* VMware, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.value.test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.lsp4j.LocationLink;
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
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.bootiful.AdHocPropertyHarnessTestConf;
|
||||
import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
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.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@BootLanguageServerTest
|
||||
@Import({ AdHocPropertyHarnessTestConf.class, ValueCompletionTest.TestConf.class })
|
||||
public class PropertyValueAnnotationDefProviderTest {
|
||||
|
||||
@Autowired
|
||||
private BootLanguageServerHarness harness;
|
||||
@Autowired
|
||||
private IJavaProject testProject;
|
||||
|
||||
private Set<Path> createdFiles = new HashSet<>();
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
harness.intialize(null);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() throws Exception {
|
||||
for (Path f : createdFiles) {
|
||||
Files.deleteIfExists(f);
|
||||
}
|
||||
createdFiles.clear();
|
||||
}
|
||||
|
||||
private Path projectFile(String relativePath, String content) throws IOException {
|
||||
Path projectPath = Paths.get(testProject.getLocationUri());
|
||||
Path filePath = projectPath.resolve(relativePath);
|
||||
Files.createDirectories(filePath.getParent());
|
||||
Files.write(filePath, content.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE);
|
||||
createdFiles.add(filePath);
|
||||
return filePath;
|
||||
}
|
||||
|
||||
@Test
|
||||
void propertiesCase() throws Exception {
|
||||
Path propertiesFilePath = projectFile("src/main/resources/application.properties", "some.prop=5");
|
||||
Editor editor = harness.newEditor(LanguageId.JAVA, """
|
||||
package org.test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
public class TestValueCompletion {
|
||||
|
||||
@Value("${some.prop}")
|
||||
private String value1;
|
||||
}""");
|
||||
|
||||
LocationLink expectedLocation = new LocationLink(propertiesFilePath.toUri().toASCIIString(),
|
||||
new Range(new Position(0, 0), new Position(0, 11)), new Range(new Position(0, 10), new Position(0, 11)),
|
||||
new Range(new Position(6, 8), new Position(6, 22)));
|
||||
|
||||
editor.assertLinkTargets("some.prop", Set.of(expectedLocation));
|
||||
}
|
||||
|
||||
@Test
|
||||
void yamlCase() throws Exception {
|
||||
Path yamlFilePath = projectFile("src/main/resources/application.yml", """
|
||||
some:
|
||||
prop: 5
|
||||
""");
|
||||
Editor editor = harness.newEditor(LanguageId.JAVA, """
|
||||
package org.test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
public class TestValueCompletion {
|
||||
|
||||
@Value("${some.prop}")
|
||||
private String value1;
|
||||
}""");
|
||||
|
||||
LocationLink expectedLocation = new LocationLink(yamlFilePath.toUri().toASCIIString(),
|
||||
new Range(new Position(1, 2), new Position(1, 9)), new Range(new Position(1, 8), new Position(1, 9)),
|
||||
new Range(new Position(6, 8), new Position(6, 22)));
|
||||
|
||||
editor.assertLinkTargets("some.prop", Set.of(expectedLocation));
|
||||
}
|
||||
|
||||
@Test
|
||||
void combinedCase() throws Exception {
|
||||
Path propertiesFilePath = projectFile("src/main/resources/application.properties", "some.prop=5");
|
||||
Path yamlFilePath = projectFile("src/main/resources/application.yml", """
|
||||
some:
|
||||
prop: 5
|
||||
""");
|
||||
Editor editor = harness.newEditor(LanguageId.JAVA, """
|
||||
package org.test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
public class TestValueCompletion {
|
||||
|
||||
@Value("${some.prop}")
|
||||
private String value1;
|
||||
}""");
|
||||
|
||||
LocationLink expectedPropsLocation = new LocationLink(propertiesFilePath.toUri().toASCIIString(),
|
||||
new Range(new Position(0, 0), new Position(0, 11)), new Range(new Position(0, 10), new Position(0, 11)),
|
||||
new Range(new Position(6, 8), new Position(6, 22)));
|
||||
LocationLink expectedYamlLocation = new LocationLink(yamlFilePath.toUri().toASCIIString(),
|
||||
new Range(new Position(1, 2), new Position(1, 9)), new Range(new Position(1, 8), new Position(1, 9)),
|
||||
new Range(new Position(6, 8), new Position(6, 22)));
|
||||
|
||||
editor.assertLinkTargets("some.prop", Set.of(expectedPropsLocation, expectedYamlLocation));
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user