Repository navigation
Expand file tree
/
Copy pathDocs.java
More file actions
235 lines (208 loc) · 9.26 KB
/
Copy pathDocs.java
File metadata and controls
235 lines (208 loc) · 9.26 KB
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
package org.javacs;
import com.sun.source.doctree.DocCommentTree;
import com.sun.source.tree.*;
import com.sun.source.util.DocTrees;
import com.sun.source.util.TreeScanner;
import com.sun.source.util.Trees;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.util.*;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.tools.*;
public class Docs {
/** File manager with source-path + platform sources, which we will use to look up individual source files */
private final StandardJavaFileManager fileManager;
private static Path srcZip() {
try {
var fs = FileSystems.newFileSystem(Lib.SRC_ZIP, Docs.class.getClassLoader());
return fs.getPath("/");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
Docs(Set<Path> sourcePath) {
this.fileManager =
ServiceLoader.load(JavaCompiler.class).iterator().next().getStandardFileManager(__ -> {}, null, null);
// Compute the full source path, including src.zip for platform classes
var sourcePathFiles = sourcePath.stream().map(Path::toFile).collect(Collectors.toSet());
try {
fileManager.setLocation(StandardLocation.SOURCE_PATH, sourcePathFiles);
fileManager.setLocationFromPaths(StandardLocation.MODULE_SOURCE_PATH, Set.of(srcZip()));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/** Look up the javadoc associated with `method` */
public Optional<DocCommentTree> methodDoc(ExecutableElement method) {
var classElement = (TypeElement) method.getEnclosingElement();
var className = classElement.getQualifiedName().toString();
var methodName = method.getSimpleName().toString();
return memberDoc(className, methodName);
}
/** Find and root the source code associated with `method` */
public Optional<MethodTree> methodTree(ExecutableElement method) {
var classElement = (TypeElement) method.getEnclosingElement();
var className = classElement.getQualifiedName().toString();
var methodName = method.getSimpleName().toString();
var parameterTypes =
method.getParameters().stream().map(p -> p.asType().toString()).collect(Collectors.toList());
return findMethod(className, methodName, parameterTypes);
}
/** Look up the javadoc associated with `type` */
public Optional<DocCommentTree> classDoc(TypeElement type) {
return classDoc(type.getQualifiedName().toString());
}
public Optional<DocCommentTree> classDoc(String qualifiedName) {
Objects.requireNonNull(qualifiedName);
return findDoc(qualifiedName, null);
}
private Optional<JavaFileObject> file(String className) {
try {
var fromSourcePath =
fileManager.getJavaFileForInput(
StandardLocation.SOURCE_PATH, className, JavaFileObject.Kind.SOURCE);
if (fromSourcePath != null) return Optional.of(fromSourcePath);
for (var module : Classes.JDK_MODULES) {
var moduleLocation = fileManager.getLocationForModule(StandardLocation.MODULE_SOURCE_PATH, module);
if (moduleLocation == null) continue;
var fromModuleSourcePath =
fileManager.getJavaFileForInput(moduleLocation, className, JavaFileObject.Kind.SOURCE);
if (fromModuleSourcePath != null) return Optional.of(fromModuleSourcePath);
}
return Optional.empty();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private boolean memberNameEquals(Tree member, String name) {
if (member instanceof VariableTree) {
var variable = (VariableTree) member;
return variable.getName().contentEquals(name);
} else if (member instanceof MethodTree) {
var method = (MethodTree) member;
return method.getName().contentEquals(name);
} else return false;
}
private Optional<DocCommentTree> findDoc(String className, String memberName) {
var file = file(className);
if (!file.isPresent()) return Optional.empty();
var task = Parser.parseTask(file.get());
CompilationUnitTree root;
try {
var it = task.parse().iterator();
if (!it.hasNext()) {
LOG.warning("Found no CompilationUnitTree in " + file);
return Optional.empty();
}
root = it.next();
} catch (IOException e) {
throw new RuntimeException(e);
}
var docs = DocTrees.instance(task);
var trees = Trees.instance(task);
class Find extends TreeScanner<Void, Void> {
Optional<DocCommentTree> result = Optional.empty();
@Override
public Void visitClass(ClassTree node, Void aVoid) {
// TODO this will be wrong when inner class has same name as top-level class
if (node.getSimpleName().contentEquals(Parser.lastName(className))) {
if (memberName == null) {
var path = trees.getPath(root, node);
result = Optional.ofNullable(docs.getDocCommentTree(path));
} else {
for (var member : node.getMembers()) {
if (memberNameEquals(member, memberName)) {
var path = trees.getPath(root, member);
result = Optional.ofNullable(docs.getDocCommentTree(path));
}
}
}
}
return null;
}
}
var find = new Find();
find.scan(root, null);
return find.result;
}
Optional<DocCommentTree> memberDoc(String className, String memberName) {
Objects.requireNonNull(className);
Objects.requireNonNull(memberName);
return findDoc(className, memberName);
}
private boolean sameMethod(MethodTree candidate, String methodName, List<String> parameterTypes) {
if (!candidate.getName().contentEquals(methodName)) return false;
var params = candidate.getParameters();
if (params.size() != parameterTypes.size()) return false;
for (int i = 0; i < params.size(); i++) {
var expect = parameterTypes.get(i);
var expectSimple = Parser.lastName(expect);
var p = params.get(i);
var t = p.getType();
if (!(t instanceof IdentifierTree)) {
LOG.warning(
"Parameter " + p.getName() + " of method " + candidate.getName() + " is not an IdentifierTree");
return false;
}
var id = (IdentifierTree) t;
var simple = Parser.lastName(id.getName().toString());
if (!simple.equals(expectSimple)) return false;
}
return true;
}
private Optional<MethodTree> findMethod(String className, String methodName, List<String> parameterTypes) {
Objects.requireNonNull(className);
Objects.requireNonNull(methodName);
var file = file(className);
if (!file.isPresent()) return Optional.empty();
var task = Parser.parseTask(file.get());
CompilationUnitTree root;
try {
root = task.parse().iterator().next();
} catch (IOException e) {
throw new RuntimeException(e);
}
class Find extends TreeScanner<Void, Void> {
Optional<MethodTree> result = Optional.empty();
@Override
public Void visitClass(ClassTree node, Void aVoid) {
// TODO this will be wrong when inner class has same name as top-level class
if (node.getSimpleName().contentEquals(Parser.lastName(className))) {
for (var member : node.getMembers()) {
if (member instanceof MethodTree) {
var method = (MethodTree) member;
if (sameMethod(method, methodName, parameterTypes)) result = Optional.of(method);
}
}
}
return null;
}
}
var find = new Find();
find.scan(root, null);
return find.result;
}
private static final Pattern HTML_TAG = Pattern.compile("<(\\w+)>");
private static boolean isHtml(String text) {
var tags = HTML_TAG.matcher(text);
while (tags.find()) {
var tag = tags.group(1);
var close = String.format("</%s>", tag);
var findClose = text.indexOf(close, tags.end());
if (findClose != -1) return true;
}
return false;
}
/** If `commentText` looks like HTML, convert it to markdown */
static String htmlToMarkdown(String commentText) {
if (isHtml(commentText)) {
return TipFormatter.asMarkdown(commentText);
} else return commentText;
}
private static final Logger LOG = Logger.getLogger("main");
}