prototype implemented for boot property hovers with values actuator data
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user