Skip to content

Commit ae37cc2

Browse files
committed
Split up JavaCompilerService
1 parent 53eb437 commit ae37cc2

17 files changed

+1933
-1854
lines changed
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
package org.javacs;
2+
3+
import com.sun.source.tree.*;
4+
import com.sun.source.util.*;
5+
import java.io.File;
6+
import java.io.IOException;
7+
import java.net.URI;
8+
import java.nio.file.Files;
9+
import java.nio.file.Paths;
10+
import java.nio.file.attribute.FileTime;
11+
import java.util.ArrayList;
12+
import java.util.Collection;
13+
import java.util.Collections;
14+
import java.util.HashMap;
15+
import java.util.List;
16+
import java.util.Map;
17+
import java.util.Objects;
18+
import java.util.Optional;
19+
import java.util.logging.Logger;
20+
import java.util.stream.Collectors;
21+
import javax.lang.model.element.*;
22+
import javax.tools.Diagnostic;
23+
import javax.tools.JavaFileObject;
24+
import org.eclipse.lsp4j.Range;
25+
26+
public class CompileBatch {
27+
private final JavaCompilerService parent;
28+
private final JavacTask task;
29+
private final Trees trees;
30+
private final List<CompilationUnitTree> roots;
31+
32+
CompileBatch(JavaCompilerService parent, Collection<URI> files) {
33+
this.parent = parent;
34+
this.task = batchTask(parent, files);
35+
this.trees = Trees.instance(task);
36+
this.roots = new ArrayList<CompilationUnitTree>();
37+
var profiler = new Profiler();
38+
task.addTaskListener(profiler);
39+
try {
40+
for (var t : task.parse()) roots.add(t);
41+
task.analyze();
42+
// The results of task.analyze() are unreliable when errors are present
43+
// You can get at `Element` values using `Trees`
44+
task.analyze();
45+
} catch (IOException e) {
46+
throw new RuntimeException(e);
47+
}
48+
profiler.print();
49+
}
50+
51+
static JavacTask batchTask(JavaCompilerService parent, Collection<URI> paths) {
52+
parent.diags.clear();
53+
var files = new ArrayList<File>();
54+
for (var p : paths) files.add(new File(p));
55+
return (JavacTask)
56+
parent.compiler.getTask(
57+
null,
58+
parent.fileManager,
59+
parent.diags::add,
60+
JavaCompilerService.options(parent.sourcePath, parent.classPath),
61+
Collections.emptyList(),
62+
parent.fileManager.getJavaFileObjectsFromFiles(files));
63+
}
64+
65+
public List<Diagnostic<? extends JavaFileObject>> lint() {
66+
return Collections.unmodifiableList(new ArrayList<>(parent.diags));
67+
}
68+
69+
public List<TreePath> references(Element to) {
70+
LOG.info(String.format("Search for references to `%s` in %d files...", to, roots.size()));
71+
72+
var result = new ArrayList<TreePath>();
73+
for (var f : roots) {
74+
result.addAll(referencesToElement(f, to));
75+
}
76+
return result;
77+
}
78+
79+
public Map<URI, Index> countReferences() {
80+
var index = new HashMap<URI, Index>();
81+
for (var f : roots) {
82+
var uri = f.getSourceFile().toUri();
83+
var path = Paths.get(uri);
84+
var refs = index(f);
85+
FileTime modified;
86+
try {
87+
modified = Files.getLastModifiedTime(path);
88+
} catch (IOException e) {
89+
throw new RuntimeException(e);
90+
}
91+
var i = new Index(refs, modified.toInstant());
92+
index.put(uri, i);
93+
}
94+
return index;
95+
}
96+
97+
public Optional<Range> range(TreePath path) {
98+
var uri = path.getCompilationUnit().getSourceFile().toUri();
99+
var file = Paths.get(uri);
100+
String contents;
101+
try {
102+
contents = Files.readAllLines(file).stream().collect(Collectors.joining("\n"));
103+
} catch (IOException e) {
104+
throw new RuntimeException(e);
105+
}
106+
return ParseFile.range(task, contents, path);
107+
}
108+
109+
private boolean toStringEquals(Object left, Object right) {
110+
return Objects.equals(Objects.toString(left, ""), Objects.toString(right, ""));
111+
}
112+
113+
private boolean sameSymbol(Element from, Element to) {
114+
return to != null
115+
&& from != null
116+
&& toStringEquals(to.getEnclosingElement(), from.getEnclosingElement())
117+
&& toStringEquals(to, from);
118+
}
119+
120+
private Optional<TreePath> ref(TreePath from) {
121+
var trees = Trees.instance(task);
122+
var pos = trees.getSourcePositions();
123+
var root = from.getCompilationUnit();
124+
var lines = root.getLineMap();
125+
var to = trees.getElement(from);
126+
// Skip elements we can't find
127+
if (to == null) {
128+
LOG.warning(String.format("No element for `%s`", from.getLeaf()));
129+
return Optional.empty();
130+
}
131+
// Skip non-methods
132+
if (!(to instanceof ExecutableElement || to instanceof TypeElement || to instanceof VariableElement)) {
133+
return Optional.empty();
134+
}
135+
// TODO skip anything not on source path
136+
var result = trees.getPath(to);
137+
if (result == null) {
138+
LOG.warning(String.format("Element `%s` has no TreePath", to));
139+
return Optional.empty();
140+
}
141+
return Optional.of(result);
142+
}
143+
144+
private List<TreePath> referencesToElement(CompilationUnitTree root, Element to) {
145+
var trees = Trees.instance(task);
146+
var results = new ArrayList<TreePath>();
147+
class FindReferencesElement extends TreePathScanner<Void, Void> {
148+
void check(TreePath from) {
149+
var found = trees.getElement(from);
150+
if (sameSymbol(found, to)) {
151+
results.add(from);
152+
}
153+
}
154+
155+
@Override
156+
public Void visitMemberReference(MemberReferenceTree t, Void __) {
157+
check(getCurrentPath());
158+
return super.visitMemberReference(t, null);
159+
}
160+
161+
@Override
162+
public Void visitMemberSelect(MemberSelectTree t, Void __) {
163+
check(getCurrentPath());
164+
return super.visitMemberSelect(t, null);
165+
}
166+
167+
@Override
168+
public Void visitIdentifier(IdentifierTree t, Void __) {
169+
check(getCurrentPath());
170+
return super.visitIdentifier(t, null);
171+
}
172+
}
173+
new FindReferencesElement().scan(root, null);
174+
LOG.info(
175+
String.format(
176+
"...found %d references in %s", results.size(), Parser.fileName(root.getSourceFile().toUri())));
177+
return results;
178+
}
179+
180+
private List<Ptr> index(CompilationUnitTree root) {
181+
var refs = new ArrayList<Ptr>();
182+
class IndexFile extends TreePathScanner<Void, Void> {
183+
void check(TreePath from) {
184+
ref(from).map(Ptr::new).ifPresent(refs::add);
185+
}
186+
187+
@Override
188+
public Void visitMemberReference(MemberReferenceTree t, Void __) {
189+
check(getCurrentPath());
190+
return super.visitMemberReference(t, null);
191+
}
192+
193+
@Override
194+
public Void visitMemberSelect(MemberSelectTree t, Void __) {
195+
check(getCurrentPath());
196+
return super.visitMemberSelect(t, null);
197+
}
198+
199+
@Override
200+
public Void visitIdentifier(IdentifierTree t, Void __) {
201+
check(getCurrentPath());
202+
return super.visitIdentifier(t, null);
203+
}
204+
}
205+
new IndexFile().scan(root, null);
206+
LOG.info(
207+
String.format(
208+
"Found %d refs in %s", refs.size(), Paths.get(root.getSourceFile().toUri()).getFileName()));
209+
return refs;
210+
}
211+
212+
private static final Logger LOG = Logger.getLogger("main");
213+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package org.javacs;
2+
3+
import com.sun.source.tree.*;
4+
import com.sun.source.util.*;
5+
import java.io.IOException;
6+
import java.net.URI;
7+
import java.util.HashSet;
8+
import java.util.Objects;
9+
import java.util.logging.Logger;
10+
import javax.lang.model.element.*;
11+
12+
public class CompileFile {
13+
private final JavaCompilerService parent;
14+
private final URI file;
15+
private final String contents;
16+
private final JavacTask task;
17+
private final Trees trees;
18+
private final CompilationUnitTree root;
19+
20+
CompileFile(JavaCompilerService parent, URI file, String contents) {
21+
this.parent = parent;
22+
this.file = file;
23+
this.contents = contents;
24+
this.task = CompileFocus.singleFileTask(parent, file, contents);
25+
this.trees = Trees.instance(task);
26+
var profiler = new Profiler();
27+
task.addTaskListener(profiler);
28+
try {
29+
var it = task.parse().iterator();
30+
this.root = it.hasNext() ? it.next() : null; // TODO something better than null when no class is present
31+
// The results of task.analyze() are unreliable when errors are present
32+
// You can get at `Element` values using `Trees`
33+
task.analyze();
34+
} catch (IOException e) {
35+
throw new RuntimeException(e);
36+
}
37+
profiler.print();
38+
}
39+
40+
/**
41+
* Figure out what imports this file should have. Star-imports like `import java.util.*` are converted to individual
42+
* class imports. Missing imports are inferred by looking at imports in other source files.
43+
*/
44+
public FixImports fixImports() {
45+
// Check diagnostics for missing imports
46+
var unresolved = new HashSet<String>();
47+
for (var d : parent.diags) {
48+
if (d.getCode().equals("compiler.err.cant.resolve.location") && d.getSource().toUri().equals(file)) {
49+
long start = d.getStartPosition(), end = d.getEndPosition();
50+
var id = contents.substring((int) start, (int) end);
51+
if (id.matches("[A-Z]\\w+")) {
52+
unresolved.add(id);
53+
} else LOG.warning(id + " doesn't look like a class");
54+
} else if (d.getMessage(null).contains("cannot find to")) {
55+
var lines = d.getMessage(null).split("\n");
56+
var firstLine = lines.length > 0 ? lines[0] : "";
57+
LOG.warning(String.format("%s %s doesn't look like to-not-found", d.getCode(), firstLine));
58+
}
59+
}
60+
// Look at imports in other classes to help us guess how to fix imports
61+
var sourcePathImports = Parser.existingImports(parent.sourcePath);
62+
var classes = new HashSet<String>();
63+
classes.addAll(parent.jdkClasses.classes());
64+
classes.addAll(parent.classPathClasses.classes());
65+
var fixes = Parser.resolveSymbols(unresolved, sourcePathImports, classes);
66+
// Figure out which existing imports are actually used
67+
var trees = Trees.instance(task);
68+
var references = new HashSet<String>();
69+
class FindUsedImports extends TreePathScanner<Void, Void> {
70+
@Override
71+
public Void visitIdentifier(IdentifierTree node, Void nothing) {
72+
var e = trees.getElement(getCurrentPath());
73+
if (e instanceof TypeElement) {
74+
var t = (TypeElement) e;
75+
var qualifiedName = t.getQualifiedName().toString();
76+
var lastDot = qualifiedName.lastIndexOf('.');
77+
var packageName = lastDot == -1 ? "" : qualifiedName.substring(0, lastDot);
78+
var thisPackage = Objects.toString(root.getPackageName(), "");
79+
// java.lang.* and current package are imported by default
80+
if (!packageName.equals("java.lang")
81+
&& !packageName.equals(thisPackage)
82+
&& !packageName.equals("")) {
83+
references.add(qualifiedName);
84+
}
85+
}
86+
return null;
87+
}
88+
}
89+
new FindUsedImports().scan(root, null);
90+
// Take the intersection of existing imports ^ existing identifiers
91+
var qualifiedNames = new HashSet<String>();
92+
for (var i : root.getImports()) {
93+
var imported = i.getQualifiedIdentifier().toString();
94+
if (imported.endsWith(".*")) {
95+
var packageName = Parser.mostName(imported);
96+
var isUsed = references.stream().anyMatch(r -> r.startsWith(packageName));
97+
if (isUsed) qualifiedNames.add(imported);
98+
else LOG.warning("There are no references to package " + imported);
99+
} else {
100+
if (references.contains(imported)) qualifiedNames.add(imported);
101+
else LOG.warning("There are no references to class " + imported);
102+
}
103+
}
104+
// Add qualified names from fixes
105+
qualifiedNames.addAll(fixes.values());
106+
return new FixImports(root, trees.getSourcePositions(), qualifiedNames);
107+
}
108+
109+
private static final Logger LOG = Logger.getLogger("main");
110+
}

0 commit comments

Comments
 (0)