1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
|
package org.javacs;
import com.sun.source.tree.*;
import com.sun.source.util.*;
import java.io.IOException;
import java.net.URI;
import java.util.HashSet;
import java.util.Objects;
import java.util.logging.Logger;
import javax.lang.model.element.*;
public class CompileFile {
private final JavaCompilerService parent;
private final URI file;
private final String contents;
private final JavacTask task;
private final Trees trees;
private final CompilationUnitTree root;
CompileFile(JavaCompilerService parent, URI file, String contents) {
this.parent = parent;
this.file = file;
this.contents = contents;
this.task = CompileFocus.singleFileTask(parent, file, contents);
this.trees = Trees.instance(task);
var profiler = new Profiler();
task.addTaskListener(profiler);
try {
var it = task.parse().iterator();
this.root = it.hasNext() ? it.next() : null; // TODO something better than null when no class is present
// The results of task.analyze() are unreliable when errors are present
// You can get at `Element` values using `Trees`
task.analyze();
} catch (IOException e) {
throw new RuntimeException(e);
}
profiler.print();
}
/**
* 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 FixImports fixImports() {
// 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(file)) {
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
var sourcePathImports = Parser.existingImports(parent.sourcePath);
var classes = new HashSet<String>();
classes.addAll(parent.jdkClasses.classes());
classes.addAll(parent.classPathClasses.classes());
var fixes = Parser.resolveSymbols(unresolved, sourcePathImports, classes);
// Figure out which existing imports are actually used
var trees = Trees.instance(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());
return new FixImports(root, trees.getSourcePositions(), qualifiedNames);
}
private static final Logger LOG = Logger.getLogger("main");
}
|