started to implement infrastructure to parse java projects completely

This commit is contained in:
Martin Lippert
2017-08-27 18:48:00 +02:00
parent 6dc57c37b4
commit 3cbce5e81a
11 changed files with 285 additions and 106 deletions

View File

@@ -1,10 +1,25 @@
package org.springframework.ide.vscode.boot.java.symbols;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.MarkerAnnotation;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.WorkspaceSymbolParams;
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServer;
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.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
@@ -15,14 +30,121 @@ public class BootJavaWorkspaceSymbolHandler implements WorkspaceSymbolHandler {
private SimpleLanguageServer server;
private JavaProjectFinder projectFinder;
public BootJavaWorkspaceSymbolHandler(BootJavaLanguageServer bootJavaLanguageServer, JavaProjectFinder javaProjectFinder) {
public BootJavaWorkspaceSymbolHandler(BootJavaLanguageServer server, JavaProjectFinder projectFinder) {
this.server = server;
this.projectFinder = projectFinder;
}
@Override
public List<? extends SymbolInformation> handle(WorkspaceSymbolParams params) {
Path root = this.server.getWorkspaceRoot();
scanFiles(root.toFile());
return collectSymbols();
}
private List<? extends SymbolInformation> collectSymbols() {
return SimpleTextDocumentService.NO_SYMBOLS;
}
private void scanFiles(File directory) {
if (this.projectFinder.isProjectRoot(directory)) {
IJavaProject project = this.projectFinder.find(directory);
if (project != null) {
scanProject(project, directory);
}
}
else if (directory.isDirectory() && directory.exists()) {
File[] files = directory.listFiles();
for (File file : files) {
scanFiles(file);
}
}
}
private void scanProject(IJavaProject project, File directory) {
try {
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(project);
String[] sourceEntries = new String[] {};
parser.setEnvironment(classpathEntries, sourceEntries, null, true);
scanFiles(parser, directory);
}
catch (Exception e) {
e.printStackTrace();
}
}
private void scanFiles(ASTParser parser, File directory) throws Exception {
File[] javaFiles = directory.listFiles((file) -> file.getName().endsWith(".java"));
for (File javaFile : javaFiles) {
if (javaFile.isFile() && javaFile.exists()) {
scanFile(parser, javaFile);
}
}
File[] directories = directory.listFiles((file) -> file.isDirectory() && file.exists());
for (File dir : directories) {
scanFiles(parser, dir);
}
}
private void scanFile(ASTParser parser, File javaFile) throws Exception {
Path path = javaFile.toPath();
String unitName = path.getFileName().toString();
parser.setUnitName(unitName);
String content = new String(Files.readAllBytes(path));
parser.setSource(content.toCharArray());
CompilationUnit cu = (CompilationUnit) parser.createAST(null);
if (cu != null) {
System.out.println("AST node found: " + cu.getClass().getName());
scanAST(cu);
}
}
private void scanAST(CompilationUnit cu) {
cu.accept(new ASTVisitor() {
@Override
public boolean visit(SingleMemberAnnotation node) {
System.out.println("annotation found: " + node.toString());
return super.visit(node);
}
@Override
public boolean visit(NormalAnnotation node) {
System.out.println("annotation found: " + node.toString());
return super.visit(node);
}
@Override
public boolean visit(MarkerAnnotation node) {
System.out.println("annotation found: " + node.toString());
return super.visit(node);
}
});
}
private String[] getClasspathEntries(IJavaProject project) throws Exception {
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

@@ -13,6 +13,7 @@ package org.springframework.ide.vscode.boot.java.completions.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.InputStream;
import java.util.List;
import java.util.concurrent.Callable;
@@ -24,6 +25,7 @@ import org.junit.Test;
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServer;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
import org.springframework.ide.vscode.languageserver.testharness.Editor;
import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness;
@@ -35,7 +37,20 @@ import org.springframework.ide.vscode.project.harness.PropertyIndexHarness;
*/
public class ScopeCompletionTest {
private final JavaProjectFinder javaProjectFinder = (doc) -> getTestProject();
protected final JavaProjectFinder javaProjectFinder = new JavaProjectFinder() {
@Override
public boolean isProjectRoot(File file) {
return false;
}
@Override
public IJavaProject find(File file) {
return null;
}
@Override
public IJavaProject find(IDocument doc) {
return getTestProject();
}
};
private LanguageServerHarness harness;
private PropertyIndexHarness indexHarness;

View File

@@ -13,6 +13,7 @@ package org.springframework.ide.vscode.boot.java.completions.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.InputStream;
import java.util.List;
import java.util.concurrent.Callable;
@@ -25,6 +26,7 @@ import org.springframework.ide.vscode.boot.java.BootJavaLanguageServer;
import org.springframework.ide.vscode.boot.java.completions.ValueCompletionProcessor;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
import org.springframework.ide.vscode.languageserver.testharness.Editor;
import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness;
@@ -35,8 +37,21 @@ import org.springframework.ide.vscode.project.harness.PropertyIndexHarness;
* @author Martin Lippert
*/
public class ValueCompletionTest {
private final JavaProjectFinder javaProjectFinder = (doc) -> getTestProject();
protected final JavaProjectFinder javaProjectFinder = new JavaProjectFinder() {
@Override
public boolean isProjectRoot(File file) {
return false;
}
@Override
public IJavaProject find(File file) {
return null;
}
@Override
public IJavaProject find(IDocument doc) {
return getTestProject();
}
};
private LanguageServerHarness harness;
private IJavaProject testProject;
@@ -49,7 +64,7 @@ public class ValueCompletionTest {
public void setup() throws Exception {
testProject = ProjectsHarness.INSTANCE.mavenProject("test-annotations");
indexHarness = new PropertyIndexHarness();
harness = new LanguageServerHarness(new Callable<BootJavaLanguageServer>() {
@Override
public BootJavaLanguageServer call() throws Exception {
@@ -64,15 +79,15 @@ public class ValueCompletionTest {
};
harness.intialize(null);
}
private IJavaProject getTestProject() {
return testProject;
}
@Test
public void testPrefixIdentification() {
ValueCompletionProcessor processor = new ValueCompletionProcessor(null);
assertEquals("pre", processor.identifyPropertyPrefix("pre", 3));
assertEquals("pre", processor.identifyPropertyPrefix("prefix", 3));
assertEquals("", processor.identifyPropertyPrefix("", 0));
@@ -195,20 +210,20 @@ public class ValueCompletionTest {
public void testPlainPrefixCompletion() throws Exception {
prepareCase("@Value(\"onField\")", "@Value(spri<*>)");
prepareDefaultIndexData();
assertAnnotationCompletions(
"@Value(\"${spring.prop1}\"<*>)");
}
@Test
public void testQoutedPrefixCompletion() throws Exception {
prepareCase("@Value(\"onField\")", "@Value(\"spri<*>\")");
prepareDefaultIndexData();
assertAnnotationCompletions(
"@Value(\"${spring.prop1}<*>\")");
}
@Test
public void testRandomSpelExpressionNoCompletion() throws Exception {
prepareCase("@Value(\"onField\")", "@Value(\"#{<*>}\")");
@@ -219,7 +234,7 @@ public class ValueCompletionTest {
"@Value(\"#{${else.prop3}<*>}\")",
"@Value(\"#{${spring.prop1}<*>}\")");
}
@Test
public void testRandomSpelExpressionWithPropertyDollar() throws Exception {
prepareCase("@Value(\"onField\")", "@Value(\"#{345$<*>}\")");
@@ -230,7 +245,7 @@ public class ValueCompletionTest {
"@Value(\"#{345${else.prop3}<*>}\")",
"@Value(\"#{345${spring.prop1}<*>}\")");
}
@Test
public void testRandomSpelExpressionWithPropertyDollerWithoutClosindBracket() throws Exception {
prepareCase("@Value(\"onField\")", "@Value(\"#{345${<*>}\")");
@@ -241,7 +256,7 @@ public class ValueCompletionTest {
"@Value(\"#{345${else.prop3}<*>}\")",
"@Value(\"#{345${spring.prop1}<*>}\")");
}
@Test
public void testRandomSpelExpressionWithPropertyDollerWithClosingBracket() throws Exception {
prepareCase("@Value(\"onField\")", "@Value(\"#{345${<*>}}\")");
@@ -252,7 +267,7 @@ public class ValueCompletionTest {
"@Value(\"#{345${else.prop3<*>}}\")",
"@Value(\"#{345${spring.prop1<*>}}\")");
}
@Test
public void testRandomSpelExpressionWithPropertyPrefixWithoutClosingBracket() throws Exception {
prepareCase("@Value(\"onField\")", "@Value(\"#{345${spri<*>}\")");
@@ -261,7 +276,7 @@ public class ValueCompletionTest {
assertAnnotationCompletions(
"@Value(\"#{345${spring.prop1}<*>}\")");
}
@Test
public void testRandomSpelExpressionWithPropertyPrefixWithClosingBracket() throws Exception {
prepareCase("@Value(\"onField\")", "@Value(\"#{345${spri<*>}}\")");
@@ -270,21 +285,21 @@ public class ValueCompletionTest {
assertAnnotationCompletions(
"@Value(\"#{345${spring.prop1<*>}}\")");
}
private void prepareDefaultIndexData() {
indexHarness.data("spring.prop1", "java.lang.String", null, null);
indexHarness.data("data.prop2", "java.lang.String", null, null);
indexHarness.data("else.prop3", "java.lang.String", null, null);
}
private void prepareCase(String selectedAnnotation, String annotationStatementBeforeTest) throws Exception {
InputStream resource = this.getClass().getResourceAsStream("/test-projects/test-annotations/src/main/java/org/test/TestValueCompletion.java");
String content = IOUtils.toString(resource);
content = content.replace(selectedAnnotation, annotationStatementBeforeTest);
editor = new Editor(harness, content, LanguageId.JAVA);
}
private void assertAnnotationCompletions(String... completedAnnotations) throws Exception {
List<CompletionItem> completions = editor.getCompletions();
int i = 0;
@@ -293,7 +308,7 @@ public class ValueCompletionTest {
clonedEditor.apply(completions.get(i++));
assertTrue(clonedEditor.getText().contains(expectedCompleted));
}
assertEquals(i, completions.size());
}