prototype implemented for boot property hovers with values actuator data

This commit is contained in:
Martin Lippert
2017-02-21 12:08:28 +01:00
parent f64e972eff
commit 68ea3cf060
4 changed files with 384 additions and 2 deletions

View File

@@ -15,6 +15,7 @@ import org.eclipse.lsp4j.ServerCapabilities;
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.metadata.SpringPropertyIndexProvider;
import org.springframework.ide.vscode.commons.gradle.GradleCore;
import org.springframework.ide.vscode.commons.gradle.GradleProjectFinderStrategy;
@@ -24,6 +25,7 @@ import org.springframework.ide.vscode.commons.languageserver.java.DefaultJavaPro
import org.springframework.ide.vscode.commons.languageserver.java.IJavaProjectFinderStrategy;
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.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.commons.maven.JavaProjectWithClasspathFileFinderStrategy;
@@ -44,11 +46,9 @@ public class BootJavaLanguageServer extends SimpleLanguageServer {
new JavaProjectWithClasspathFileFinderStrategy()
});
private final JavaProjectFinder javaProjectFinder;
private final VscodeCompletionEngineAdapter completionEngine;
public BootJavaLanguageServer(JavaProjectFinder javaProjectFinder, SpringPropertyIndexProvider indexProvider) {
this.javaProjectFinder = javaProjectFinder;
SimpleTextDocumentService documents = getTextDocumentService();
IReconcileEngine reconcileEngine = new BootJavaReconcileEngine();
@@ -62,6 +62,9 @@ public class BootJavaLanguageServer extends SimpleLanguageServer {
completionEngine.setMaxCompletionsNumber(100);
documents.onCompletion(completionEngine::getCompletions);
documents.onCompletionResolve(completionEngine::resolveCompletion);
HoverHandler hoverInfoProvider = new BootJavaHoverProvider(this, javaProjectFinder);
documents.onHover(hoverInfoProvider);
}
public void setMaxCompletionsNumber(int number) {
@@ -76,6 +79,7 @@ public class BootJavaLanguageServer extends SimpleLanguageServer {
CompletionOptions completionProvider = new CompletionOptions();
completionProvider.setResolveProvider(false);
c.setCompletionProvider(completionProvider);
c.setHoverProvider(true);
return c;
}

View File

@@ -0,0 +1,140 @@
/*******************************************************************************
* 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.hover;
import java.nio.file.Path;
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.TextDocumentPositionParams;
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.HoverHandler;
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 BootJavaHoverProvider implements HoverHandler {
private static final String SPRING_VALUE = "org.springframework.beans.factory.annotation.Value";
private JavaProjectFinder projectFinder;
private SimpleLanguageServer server;
public BootJavaHoverProvider(SimpleLanguageServer server, JavaProjectFinder projectFinder) {
this.server = server;
this.projectFinder = projectFinder;
}
@Override
public CompletableFuture<Hover> handle(TextDocumentPositionParams params) {
SimpleTextDocumentService documents = server.getTextDocumentService();
TextDocument doc = documents.get(params).copy();
if (doc != null) {
try {
int offset = doc.toOffset(params.getPosition());
CompletableFuture<Hover> hoverResult = provideHover(doc, offset);
if (hoverResult != null) {
return hoverResult;
}
}
catch (Exception e) {
}
}
return SimpleTextDocumentService.NO_HOVER;
}
private CompletableFuture<Hover> provideHover(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 provideHoverForAnnotation(node, offset, document);
}
return null;
}
private CompletableFuture<Hover> provideHoverForAnnotation(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 provideHoverForSpringAnnotation(exactNode, annotation, type, offset, doc);
}
}
}
return null;
}
private CompletableFuture<Hover> provideHoverForSpringAnnotation(ASTNode node, Annotation annotation, ITypeBinding type, int offset, TextDocument doc) {
if (type.getQualifiedName().equals(SPRING_VALUE)) {
return new ValueHoverProvider().provideHoverForValueAnnotation(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,186 @@
/*******************************************************************************
* 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.hover;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.apache.commons.io.IOUtils;
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.Hover;
import org.eclipse.lsp4j.Range;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
/**
* @author Martin Lippert
*/
public class ValueHoverProvider {
public CompletableFuture<Hover> provideHoverForValueAnnotation(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 provideHover(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 provideHover(node.toString(), offset - node.getStartPosition(), node.getStartPosition(), doc);
}
}
}
catch (Exception e) {
e.printStackTrace();
}
return null;
}
private CompletableFuture<Hover> provideHover(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());
JSONObject properties = getPropertiesFromProcess();
if (propertyKey != null && properties != null) {
Iterator<?> keys = properties.keys();
while (keys.hasNext()) {
String key = (String) keys.next();
JSONObject props = properties.getJSONObject(key);
if (props.has(propertyKey)) {
String propertyValue = props.getString(propertyKey);
Range hoverRange = doc.toRange(nodeStartOffset + range.getStart(), range.getEnd() - range.getStart());
Hover hover = new Hover();
List<String> hoverContent = new ArrayList<>();
hoverContent.add("property value for " + propertyKey);
hoverContent.add(propertyValue);
hoverContent.add("coming from:");
hoverContent.add(key);
hover.setContents(hoverContent);
hover.setRange(hoverRange);
return CompletableFuture.completedFuture(hover);
}
}
}
}
}
catch (Exception e) {
e.printStackTrace();
}
return null;
}
public JSONObject getPropertiesFromProcess() {
try {
URL url = new URL("http://localhost:8080/env");
URLConnection con = url.openConnection();
InputStream in = con.getInputStream();
String encoding = con.getContentEncoding();
encoding = encoding == null ? "UTF-8" : encoding;
String body = IOUtils.toString(in, encoding);
JSONTokener tokener = new JSONTokener(body);
JSONObject jsonData = new JSONObject(tokener);
return jsonData;
}
catch (Exception e) {
e.printStackTrace();
}
return null;
}
public String getPropertyKey(String value, int offset) {
LocalRange range = getPropertyRange(value, offset);
if (range != null) {
return value.substring(range.getStart(), range.getEnd());
}
return null;
}
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,52 @@
/*******************************************************************************
* 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.hover.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import org.junit.Test;
import org.springframework.ide.vscode.boot.java.hover.ValueHoverProvider;
/**
* @author Martin Lippert
*/
public class ValueHoverTest {
@Test
public void testGetPropertyFromValue() {
ValueHoverProvider provider = new ValueHoverProvider();
assertNull(provider.getPropertyKey("${spring}", 0));
assertNull(provider.getPropertyKey("${spring}", 1));
assertEquals("spring", provider.getPropertyKey("${spring}", 2));
assertEquals("spring", provider.getPropertyKey("${spring}", 3));
assertEquals("spring", provider.getPropertyKey("${spring}", 8));
assertNull(provider.getPropertyKey("${spring}", 9));
assertNull(provider.getPropertyKey("abc ${spring} and other stuff", 0));
assertNull(provider.getPropertyKey("abc ${spring} and other stuff", 5));
assertEquals("spring", provider.getPropertyKey("abc ${spring} and other stuff", 6));
assertEquals("spring", provider.getPropertyKey("abc ${spring} and other stuff", 12));
assertNull(provider.getPropertyKey("abc ${spring} and other stuff", 13));
assertNull(provider.getPropertyKey("abc ${spring} and ${boot} other stuff", 5));
assertEquals("spring", provider.getPropertyKey("abc ${spring} and ${boot} other stuff", 6));
assertEquals("spring", provider.getPropertyKey("abc ${spring} and ${boot} other stuff", 12));
assertNull(provider.getPropertyKey("abc ${spring} and ${boot} other stuff", 13));
assertNull(provider.getPropertyKey("abc ${spring} and ${boot} other stuff", 19));
assertEquals("boot", provider.getPropertyKey("abc ${spring} and ${boot} other stuff", 20));
assertEquals("boot", provider.getPropertyKey("abc ${spring} and ${boot} other stuff", 24));
assertNull(provider.getPropertyKey("abc ${spring} and ${boot} other stuff", 25));
}
}