summaryrefslogtreecommitdiff
path: root/src/main/java/org/javacs/CompileBatch.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/javacs/CompileBatch.java')
-rw-r--r--src/main/java/org/javacs/CompileBatch.java166
1 files changed, 162 insertions, 4 deletions
diff --git a/src/main/java/org/javacs/CompileBatch.java b/src/main/java/org/javacs/CompileBatch.java
index ddfb505..f12c486 100644
--- a/src/main/java/org/javacs/CompileBatch.java
+++ b/src/main/java/org/javacs/CompileBatch.java
@@ -88,12 +88,10 @@ public class CompileBatch implements AutoCloseable {
sources);
}
- public Optional<Element> element(URI uri, int line, int character) {
+ public CompilationUnitTree root(URI uri) {
for (var root : roots) {
if (root.getSourceFile().toUri().equals(uri)) {
- var path = CompileFocus.findPath(borrow.task, root, line, character);
- var el = trees.getElement(path);
- return Optional.ofNullable(el);
+ return root;
}
}
// Somehow, uri was not in batch
@@ -104,6 +102,21 @@ public class CompileBatch implements AutoCloseable {
throw new RuntimeException("File " + uri + " isn't in batch " + names);
}
+ private String contents(CompilationUnitTree root) {
+ try {
+ return root.getSourceFile().getCharContent(true).toString();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public Optional<Element> element(URI uri, int line, int character) {
+ var root = root(uri);
+ var path = CompileFocus.findPath(borrow.task, root, line, character);
+ var el = trees.getElement(path);
+ return Optional.ofNullable(el);
+ }
+
public Optional<List<TreePath>> definitions(Element el) {
LOG.info(String.format("Search for definitions of `%s` in %d files...", el, roots.size()));
@@ -223,5 +236,150 @@ public class CompileBatch implements AutoCloseable {
return ParseFile.range(borrow.task, contents, path);
}
+ public SourcePositions sourcePositions() {
+ return trees.getSourcePositions();
+ }
+
+ public LineMap lineMap(URI uri) {
+ return root(uri).getLineMap();
+ }
+
+ public List<? extends ImportTree> imports(URI uri) {
+ return root(uri).getImports();
+ }
+
+ private List<Element> overrides(ExecutableElement method) {
+ var elements = borrow.task.getElements();
+ var types = borrow.task.getTypes();
+ var results = new ArrayList<Element>();
+ var enclosingClass = (TypeElement) method.getEnclosingElement();
+ var enclosingType = enclosingClass.asType();
+ for (var superClass : types.directSupertypes(enclosingType)) {
+ var e = (TypeElement) types.asElement(superClass);
+ for (var other : e.getEnclosedElements()) {
+ if (!(other instanceof ExecutableElement)) continue;
+ if (elements.overrides(method, (ExecutableElement) other, enclosingClass)) {
+ results.add(other);
+ }
+ }
+ }
+ return results;
+ }
+
+ private boolean hasOverrideAnnotation(ExecutableElement method) {
+ for (var ann : method.getAnnotationMirrors()) {
+ var type = ann.getAnnotationType();
+ var el = type.asElement();
+ var name = el.toString();
+ if (name.equals("java.lang.Override")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Find methods that override a method from a superclass but don't have an @Override annotation. */
+ public List<TreePath> needsOverrideAnnotation(URI uri) {
+ LOG.info(String.format("Looking for methods that need an @Override annotation in %s ...", uri.getPath()));
+
+ var root = root(uri);
+ var results = new ArrayList<TreePath>();
+ class FindMissingOverride extends TreePathScanner<Void, Void> {
+ @Override
+ public Void visitMethod(MethodTree t, Void __) {
+ var method = (ExecutableElement) trees.getElement(getCurrentPath());
+ var supers = overrides(method);
+ if (!supers.isEmpty() && !hasOverrideAnnotation(method)) {
+ var overridesMethod = supers.get(0);
+ var overridesClass = overridesMethod.getEnclosingElement();
+ LOG.info(
+ String.format(
+ "...`%s` has no @Override annotation but overrides `%s.%s`",
+ method, overridesClass, overridesMethod));
+ results.add(getCurrentPath());
+ }
+ return super.visitMethod(t, null);
+ }
+ }
+ new FindMissingOverride().scan(root, null);
+ return results;
+ }
+
+ /**
+ * Figure out what imports this file should have. Star-imports like `import java.util.*` are converted to individual
+ * class imports. Missing imports are inferred by looking at imports in other source files.
+ */
+ public List<String> fixImports(URI uri) {
+ var root = root(uri);
+ var contents = contents(root);
+ // Check diagnostics for missing imports
+ var unresolved = new HashSet<String>();
+ for (var d : parent.diags) {
+ if (d.getCode().equals("compiler.err.cant.resolve.location") && d.getSource().toUri().equals(uri)) {
+ long start = d.getStartPosition(), end = d.getEndPosition();
+ var id = contents.substring((int) start, (int) end);
+ if (id.matches("[A-Z]\\w+")) {
+ unresolved.add(id);
+ } else LOG.warning(id + " doesn't look like a class");
+ } else if (d.getMessage(null).contains("cannot find to")) {
+ var lines = d.getMessage(null).split("\n");
+ var firstLine = lines.length > 0 ? lines[0] : "";
+ LOG.warning(String.format("%s %s doesn't look like to-not-found", d.getCode(), firstLine));
+ }
+ }
+ // Look at imports in other classes to help us guess how to fix imports
+ // TODO cache parsed imports on a per-file basis
+ var sourcePathImports = Parser.existingImports(FileStore.all());
+ var classes = new HashSet<String>();
+ classes.addAll(parent.jdkClasses);
+ classes.addAll(parent.classPathClasses);
+ var fixes = Parser.resolveSymbols(unresolved, sourcePathImports, classes);
+ // Figure out which existing imports are actually used
+ var trees = Trees.instance(borrow.task);
+ var references = new HashSet<String>();
+ class FindUsedImports extends TreePathScanner<Void, Void> {
+ @Override
+ public Void visitIdentifier(IdentifierTree node, Void nothing) {
+ var e = trees.getElement(getCurrentPath());
+ if (e instanceof TypeElement) {
+ var t = (TypeElement) e;
+ var qualifiedName = t.getQualifiedName().toString();
+ var lastDot = qualifiedName.lastIndexOf('.');
+ var packageName = lastDot == -1 ? "" : qualifiedName.substring(0, lastDot);
+ var thisPackage = Objects.toString(root.getPackageName(), "");
+ // java.lang.* and current package are imported by default
+ if (!packageName.equals("java.lang")
+ && !packageName.equals(thisPackage)
+ && !packageName.equals("")) {
+ references.add(qualifiedName);
+ }
+ }
+ return null;
+ }
+ }
+ new FindUsedImports().scan(root, null);
+ // Take the intersection of existing imports ^ existing identifiers
+ var qualifiedNames = new HashSet<String>();
+ for (var i : root.getImports()) {
+ var imported = i.getQualifiedIdentifier().toString();
+ if (imported.endsWith(".*")) {
+ var packageName = Parser.mostName(imported);
+ var isUsed = references.stream().anyMatch(r -> r.startsWith(packageName));
+ if (isUsed) qualifiedNames.add(imported);
+ else LOG.warning("There are no references to package " + imported);
+ } else {
+ if (references.contains(imported)) qualifiedNames.add(imported);
+ else LOG.warning("There are no references to class " + imported);
+ }
+ }
+ // Add qualified names from fixes
+ qualifiedNames.addAll(fixes.values());
+ // Sort in alphabetical order
+ var sorted = new ArrayList<String>();
+ sorted.addAll(qualifiedNames);
+ Collections.sort(sorted);
+ return sorted;
+ }
+
private static final Logger LOG = Logger.getLogger("main");
}