diff options
-rw-r--r-- | TODOS.md | 2 | ||||
-rw-r--r-- | src/main/java/org/javacs/Index.java | 4 | ||||
-rw-r--r-- | src/main/java/org/javacs/JavaCompilerService.java | 182 | ||||
-rw-r--r-- | src/main/java/org/javacs/JavaTextDocumentService.java | 16 | ||||
-rw-r--r-- | src/main/java/org/javacs/Ptr.java | 99 | ||||
-rw-r--r-- | src/main/java/org/javacs/Ref.java | 19 | ||||
-rw-r--r-- | src/main/java/org/javacs/SourceRange.java | 27 |
7 files changed, 163 insertions, 186 deletions
@@ -8,6 +8,8 @@ - Files created in session don't autocomplete - EnumMap default methods don't autocomplete - Crashes when you create the first file in a new maven project +- Deleted files remain in compiler, even when you restart (via classpath?) +- Always shows last javadoc ## Autocomplete - Annotation fields diff --git a/src/main/java/org/javacs/Index.java b/src/main/java/org/javacs/Index.java index d3a8861..2e7aaab 100644 --- a/src/main/java/org/javacs/Index.java +++ b/src/main/java/org/javacs/Index.java @@ -6,11 +6,11 @@ import java.util.List; public class Index { public static final Index EMPTY = new Index(List.of(), Instant.EPOCH); - public final List<Ref> refs; + public final List<Ptr> refs; // TODO modified time can rewind when you switch branches, need to track modified and look for exact match public final Instant created; - public Index(List<Ref> refs, Instant created) { + public Index(List<Ptr> refs, Instant created) { this.refs = refs; this.created = created; } diff --git a/src/main/java/org/javacs/JavaCompilerService.java b/src/main/java/org/javacs/JavaCompilerService.java index f499a98..0bc4d36 100644 --- a/src/main/java/org/javacs/JavaCompilerService.java +++ b/src/main/java/org/javacs/JavaCompilerService.java @@ -22,6 +22,8 @@ import java.util.stream.Stream; import javax.lang.model.element.*; import javax.lang.model.type.*; import javax.tools.*; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; // TODO eliminate uses of URI in favor of Path public class JavaCompilerService { @@ -1113,7 +1115,6 @@ public class JavaCompilerService { return Optional.ofNullable(result); } - /** */ private boolean containsTopLevelDeclaration(Path file, String simpleClassName) { var find = Pattern.compile("\\b(class|interface|enum) +" + simpleClassName + "\\b"); try (var lines = Files.newBufferedReader(file)) { @@ -1160,38 +1161,8 @@ public class JavaCompilerService { return Optional.empty(); } - private static Optional<Ref> ref(TreePath from, JavacTask task) { - var trees = Trees.instance(task); - var pos = trees.getSourcePositions(); - var root = from.getCompilationUnit(); - var lines = root.getLineMap(); - var to = trees.getElement(from); - // Skip elements we can't find - if (to == null) { - LOG.warning(String.format("No element for %s", from.getLeaf())); - return Optional.empty(); - } - // Skip non-methods - if (!(to instanceof ExecutableElement || to instanceof TypeElement || to instanceof VariableElement)) { - return Optional.empty(); - } - // TODO skip anything not on source path - var toEl = idEl(to); - long start = pos.getStartPosition(root, from.getLeaf()), end = pos.getEndPosition(root, from.getLeaf()); - if (start == -1) { - LOG.warning(String.format("No position for %s", from.getLeaf())); - return Optional.empty(); - } - if (end == -1) end = start + from.getLeaf().toString().length(); - int startLine = (int) lines.getLineNumber(start), startCol = (int) lines.getColumnNumber(start); - int endLine = (int) lines.getLineNumber(end), endCol = (int) lines.getColumnNumber(end); - var fromFile = root.getSourceFile().toUri(); - var ref = new Ref(fromFile, startLine, startCol, endLine, endCol, toEl); - return Optional.of(ref); - } - /** Compile `file` and locate `e` in it */ - private Optional<Ref> findIn(Element e, Path file, String contents) { + private Optional<TreePath> findIn(Element e, Path file, String contents) { var task = singleFileTask(file.toUri(), contents); CompilationUnitTree tree; try { @@ -1202,7 +1173,7 @@ public class JavaCompilerService { } var trees = Trees.instance(task); class Find extends TreePathScanner<Void, Void> { - Optional<Ref> found = Optional.empty(); + TreePath found; boolean toStringEquals(Object left, Object right) { return Objects.equals(Objects.toString(left, ""), Objects.toString(right, "")); @@ -1218,7 +1189,7 @@ public class JavaCompilerService { void check() { if (sameSymbol()) { - found = ref(getCurrentPath(), cache.task); + found = getCurrentPath(); } } @@ -1239,16 +1210,13 @@ public class JavaCompilerService { check(); return super.visitVariable(node, aVoid); } - - Optional<Ref> run() { - scan(tree, null); - return found; - } } - return new Find().run(); + var finder = new Find(); + finder.scan(tree, null); + return Optional.ofNullable(finder.found); } - public Optional<Ref> definition(URI file, int line, int character, Function<URI, String> contents) { + public Optional<TreePath> definition(URI file, int line, int character, Function<URI, String> contents) { recompile(file, contents.apply(file), line, character); var trees = Trees.instance(cache.task); @@ -1397,63 +1365,6 @@ public class JavaCompilerService { return result; } - private static String reverseAndJoin(List<CharSequence> parts, String sep) { - var join = new StringJoiner(sep); - for (var i = parts.size() - 1; i >= 0; i--) { - join.add(parts.get(i)); - } - return join.toString(); - } - - private static String idPath(TreePath path) { - var packageName = path.getCompilationUnit().getPackageName(); - var rev = new ArrayList<CharSequence>(); - while (path != null) { - var part = path.getLeaf(); - if (part instanceof ClassTree) { - var cls = (ClassTree) part; - rev.add(cls.getSimpleName()); - } else if (part instanceof MethodTree) { - var method = (MethodTree) part; - // TODO overloads - rev.add(method.getName()); - } else if (part instanceof VariableTree) { - var variable = (VariableTree) part; - rev.add(variable.getName()); - } - path = path.getParentPath(); - } - if (packageName != null) rev.add(packageName.toString()); - var name = reverseAndJoin(rev, "."); - if (!name.matches("(\\w+\\.)*(\\w+|<init>)")) LOG.warning(String.format("`%s` doesn't look like a name", name)); - return name; - } - - private static String idEl(Element e) { - var rev = new ArrayList<CharSequence>(); - while (e != null) { - if (e instanceof PackageElement) { - var pkg = (PackageElement) e; - if (!pkg.isUnnamed()) - rev.add(pkg.getQualifiedName()); - } else if (e instanceof TypeElement) { - var type = (TypeElement) e; - rev.add(type.getSimpleName()); - } else if (e instanceof ExecutableElement) { - var method = (ExecutableElement) e; - // TODO overloads - rev.add(method.getSimpleName()); - } else if (e instanceof VariableElement) { - var field = (VariableElement) e; - rev.add(field.getSimpleName()); - } - e = e.getEnclosingElement(); - } - var name = reverseAndJoin(rev, "."); - if (!name.matches("(\\w+\\.)*(\\w+|<init>)")) LOG.warning(String.format("`%s` doesn't look like a name", name)); - return name; - } - /** * Represents a batch compilation of many files. The batch context is different that the incremental context, so * methods in this class should not access `cache`. @@ -1478,14 +1389,38 @@ public class JavaCompilerService { && toStringEquals(to, from); } - List<Ref> referencesToElement(CompilationUnitTree root, Element to) { + Optional<TreePath> ref(TreePath from) { var trees = Trees.instance(task); - var results = new ArrayList<Ref>(); + var pos = trees.getSourcePositions(); + var root = from.getCompilationUnit(); + var lines = root.getLineMap(); + var to = trees.getElement(from); + // Skip elements we can't find + if (to == null) { + LOG.warning(String.format("No element for `%s`", from.getLeaf())); + return Optional.empty(); + } + // Skip non-methods + if (!(to instanceof ExecutableElement || to instanceof TypeElement || to instanceof VariableElement)) { + return Optional.empty(); + } + // TODO skip anything not on source path + var result = trees.getPath(to); + if (result == null) { + LOG.warning(String.format("Element `%s` has no TreePath", to)); + return Optional.empty(); + } + return Optional.of(result); + } + + List<TreePath> referencesToElement(CompilationUnitTree root, Element to) { + var trees = Trees.instance(task); + var results = new ArrayList<TreePath>(); class FindReferencesElement extends TreePathScanner<Void, Void> { void check(TreePath from) { var found = trees.getElement(from); if (sameSymbol(found, to)) { - ref(from, task).ifPresent(results::add); + ref(from).ifPresent(results::add); } } @@ -1511,11 +1446,11 @@ public class JavaCompilerService { return results; } - List<Ref> index(CompilationUnitTree root) { - var refs = new ArrayList<Ref>(); + List<Ptr> index(CompilationUnitTree root) { + var refs = new ArrayList<Ptr>(); class IndexFile extends TreePathScanner<Void, Void> { void check(TreePath from) { - ref(from, task).ifPresent(refs::add); + ref(from).map(Ptr::new).ifPresent(refs::add); } @Override @@ -1585,7 +1520,7 @@ public class JavaCompilerService { return result; } - public List<Ref> references(URI file, String contents, int line, int character, ReportReferencesProgress progress) { + public List<TreePath> references(URI file, String contents, int line, int character, ReportReferencesProgress progress) { recompile(file, contents, -1, -1); var trees = Trees.instance(cache.task); @@ -1604,7 +1539,7 @@ public class JavaCompilerService { progress.checkPotentialReferences(0, possible.size()); // TODO optimize by pruning method bodies that don't contain potential references var batch = compileBatch(possible); - var result = new ArrayList<Ref>(); + var result = new ArrayList<TreePath>(); var nChecked = 0; for (var f : batch.roots) { result.addAll(batch.referencesToElement(f, toEl)); @@ -1616,12 +1551,12 @@ public class JavaCompilerService { private Map<Path, Index> index = new HashMap<>(); - private boolean referencesAnyClass(Ref r, String packageName, List<String> classNames) { - if (!r.toEl.startsWith(packageName)) + private boolean referencesAnyClass(Ptr p, String packageName, List<String> classNames) { + if (!p.inPackage(packageName)) return false; for (var c : classNames) { var qualifiedName = packageName.isEmpty() ? c : packageName + "." + c; - if (r.toEl.startsWith(qualifiedName)) + if (p.inClass(qualifiedName)) return true; } return false; @@ -1664,7 +1599,7 @@ public class JavaCompilerService { } } - public List<Ref> referencesFile(URI file, String contents, ReportReferencesProgress progress) { + public Map<Ptr, Integer> countReferences(URI file, String contents, ReportReferencesProgress progress) { LOG.info(String.format("Finding all references to %s", Paths.get(file).getFileName())); recompile(file, contents, -1, -1); @@ -1674,17 +1609,17 @@ public class JavaCompilerService { var possible = potentialReferencesToClasses(toPackage, toClasses, progress); if (possible.isEmpty()) { LOG.info("No potential references to " + file); - return List.of(); + return Map.of(); } // Reindex only files that are out-of-date updateIndex(possible, progress); // Assemble results - var result = new ArrayList<Ref>(); + var result = new HashMap<Ptr, Integer>(); for (var p : possible) { var i = index.get(p); for (var r : i.refs) { - if (referencesAnyClass(r, toPackage, toClasses)) - result.add(r); + var count = result.getOrDefault(r, 0); + result.put(r, count + 1); } } return result; @@ -1779,11 +1714,11 @@ public class JavaCompilerService { return new FixImports(tree, trees.getSourcePositions(), qualifiedNames); } - public Set<String> testMethods(URI file, String contents) { + public List<Ptr> testMethods(URI file, String contents) { LOG.info(String.format("Finding test methods in %s", Paths.get(file).getFileName())); var root = Parser.parse(new StringFileObject(contents, file)); - var found = new LinkedHashSet<String>(); + var found = new ArrayList<Ptr>(); class FindTestMethods extends TreePathScanner<Void, Void> { boolean isTestMethod(MethodTree node) { for (var ann : node.getModifiers().getAnnotations()) { @@ -1811,13 +1746,13 @@ public class JavaCompilerService { @Override public Void visitClass​(ClassTree node, Void __) { - if (isTestClass(node)) found.add(idPath(getCurrentPath())); + if (isTestClass(node)) found.add(new Ptr(getCurrentPath())); return super.visitClass(node, null); } @Override public Void visitMethod(MethodTree node, Void __) { - if (isTestMethod(node)) found.add(idPath(getCurrentPath())); + if (isTestMethod(node)) found.add(new Ptr(getCurrentPath())); return null; } } @@ -1826,7 +1761,8 @@ public class JavaCompilerService { return found; } - public Optional<SourceRange> position(URI file, String contents, String id) { + // TODO this doesn't belong here + public Optional<Range> position(URI file, String contents, Ptr id) { var task = Parser.parseTask(new StringFileObject(contents, file)); CompilationUnitTree root; try { @@ -1861,7 +1797,7 @@ public class JavaCompilerService { public Void visitClass(ClassTree c, Void nothing) { // Check if id references this class var path = getCurrentPath(); - if (idPath(path).equals(id)) { + if (new Ptr(path).equals(id)) { found = path; className = c.getSimpleName().toString(); memberName = null; @@ -1869,7 +1805,7 @@ public class JavaCompilerService { // Check if id references each method of this class for (var m : c.getMembers()) { var child = new TreePath(path, m); - if (idPath(child).equals(id)) { + if (new Ptr(child).equals(id)) { found = child; className = c.getSimpleName().toString(); memberName = memberName(m); @@ -1896,9 +1832,7 @@ public class JavaCompilerService { var startCol = (int) lines.getColumnNumber(start); var endLine = (int) lines.getLineNumber(end); var endCol = (int) lines.getColumnNumber(end); - var c = Optional.of(finder.className); - var m = Optional.ofNullable(finder.memberName); - var range = new SourceRange(file, startLine, startCol, endLine, endCol, c, m); + var range = new Range(new Position(startLine, startCol), new Position(endLine, endCol)); return Optional.of(range); } start++; diff --git a/src/main/java/org/javacs/JavaTextDocumentService.java b/src/main/java/org/javacs/JavaTextDocumentService.java index 88325d1..839d638 100644 --- a/src/main/java/org/javacs/JavaTextDocumentService.java +++ b/src/main/java/org/javacs/JavaTextDocumentService.java @@ -5,6 +5,7 @@ import com.sun.source.doctree.DocCommentTree; import com.sun.source.doctree.DocTree; import com.sun.source.doctree.ParamTree; import com.sun.source.tree.MethodTree; +import com.sun.source.util.TreePath; import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; @@ -441,19 +442,6 @@ class JavaTextDocumentService implements TextDocumentService { return null; } - private Range asRange(SourceRange pos) { - var start = new Position(pos.startLine - 1, pos.startCol - 1); - var end = new Position(pos.endLine - 1, pos.endCol - 1); - return new Range(start, end); - } - - private Location asLocation(Ref ref) { - var start = new Position(ref.startLine - 1, ref.startCol - 1); - var end = new Position(ref.endLine - 1, ref.endCol - 1); - var range = new Range(start, end); - return new Location(ref.fromFile.toString(), range); - } - private List<CodeLens> testMethods(URI uri) { var content = contents(uri).content; var tests = server.compiler.testMethods(uri, content); @@ -489,7 +477,7 @@ class JavaTextDocumentService implements TextDocumentService { try (var progress = new ReportProgress(startMessage, scanMessage, checkMessage)) { // Organize by method var refs = server.compiler.referencesFile(uri, content, progress); - var byId = new HashMap<String, List<Ref>>(); + var byId = new HashMap<String, List<TreePath>>(); for (var r : refs) { byId.computeIfAbsent(r.toEl, __ -> new ArrayList<>()).add(r); } diff --git a/src/main/java/org/javacs/Ptr.java b/src/main/java/org/javacs/Ptr.java new file mode 100644 index 0000000..71520a3 --- /dev/null +++ b/src/main/java/org/javacs/Ptr.java @@ -0,0 +1,99 @@ +package org.javacs; + +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.VariableTree; +import com.sun.source.util.TreePath; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.StringJoiner; +import java.util.logging.Logger; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; + +/** Ptr is a serialized TreePath, suitable for storing in Index and remember across compiler invocations */ +public class Ptr { + private final String path; + + public Ptr(TreePath path) { + var packageName = path.getCompilationUnit().getPackageName(); + var rev = new ArrayList<CharSequence>(); + while (path != null) { + var part = path.getLeaf(); + if (part instanceof ClassTree) { + var cls = (ClassTree) part; + rev.add(cls.getSimpleName()); + } else if (part instanceof MethodTree) { + var method = (MethodTree) part; + // TODO overloads + rev.add(method.getName()); + } else if (part instanceof VariableTree) { + var variable = (VariableTree) part; + rev.add(variable.getName()); + } + path = path.getParentPath(); + } + if (packageName != null) rev.add(packageName.toString()); + var name = reverseAndJoin(rev, "."); + if (!name.matches("(\\w+\\.)*(\\w+|<init>)")) LOG.warning(String.format("`%s` doesn't look like a name", name)); + this.path = name; + } + + public Ptr(Element e) { + var rev = new ArrayList<CharSequence>(); + while (e != null) { + if (e instanceof PackageElement) { + var pkg = (PackageElement) e; + if (!pkg.isUnnamed()) rev.add(pkg.getQualifiedName()); + } else if (e instanceof TypeElement) { + var type = (TypeElement) e; + rev.add(type.getSimpleName()); + } else if (e instanceof ExecutableElement) { + var method = (ExecutableElement) e; + // TODO overloads + rev.add(method.getSimpleName()); + } else if (e instanceof VariableElement) { + var field = (VariableElement) e; + rev.add(field.getSimpleName()); + } + e = e.getEnclosingElement(); + } + var name = reverseAndJoin(rev, "."); + if (!name.matches("(\\w+\\.)*(\\w+|<init>)")) LOG.warning(String.format("`%s` doesn't look like a name", name)); + this.path = name; + } + + private static String reverseAndJoin(List<CharSequence> parts, String sep) { + var join = new StringJoiner(sep); + for (var i = parts.size() - 1; i >= 0; i--) { + join.add(parts.get(i)); + } + return join.toString(); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof Ptr)) return false; + var that = (Ptr) other; + return this.path.equals(that.path); + } + + @Override + public int hashCode() { + return Objects.hash(path); + } + + public boolean inPackage(String packageName) { + return path.startsWith(packageName); + } + + public boolean inClass(String qualifiedName) { + return path.startsWith(qualifiedName); + } + + private static final Logger LOG = Logger.getLogger("main"); +} diff --git a/src/main/java/org/javacs/Ref.java b/src/main/java/org/javacs/Ref.java deleted file mode 100644 index a33a8ec..0000000 --- a/src/main/java/org/javacs/Ref.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.javacs; - -import java.net.URI; - -/** Reference from file:line(start,end) to a class or method */ -public class Ref { - public final URI fromFile; - public final int startLine, startCol, endLine, endCol; - public final String toEl; - - public Ref(URI fromFile, int startLine, int startCol, int endLine, int endCol, String toEl) { - this.fromFile = fromFile; - this.startLine = startLine; - this.startCol = startCol; - this.endLine = endLine; - this.endCol = endCol; - this.toEl = toEl; - } -} diff --git a/src/main/java/org/javacs/SourceRange.java b/src/main/java/org/javacs/SourceRange.java deleted file mode 100644 index 65c1aee..0000000 --- a/src/main/java/org/javacs/SourceRange.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.javacs; - -import java.net.URI; -import java.util.Optional; - -public class SourceRange { - public final URI file; - public final int startLine, startCol, endLine, endCol; - public final Optional<String> className, memberName; - - public SourceRange( - URI file, - int startLine, - int startCol, - int endLine, - int endCol, - Optional<String> className, - Optional<String> memberName) { - this.file = file; - this.startLine = startLine; - this.startCol = startCol; - this.endLine = endLine; - this.endCol = endCol; - this.className = className; - this.memberName = memberName; - } -} |