implemented first steps towards a reference handler for boot properties used in value annotations

This commit is contained in:
Martin Lippert
2017-03-06 10:03:40 +01:00
parent 7b4db21465
commit 71152eaeb6
5 changed files with 360 additions and 1 deletions

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2016 Pivotal, Inc.
* Copyright (c) 2016, 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
@@ -16,6 +16,7 @@ import org.eclipse.lsp4j.TextDocumentSyncKind;
import org.springframework.ide.vscode.boot.java.completions.BootJavaCompletionEngine;
import org.springframework.ide.vscode.boot.java.completions.BootJavaReconcileEngine;
import org.springframework.ide.vscode.boot.java.hover.BootJavaHoverProvider;
import org.springframework.ide.vscode.boot.java.references.BootJavaReferencesHandler;
import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndexProvider;
import org.springframework.ide.vscode.commons.gradle.GradleCore;
import org.springframework.ide.vscode.commons.gradle.GradleProjectFinderStrategy;
@@ -26,6 +27,7 @@ import org.springframework.ide.vscode.commons.languageserver.java.IJavaProjectFi
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine;
import org.springframework.ide.vscode.commons.languageserver.util.HoverHandler;
import org.springframework.ide.vscode.commons.languageserver.util.ReferencesHandler;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.commons.maven.JavaProjectWithClasspathFileFinderStrategy;
@@ -65,6 +67,9 @@ public class BootJavaLanguageServer extends SimpleLanguageServer {
HoverHandler hoverInfoProvider = new BootJavaHoverProvider(this, javaProjectFinder);
documents.onHover(hoverInfoProvider);
ReferencesHandler referencesHandler = new BootJavaReferencesHandler(this, javaProjectFinder);
documents.onRefeences(referencesHandler);
}
public void setMaxCompletionsNumber(int number) {
@@ -80,6 +85,7 @@ public class BootJavaLanguageServer extends SimpleLanguageServer {
completionProvider.setResolveProvider(false);
c.setCompletionProvider(completionProvider);
c.setHoverProvider(true);
c.setReferencesProvider(true);
return c;
}

View File

@@ -0,0 +1,143 @@
/*******************************************************************************
* 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
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.references;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.NodeFinder;
import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.ReferenceParams;
import org.springframework.ide.vscode.boot.java.hover.ValueHoverProvider;
import org.springframework.ide.vscode.commons.java.IClasspath;
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.ReferencesHandler;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
/**
* @author Martin Lippert
*/
public class BootJavaReferencesHandler implements ReferencesHandler {
private static final String SPRING_VALUE = "org.springframework.beans.factory.annotation.Value";
private JavaProjectFinder projectFinder;
private SimpleLanguageServer server;
public BootJavaReferencesHandler(SimpleLanguageServer server, JavaProjectFinder projectFinder) {
this.server = server;
this.projectFinder = projectFinder;
}
@Override
public CompletableFuture<List<? extends Location>> handle(ReferenceParams params) {
SimpleTextDocumentService documents = server.getTextDocumentService();
TextDocument doc = documents.get(params).copy();
if (doc != null) {
try {
int offset = doc.toOffset(params.getPosition());
CompletableFuture<List<? extends Location>> referencesResult = provideReferences(doc, offset);
if (referencesResult != null) {
return referencesResult;
}
}
catch (Exception e) {
}
}
return SimpleTextDocumentService.NO_REFERENCES;
}
private CompletableFuture<List<? extends Location>> provideReferences(TextDocument document, int offset) throws Exception {
ASTParser parser = ASTParser.newParser(AST.JLS8);
Map<String, String> options = JavaCore.getOptions();
JavaCore.setComplianceOptions(JavaCore.VERSION_1_8, options);
parser.setCompilerOptions(options);
parser.setKind(ASTParser.K_COMPILATION_UNIT);
parser.setStatementsRecovery(true);
parser.setBindingsRecovery(true);
parser.setResolveBindings(true);
String[] classpathEntries = getClasspathEntries(document);
String[] sourceEntries = new String[] {};
parser.setEnvironment(classpathEntries, sourceEntries, null, true);
String docURI = document.getUri();
String unitName = docURI.substring(docURI.lastIndexOf("/"));
parser.setUnitName(unitName);
parser.setSource(document.get(0, document.getLength()).toCharArray());
CompilationUnit cu = (CompilationUnit) parser.createAST(null);
ASTNode node = NodeFinder.perform(cu, offset, 0);
if (node != null) {
System.out.println("AST node found: " + node.getClass().getName());
return provideReferencesForAnnotation(node, offset, document);
}
return null;
}
private CompletableFuture<List<? extends Location>> provideReferencesForAnnotation(ASTNode node, int offset, TextDocument doc) {
Annotation annotation = null;
ASTNode exactNode = node;
while (node != null && !(node instanceof Annotation)) {
node = node.getParent();
}
if (node != null) {
annotation = (Annotation) node;
ITypeBinding type = annotation.resolveTypeBinding();
if (type != null) {
String qualifiedName = type.getQualifiedName();
if (qualifiedName != null && qualifiedName.startsWith("org.springframework")) {
return provideReferencesForSpringAnnotation(exactNode, annotation, type, offset, doc);
}
}
}
return null;
}
private CompletableFuture<List<? extends Location>> provideReferencesForSpringAnnotation(ASTNode node, Annotation annotation, ITypeBinding type, int offset, TextDocument doc) {
if (type.getQualifiedName().equals(SPRING_VALUE)) {
return new ValuePropertyReferencesProvider(server).provideReferencesForValueAnnotation(node, annotation, type, offset, doc);
}
return null;
}
private String[] getClasspathEntries(IDocument doc) throws Exception {
IJavaProject project = this.projectFinder.find(doc);
IClasspath classpath = project.getClasspath();
Stream<Path> classpathEntries = classpath.getClasspathEntries();
return classpathEntries
.filter(path -> path.toFile().exists())
.map(path -> path.toAbsolutePath().toString()).toArray(String[]::new);
}
}

View File

@@ -0,0 +1,169 @@
/*******************************************************************************
* 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
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.references;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CompletableFuture;
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.ITypeBinding;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.lsp4j.Location;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
/**
* @author Martin Lippert
*/
public class ValuePropertyReferencesProvider {
private SimpleLanguageServer languageServer;
public ValuePropertyReferencesProvider(SimpleLanguageServer languageServer) {
this.languageServer = languageServer;
}
public CompletableFuture<List<? extends Location>> provideReferencesForValueAnnotation(ASTNode node, Annotation annotation,
ITypeBinding type, int offset, TextDocument doc) {
try {
// case: @Value("prefix<*>")
if (node instanceof StringLiteral && node.getParent() instanceof Annotation) {
if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) {
return provideReferences(node.toString(), offset - node.getStartPosition(), node.getStartPosition(), doc);
}
}
// 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(node.toString(), offset - node.getStartPosition(), node.getStartPosition(), doc);
}
}
}
catch (Exception e) {
e.printStackTrace();
}
return null;
}
private CompletableFuture<List<? extends Location>> provideReferences(String value, int offset, int nodeStartOffset, TextDocument doc) {
try {
LocalRange range = getPropertyRange(value, offset);
if (range != null) {
String propertyKey = value.substring(range.getStart(), range.getEnd());
if (propertyKey != null && propertyKey.length() > 0) {
return findReferencesFromPropertyFiles(languageServer.getWorkspaceRoot(), propertyKey);
}
}
}
catch (Exception e) {
e.printStackTrace();
}
return null;
}
public CompletableFuture<List<? extends Location>> findReferencesFromPropertyFiles(Path workspaceRoot,
String propertyKey) {
try (Stream<Path> walk = Files.walk(workspaceRoot)) {
List<Location> locations = walk
.filter(path -> isPropertiesFile(path))
.filter(path -> path.toFile().isFile())
.map(path -> findReferences(path))
.flatMap(Collection::stream)
.collect(Collectors.toList());
return CompletableFuture.completedFuture(locations);
}
catch (Exception e) {
e.printStackTrace();
}
return null;
}
private boolean isPropertiesFile(Path path) {
return path.toString().endsWith("application.properties");
}
private List<Location> findReferences(Path path) {
String filePath = path.toString();
if (filePath.endsWith(".properties")) {
// do the real work
}
else if (filePath.endsWith(".yml")) {
// do the real work
}
return new ArrayList<Location>();
}
public LocalRange getPropertyRange(String value, int offset) {
int start = -1;
int end = -1;
for (int i = offset - 1; i >= 0; i--) {
if (value.charAt(i) == '{') {
start = i + 1;
break;
}
else if (value.charAt(i) == '}') {
break;
}
}
for(int i = offset; i < value.length(); i++) {
if (value.charAt(i) == '{' || value.charAt(i) == '$') {
break;
}
else if (value.charAt(i) == '}') {
end = i;
break;
}
}
if (start > 0 && start < value.length() && end > 0 && end <= value.length() && start < end) {
return new LocalRange(start, end);
}
return null;
}
public static class LocalRange {
private int start;
private int end;
public LocalRange(int start, int end) {
this.start = start;
this.end = end;
}
public int getStart() {
return start;
}
public int getEnd() {
return end;
}
}
}

View File

@@ -0,0 +1,40 @@
/*******************************************************************************
* 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
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.references.test;
import static org.junit.Assert.assertNotNull;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.eclipse.lsp4j.Location;
import org.junit.Test;
import org.springframework.ide.vscode.boot.java.references.ValuePropertyReferencesProvider;
import org.springframework.ide.vscode.project.harness.ProjectsHarness;
/**
* @author Martin Lippert
*/
public class PropertyReferenceFinderTest {
@Test
public void testFindReferences() throws Exception {
ValuePropertyReferencesProvider provider = new ValuePropertyReferencesProvider(null);
Path root = Paths.get(ProjectsHarness.class.getResource("/test-property-files/simple-case/").toURI());
CompletableFuture<List<? extends Location>> locations = provider.findReferencesFromPropertyFiles(root, "test.property");
assertNotNull(locations);
}
}