PT #151438408: annotation index is updated on document save event

This commit is contained in:
Martin Lippert
2017-10-09 20:50:28 +02:00
parent aba1f581de
commit 194b4fac25
3 changed files with 105 additions and 20 deletions

View File

@@ -17,6 +17,7 @@ import java.util.concurrent.CompletableFuture;
import org.eclipse.lsp4j.CompletionItemKind;
import org.eclipse.lsp4j.InitializeParams;
import org.eclipse.lsp4j.InitializeResult;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.springframework.ide.vscode.boot.java.beans.BeansSymbolProvider;
import org.springframework.ide.vscode.boot.java.beans.ComponentSymbolProvider;
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeLensEngine;
@@ -109,7 +110,7 @@ public class BootJavaLanguageServer extends SimpleLanguageServer {
liveHoverWatchdog = new SpringLiveHoverWatchdog(this, hoverInfoProvider);
documents.onDidChangeContent(params -> {
TextDocument doc = params.getDocument();
if (testHightlighter!=null) {
if (testHightlighter != null) {
getClient().highlight(new HighlightParams(params.getDocument().getId(), testHightlighter.apply(doc)));
} else {
liveHoverWatchdog.watchDocument(doc.getUri());
@@ -121,12 +122,19 @@ public class BootJavaLanguageServer extends SimpleLanguageServer {
documents.onReferences(referencesHandler);
indexer = createAnnotationIndexer(this, javaProjectFinder);
documents.onDidSave(params -> {
String docURI = params.getDocument().getId().getUri();
String content = params.getDocument().get();
indexer.updateDocument(docURI, content);
});
documents.onDocumentSymbol(new BootJavaDocumentSymbolHandler(indexer));
workspaceService.onWorkspaceSymbol(new BootJavaWorkspaceSymbolHandler(indexer));
BootJavaCodeLensEngine codeLensHandler = createCodeLensEngine(this, javaProjectFinder);
documents.onCodeLens(codeLensHandler::createCodeLenses);
documents.onCodeLensResolve(codeLensHandler::resolveCodeLens);
}
public void setMaxCompletionsNumber(int number) {

View File

@@ -49,8 +49,6 @@ import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguage
import org.springframework.ide.vscode.commons.util.text.LanguageId;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import com.google.common.collect.ImmutableList;
/**
* @author Martin Lippert
*/
@@ -89,8 +87,19 @@ public class SpringIndexer {
}
}
public void updateDocument(String docURI) {
// TODO: update information because of doc change
public void updateDocument(String docURI, String content) {
if (docURI.endsWith(".java")) {
try {
IJavaProject project = projectFinder.find(new File(new URI(docURI)));
if (project != null) {
String[] classpathEntries = getClasspathEntries(project);
scanFile(docURI, content, classpathEntries);
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}
public List<? extends SymbolInformation> getAllSymbols(String query) {
@@ -184,6 +193,34 @@ public class SpringIndexer {
}
}
private void scanFile(String docURI, String content, String[] classpathEntries) 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[] sourceEntries = new String[] {};
parser.setEnvironment(classpathEntries, sourceEntries, null, true);
String unitName = docURI.substring(docURI.lastIndexOf("/"));
parser.setUnitName(unitName);
parser.setSource(content.toCharArray());
CompilationUnit cu = (CompilationUnit) parser.createAST(null);
if (cu != null) {
List<SymbolInformation> oldSymbols = symbolsByDoc.remove(docURI);
symbols.removeAll(oldSymbols);
AtomicReference<TextDocument> docRef = new AtomicReference<>();
scanAST(cu, docURI, docRef, content);
}
}
private void scanFiles(ASTParser parser, String[] javaFiles, String[] classpathEntries) throws Exception {
Map<String, String> options = JavaCore.getOptions();
@@ -203,20 +240,20 @@ public class SpringIndexer {
public void acceptAST(String sourceFilePath, CompilationUnit cu) {
String docURI = "file://" + sourceFilePath;
AtomicReference<TextDocument> docRef = new AtomicReference<>();
scanAST(cu, docURI, docRef);
scanAST(cu, docURI, docRef, null);
}
};
parser.createASTs(javaFiles, null, new String[0], requestor, null);
}
private void scanAST(final CompilationUnit cu, final String docURI, AtomicReference<TextDocument> docRef) {
private void scanAST(final CompilationUnit cu, final String docURI, AtomicReference<TextDocument> docRef, final String content) {
cu.accept(new ASTVisitor() {
@Override
public boolean visit(SingleMemberAnnotation node) {
try {
extractSymbolInformation(node, docURI, docRef);
extractSymbolInformation(node, docURI, docRef, content);
}
catch (Exception e) {
e.printStackTrace();
@@ -228,7 +265,7 @@ public class SpringIndexer {
@Override
public boolean visit(NormalAnnotation node) {
try {
extractSymbolInformation(node, docURI, docRef);
extractSymbolInformation(node, docURI, docRef, content);
}
catch (Exception e) {
e.printStackTrace();
@@ -240,7 +277,7 @@ public class SpringIndexer {
@Override
public boolean visit(MarkerAnnotation node) {
try {
extractSymbolInformation(node, docURI, docRef);
extractSymbolInformation(node, docURI, docRef, content);
}
catch (Exception e) {
e.printStackTrace();
@@ -251,7 +288,7 @@ public class SpringIndexer {
});
}
private void extractSymbolInformation(Annotation node, String docURI, AtomicReference<TextDocument> docRef) throws Exception {
private void extractSymbolInformation(Annotation node, String docURI, AtomicReference<TextDocument> docRef, String content) throws Exception {
ITypeBinding typeBinding = node.resolveTypeBinding();
if (typeBinding != null) {
@@ -259,7 +296,7 @@ public class SpringIndexer {
SymbolProvider provider = symbolProviders.get(qualifiedTypeName);
if (provider != null) {
TextDocument doc = getTempTextDocument(docURI, docRef);
TextDocument doc = getTempTextDocument(docURI, docRef, content);
SymbolInformation symbol = provider.getSymbol(node, doc);
if (symbol != null) {
symbols.add(symbol);
@@ -267,7 +304,7 @@ public class SpringIndexer {
}
}
else {
SymbolInformation symbol = provideDefaultSymbol(node, docURI, docRef);
SymbolInformation symbol = provideDefaultSymbol(node, docURI, docRef, content);
if (symbol != null) {
symbols.add(symbol);
symbolsByDoc.computeIfAbsent(docURI, s -> new ArrayList<SymbolInformation>()).add(symbol);
@@ -276,30 +313,32 @@ public class SpringIndexer {
}
}
private TextDocument getTempTextDocument(String docURI, AtomicReference<TextDocument> docRef) throws Exception {
private TextDocument getTempTextDocument(String docURI, AtomicReference<TextDocument> docRef, String content) throws Exception {
TextDocument doc = docRef.get();
if (doc == null) {
doc = createTempTextDocument(docURI);
doc = createTempTextDocument(docURI, content);
docRef.set(doc);
}
return doc;
}
private TextDocument createTempTextDocument(String docURI) throws Exception {
Path path = Paths.get(new URI(docURI));
String content = new String(Files.readAllBytes(path));
private TextDocument createTempTextDocument(String docURI, String content) throws Exception {
if (content == null) {
Path path = Paths.get(new URI(docURI));
content = new String(Files.readAllBytes(path));
}
TextDocument doc = new TextDocument(docURI, LanguageId.PLAINTEXT, 0, content);
return doc;
}
private SymbolInformation provideDefaultSymbol(Annotation node, String docURI, AtomicReference<TextDocument> docRef) {
private SymbolInformation provideDefaultSymbol(Annotation node, String docURI, AtomicReference<TextDocument> docRef, String content) {
try {
ITypeBinding type = node.resolveTypeBinding();
if (type != null) {
String qualifiedName = type.getQualifiedName();
if (qualifiedName != null && qualifiedName.startsWith("org.springframework")) {
TextDocument doc = getTempTextDocument(docURI, docRef);
TextDocument doc = getTempTextDocument(docURI, docRef, content);
SymbolInformation symbol = new SymbolInformation(node.toString(), SymbolKind.Interface,
new Location(doc.getUri(), doc.toRange(node.getStartPosition(), node.getLength())));
return symbol;

View File

@@ -14,12 +14,14 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.net.URI;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import org.apache.commons.io.FileUtils;
import org.eclipse.lsp4j.SymbolInformation;
import org.junit.Before;
import org.junit.Test;
@@ -144,6 +146,42 @@ public class SpringIndexerTest {
assertTrue(containsSymbol(allSymbols, "@/classlevel/mapping-subpackage -- (no method defined)", uriPrefix + "/src/main/java/org/test/sub/MappingClassSubpackage.java", 7, 1, 7, 38));
}
@Test
public void testUpdateChangedDocument() throws Exception {
harness.intialize(new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-parent/test-annotation-indexing/").toURI()));
// create initial index content
SpringIndexer indexer = new SpringIndexer(harness.getServer(), projectFinder, symbolProviders);
File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-parent/test-annotation-indexing/").toURI());
indexer.initialize(directory.toPath());
// update document and update index
String changedDocURI = "file://" + directory.getAbsolutePath() + "/src/main/java/org/test/SimpleMappingClass.java";
String newContent = FileUtils.readFileToString(new File(new URI(changedDocURI))).replace("mapping1", "mapping1-CHANGED");
indexer.updateDocument(changedDocURI, newContent);
// check for updated index per document
List<? extends SymbolInformation> symbols = indexer.getSymbols(changedDocURI);
assertEquals(2, symbols.size());
assertTrue(containsSymbol(symbols, "@/mapping1-CHANGED -- (no method defined)", changedDocURI, 6, 1, 6, 36));
assertTrue(containsSymbol(symbols, "@/mapping2 -- (no method defined)", changedDocURI, 11, 1, 11, 28));
// check for updated index in all symbols
List<? extends SymbolInformation> allSymbols = indexer.getAllSymbols("");
assertEquals(8, allSymbols.size());
String uriPrefix = "file://" + directory.getAbsolutePath();
assertTrue(containsSymbol(allSymbols, "@SpringBootApplication", uriPrefix + "/src/main/java/org/test/MainClass.java", 6, 0, 6, 22));
assertTrue(containsSymbol(allSymbols, "@/embedded-foo-mapping -- (no method defined)", uriPrefix + "/src/main/java/org/test/MainClass.java", 17, 1, 17, 41));
assertTrue(containsSymbol(allSymbols, "@/foo-root-mapping -- (no method defined)", uriPrefix + "/src/main/java/org/test/MainClass.java", 24, 0, 24, 36));
assertTrue(containsSymbol(allSymbols, "@/foo-root-mapping/embedded-foo-mapping-with-root -- (no method defined)", uriPrefix + "/src/main/java/org/test/MainClass.java", 27, 1, 27, 51));
assertTrue(containsSymbol(allSymbols, "@/mapping1-CHANGED -- (no method defined)", uriPrefix + "/src/main/java/org/test/SimpleMappingClass.java", 6, 1, 6, 36));
assertTrue(containsSymbol(allSymbols, "@/mapping2 -- (no method defined)", uriPrefix + "/src/main/java/org/test/SimpleMappingClass.java", 11, 1, 11, 28));
assertTrue(containsSymbol(allSymbols, "@/classlevel -- (no method defined)", uriPrefix + "/src/main/java/org/test/sub/MappingClassSubpackage.java", 4, 0, 4, 30));
assertTrue(containsSymbol(allSymbols, "@/classlevel/mapping-subpackage -- (no method defined)", uriPrefix + "/src/main/java/org/test/sub/MappingClassSubpackage.java", 7, 1, 7, 38));
}
@Test
public void testFilterSymbolsUsingQueryString() throws Exception {
harness.intialize(new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-parent/test-annotation-indexing/").toURI()));