|
1 /* |
|
2 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. |
|
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
4 * |
|
5 * This code is free software; you can redistribute it and/or modify it |
|
6 * under the terms of the GNU General Public License version 2 only, as |
|
7 * published by the Free Software Foundation. Oracle designates this |
|
8 * particular file as subject to the "Classpath" exception as provided |
|
9 * by Oracle in the LICENSE file that accompanied this code. |
|
10 * |
|
11 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 * version 2 for more details (a copy is included in the LICENSE file that |
|
15 * accompanied this code). |
|
16 * |
|
17 * You should have received a copy of the GNU General Public License version |
|
18 * 2 along with this work; if not, write to the Free Software Foundation, |
|
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 * |
|
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
22 * or visit www.oracle.com if you need additional information or have any |
|
23 * questions. |
|
24 */ |
|
25 package jdk.internal.shellsupport.doc; |
|
26 |
|
27 import java.io.IOException; |
|
28 import java.net.URI; |
|
29 import java.net.URISyntaxException; |
|
30 import java.nio.file.Path; |
|
31 import java.util.ArrayList; |
|
32 import java.util.Arrays; |
|
33 import java.util.Collection; |
|
34 import java.util.Comparator; |
|
35 import java.util.HashMap; |
|
36 import java.util.HashSet; |
|
37 import java.util.IdentityHashMap; |
|
38 import java.util.Iterator; |
|
39 import java.util.List; |
|
40 import java.util.Map; |
|
41 import java.util.Map.Entry; |
|
42 import java.util.Objects; |
|
43 import java.util.Set; |
|
44 import java.util.Stack; |
|
45 import java.util.TreeMap; |
|
46 import java.util.stream.Collectors; |
|
47 import java.util.stream.Stream; |
|
48 |
|
49 import javax.lang.model.element.Element; |
|
50 import javax.lang.model.element.ElementKind; |
|
51 import javax.lang.model.element.ExecutableElement; |
|
52 import javax.lang.model.element.TypeElement; |
|
53 import javax.lang.model.element.VariableElement; |
|
54 import javax.lang.model.type.DeclaredType; |
|
55 import javax.lang.model.type.TypeKind; |
|
56 import javax.lang.model.util.ElementFilter; |
|
57 import javax.tools.JavaCompiler; |
|
58 import javax.tools.JavaFileManager; |
|
59 import javax.tools.JavaFileObject; |
|
60 import javax.tools.SimpleJavaFileObject; |
|
61 import javax.tools.StandardJavaFileManager; |
|
62 import javax.tools.StandardLocation; |
|
63 import javax.tools.ToolProvider; |
|
64 |
|
65 import com.sun.source.doctree.DocCommentTree; |
|
66 import com.sun.source.doctree.DocTree; |
|
67 import com.sun.source.doctree.InheritDocTree; |
|
68 import com.sun.source.doctree.ParamTree; |
|
69 import com.sun.source.doctree.ReturnTree; |
|
70 import com.sun.source.doctree.ThrowsTree; |
|
71 import com.sun.source.tree.ClassTree; |
|
72 import com.sun.source.tree.CompilationUnitTree; |
|
73 import com.sun.source.tree.MethodTree; |
|
74 import com.sun.source.tree.VariableTree; |
|
75 import com.sun.source.util.DocTreePath; |
|
76 import com.sun.source.util.DocTreeScanner; |
|
77 import com.sun.source.util.DocTrees; |
|
78 import com.sun.source.util.JavacTask; |
|
79 import com.sun.source.util.TreePath; |
|
80 import com.sun.source.util.TreePathScanner; |
|
81 import com.sun.source.util.Trees; |
|
82 import com.sun.tools.javac.api.JavacTaskImpl; |
|
83 import com.sun.tools.javac.util.DefinedBy; |
|
84 import com.sun.tools.javac.util.DefinedBy.Api; |
|
85 import com.sun.tools.javac.util.Pair; |
|
86 |
|
87 /**Helper to find javadoc and resolve @inheritDoc. |
|
88 */ |
|
89 public abstract class JavadocHelper implements AutoCloseable { |
|
90 private static final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); |
|
91 |
|
92 /**Create the helper. |
|
93 * |
|
94 * @param mainTask JavacTask from which the further Elements originate |
|
95 * @param sourceLocations paths where source files should be searched |
|
96 * @return a JavadocHelper |
|
97 */ |
|
98 public static JavadocHelper create(JavacTask mainTask, Collection<? extends Path> sourceLocations) { |
|
99 StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null); |
|
100 try { |
|
101 fm.setLocationFromPaths(StandardLocation.SOURCE_PATH, sourceLocations); |
|
102 return new OnDemandJavadocHelper(mainTask, fm); |
|
103 } catch (IOException ex) { |
|
104 try { |
|
105 fm.close(); |
|
106 } catch (IOException closeEx) { |
|
107 } |
|
108 return new JavadocHelper() { |
|
109 @Override |
|
110 public String getResolvedDocComment(Element forElement) throws IOException { |
|
111 return null; |
|
112 } |
|
113 @Override |
|
114 public Element getSourceElement(Element forElement) throws IOException { |
|
115 return forElement; |
|
116 } |
|
117 @Override |
|
118 public void close() throws IOException {} |
|
119 }; |
|
120 } |
|
121 } |
|
122 |
|
123 /**Returns javadoc for the given element, if it can be found, or null otherwise. The javadoc |
|
124 * will have @inheritDoc resolved. |
|
125 * |
|
126 * @param forElement element for which the javadoc should be searched |
|
127 * @return javadoc if found, null otherwise |
|
128 * @throws IOException if something goes wrong in the search |
|
129 */ |
|
130 public abstract String getResolvedDocComment(Element forElement) throws IOException; |
|
131 |
|
132 /**Returns an element representing the same given program element, but the returned element will |
|
133 * be resolved from source, if it can be found. Returns the original element if the source for |
|
134 * the given element cannot be found. |
|
135 * |
|
136 * @param forElement element for which the source element should be searched |
|
137 * @return source element if found, the original element otherwise |
|
138 * @throws IOException if something goes wrong in the search |
|
139 */ |
|
140 public abstract Element getSourceElement(Element forElement) throws IOException; |
|
141 |
|
142 /**Closes the helper. |
|
143 * |
|
144 * @throws IOException if something foes wrong during the close |
|
145 */ |
|
146 @Override |
|
147 public abstract void close() throws IOException; |
|
148 |
|
149 private static final class OnDemandJavadocHelper extends JavadocHelper { |
|
150 private final JavacTask mainTask; |
|
151 private final JavaFileManager baseFileManager; |
|
152 private final StandardJavaFileManager fm; |
|
153 private final Map<String, Pair<JavacTask, TreePath>> signature2Source = new HashMap<>(); |
|
154 |
|
155 private OnDemandJavadocHelper(JavacTask mainTask, StandardJavaFileManager fm) { |
|
156 this.mainTask = mainTask; |
|
157 this.baseFileManager = ((JavacTaskImpl) mainTask).getContext().get(JavaFileManager.class); |
|
158 this.fm = fm; |
|
159 } |
|
160 |
|
161 @Override |
|
162 public String getResolvedDocComment(Element forElement) throws IOException { |
|
163 Pair<JavacTask, TreePath> sourceElement = getSourceElement(mainTask, forElement); |
|
164 |
|
165 if (sourceElement == null) |
|
166 return null; |
|
167 |
|
168 return getResolvedDocComment(sourceElement.fst, sourceElement.snd); |
|
169 } |
|
170 |
|
171 @Override |
|
172 public Element getSourceElement(Element forElement) throws IOException { |
|
173 Pair<JavacTask, TreePath> sourceElement = getSourceElement(mainTask, forElement); |
|
174 |
|
175 if (sourceElement == null) |
|
176 return forElement; |
|
177 |
|
178 Element result = Trees.instance(sourceElement.fst).getElement(sourceElement.snd); |
|
179 |
|
180 if (result == null) |
|
181 return forElement; |
|
182 |
|
183 return result; |
|
184 } |
|
185 |
|
186 private String getResolvedDocComment(JavacTask task, TreePath el) throws IOException { |
|
187 DocTrees trees = DocTrees.instance(task); |
|
188 Element element = trees.getElement(el); |
|
189 String docComment = trees.getDocComment(el); |
|
190 |
|
191 if (docComment == null && element.getKind() == ElementKind.METHOD) { |
|
192 ExecutableElement executableElement = (ExecutableElement) element; |
|
193 Iterable<Element> superTypes = |
|
194 () -> superTypeForInheritDoc(task, element.getEnclosingElement()).iterator(); |
|
195 for (Element sup : superTypes) { |
|
196 for (ExecutableElement supMethod : ElementFilter.methodsIn(sup.getEnclosedElements())) { |
|
197 TypeElement clazz = (TypeElement) executableElement.getEnclosingElement(); |
|
198 if (task.getElements().overrides(executableElement, supMethod, clazz)) { |
|
199 Pair<JavacTask, TreePath> source = getSourceElement(task, supMethod); |
|
200 |
|
201 if (source != null) { |
|
202 String overriddenComment = getResolvedDocComment(source.fst, source.snd); |
|
203 |
|
204 if (overriddenComment != null) { |
|
205 return overriddenComment; |
|
206 } |
|
207 } |
|
208 } |
|
209 } |
|
210 } |
|
211 } |
|
212 |
|
213 DocCommentTree docCommentTree = parseDocComment(task, docComment); |
|
214 IOException[] exception = new IOException[1]; |
|
215 Map<int[], String> replace = new TreeMap<>((span1, span2) -> span2[0] - span1[0]); |
|
216 |
|
217 new DocTreeScanner<Void, Void>() { |
|
218 private Stack<DocTree> interestingParent = new Stack<>(); |
|
219 private DocCommentTree dcTree; |
|
220 private JavacTask inheritedJavacTask; |
|
221 private TreePath inheritedTreePath; |
|
222 private String inherited; |
|
223 private Map<DocTree, String> syntheticTrees = new IdentityHashMap<>(); |
|
224 private long lastPos = 0; |
|
225 @Override @DefinedBy(Api.COMPILER_TREE) |
|
226 public Void visitDocComment(DocCommentTree node, Void p) { |
|
227 dcTree = node; |
|
228 interestingParent.push(node); |
|
229 try { |
|
230 scan(node.getFirstSentence(), p); |
|
231 scan(node.getBody(), p); |
|
232 List<DocTree> augmentedBlockTags = new ArrayList<>(node.getBlockTags()); |
|
233 if (element.getKind() == ElementKind.METHOD) { |
|
234 ExecutableElement executableElement = (ExecutableElement) element; |
|
235 List<String> parameters = |
|
236 executableElement.getParameters() |
|
237 .stream() |
|
238 .map(param -> param.getSimpleName().toString()) |
|
239 .collect(Collectors.toList()); |
|
240 List<String> throwsList = |
|
241 executableElement.getThrownTypes() |
|
242 .stream() |
|
243 .map(exc -> exc.toString()) |
|
244 .collect(Collectors.toList()); |
|
245 Set<String> missingParams = new HashSet<>(parameters); |
|
246 Set<String> missingThrows = new HashSet<>(throwsList); |
|
247 boolean hasReturn = false; |
|
248 |
|
249 for (DocTree dt : augmentedBlockTags) { |
|
250 switch (dt.getKind()) { |
|
251 case PARAM: |
|
252 missingParams.remove(((ParamTree) dt).getName().getName().toString()); |
|
253 break; |
|
254 case THROWS: |
|
255 missingThrows.remove(getThrownException(task, el, docCommentTree, (ThrowsTree) dt)); |
|
256 break; |
|
257 case RETURN: |
|
258 hasReturn = true; |
|
259 break; |
|
260 } |
|
261 } |
|
262 |
|
263 for (String missingParam : missingParams) { |
|
264 DocTree syntheticTag = parseBlockTag(task, "@param " + missingParam + " {@inheritDoc}"); |
|
265 syntheticTrees.put(syntheticTag, "@param " + missingParam + " "); |
|
266 insertTag(augmentedBlockTags, syntheticTag, parameters, throwsList); |
|
267 } |
|
268 |
|
269 for (String missingThrow : missingThrows) { |
|
270 DocTree syntheticTag = parseBlockTag(task, "@throws " + missingThrow + " {@inheritDoc}"); |
|
271 syntheticTrees.put(syntheticTag, "@throws " + missingThrow + " "); |
|
272 insertTag(augmentedBlockTags, syntheticTag, parameters, throwsList); |
|
273 } |
|
274 |
|
275 if (!hasReturn) { |
|
276 DocTree syntheticTag = parseBlockTag(task, "@return {@inheritDoc}"); |
|
277 syntheticTrees.put(syntheticTag, "@return "); |
|
278 insertTag(augmentedBlockTags, syntheticTag, parameters, throwsList); |
|
279 } |
|
280 } |
|
281 scan(augmentedBlockTags, p); |
|
282 return null; |
|
283 } finally { |
|
284 interestingParent.pop(); |
|
285 } |
|
286 } |
|
287 @Override @DefinedBy(Api.COMPILER_TREE) |
|
288 public Void visitParam(ParamTree node, Void p) { |
|
289 interestingParent.push(node); |
|
290 try { |
|
291 return super.visitParam(node, p); |
|
292 } finally { |
|
293 interestingParent.pop(); |
|
294 } |
|
295 } |
|
296 @Override @DefinedBy(Api.COMPILER_TREE) |
|
297 public Void visitThrows(ThrowsTree node, Void p) { |
|
298 interestingParent.push(node); |
|
299 try { |
|
300 return super.visitThrows(node, p); |
|
301 } finally { |
|
302 interestingParent.pop(); |
|
303 } |
|
304 } |
|
305 @Override @DefinedBy(Api.COMPILER_TREE) |
|
306 public Void visitReturn(ReturnTree node, Void p) { |
|
307 interestingParent.push(node); |
|
308 try { |
|
309 return super.visitReturn(node, p); |
|
310 } finally { |
|
311 interestingParent.pop(); |
|
312 } |
|
313 } |
|
314 @Override @DefinedBy(Api.COMPILER_TREE) |
|
315 public Void visitInheritDoc(InheritDocTree node, Void p) { |
|
316 if (inherited == null) { |
|
317 try { |
|
318 if (element.getKind() == ElementKind.METHOD) { |
|
319 ExecutableElement executableElement = (ExecutableElement) element; |
|
320 Iterable<Element> superTypes = () -> superTypeForInheritDoc(task, element.getEnclosingElement()).iterator(); |
|
321 OUTER: for (Element sup : superTypes) { |
|
322 for (ExecutableElement supMethod : ElementFilter.methodsIn(sup.getEnclosedElements())) { |
|
323 if (task.getElements().overrides(executableElement, supMethod, (TypeElement) executableElement.getEnclosingElement())) { |
|
324 Pair<JavacTask, TreePath> source = getSourceElement(task, supMethod); |
|
325 |
|
326 if (source != null) { |
|
327 String overriddenComment = getResolvedDocComment(source.fst, source.snd); |
|
328 |
|
329 if (overriddenComment != null) { |
|
330 inheritedJavacTask = source.fst; |
|
331 inheritedTreePath = source.snd; |
|
332 inherited = overriddenComment; |
|
333 break OUTER; |
|
334 } |
|
335 } |
|
336 } |
|
337 } |
|
338 } |
|
339 } |
|
340 } catch (IOException ex) { |
|
341 exception[0] = ex; |
|
342 return null; |
|
343 } |
|
344 } |
|
345 if (inherited == null) { |
|
346 return null; |
|
347 } |
|
348 DocCommentTree inheritedDocTree = parseDocComment(inheritedJavacTask, inherited); |
|
349 List<List<? extends DocTree>> inheritedText = new ArrayList<>(); |
|
350 DocTree parent = interestingParent.peek(); |
|
351 switch (parent.getKind()) { |
|
352 case DOC_COMMENT: |
|
353 inheritedText.add(inheritedDocTree.getFullBody()); |
|
354 break; |
|
355 case PARAM: |
|
356 String paramName = ((ParamTree) parent).getName().getName().toString(); |
|
357 new DocTreeScanner<Void, Void>() { |
|
358 @Override @DefinedBy(Api.COMPILER_TREE) |
|
359 public Void visitParam(ParamTree node, Void p) { |
|
360 if (node.getName().getName().contentEquals(paramName)) { |
|
361 inheritedText.add(node.getDescription()); |
|
362 } |
|
363 return super.visitParam(node, p); |
|
364 } |
|
365 }.scan(inheritedDocTree, null); |
|
366 break; |
|
367 case THROWS: |
|
368 String thrownName = getThrownException(task, el, docCommentTree, (ThrowsTree) parent); |
|
369 new DocTreeScanner<Void, Void>() { |
|
370 @Override @DefinedBy(Api.COMPILER_TREE) |
|
371 public Void visitThrows(ThrowsTree node, Void p) { |
|
372 if (Objects.equals(getThrownException(inheritedJavacTask, inheritedTreePath, inheritedDocTree, node), thrownName)) { |
|
373 inheritedText.add(node.getDescription()); |
|
374 } |
|
375 return super.visitThrows(node, p); |
|
376 } |
|
377 }.scan(inheritedDocTree, null); |
|
378 break; |
|
379 case RETURN: |
|
380 new DocTreeScanner<Void, Void>() { |
|
381 @Override @DefinedBy(Api.COMPILER_TREE) |
|
382 public Void visitReturn(ReturnTree node, Void p) { |
|
383 inheritedText.add(node.getDescription()); |
|
384 return super.visitReturn(node, p); |
|
385 } |
|
386 }.scan(inheritedDocTree, null); |
|
387 break; |
|
388 } |
|
389 if (!inheritedText.isEmpty()) { |
|
390 long offset = trees.getSourcePositions().getStartPosition(null, inheritedDocTree, inheritedDocTree); |
|
391 long start = Long.MAX_VALUE; |
|
392 long end = Long.MIN_VALUE; |
|
393 |
|
394 for (DocTree t : inheritedText.get(0)) { |
|
395 start = Math.min(start, trees.getSourcePositions().getStartPosition(null, inheritedDocTree, t) - offset); |
|
396 end = Math.max(end, trees.getSourcePositions().getEndPosition(null, inheritedDocTree, t) - offset); |
|
397 } |
|
398 String text = inherited.substring((int) start, (int) end); |
|
399 |
|
400 if (syntheticTrees.containsKey(parent)) { |
|
401 replace.put(new int[] {(int) lastPos + 1, (int) lastPos}, "\n" + syntheticTrees.get(parent) + text); |
|
402 } else { |
|
403 long inheritedStart = trees.getSourcePositions().getStartPosition(null, dcTree, node); |
|
404 long inheritedEnd = trees.getSourcePositions().getEndPosition(null, dcTree, node); |
|
405 |
|
406 replace.put(new int[] {(int) inheritedStart, (int) inheritedEnd}, text); |
|
407 } |
|
408 } |
|
409 return super.visitInheritDoc(node, p); |
|
410 } |
|
411 private boolean inSynthetic; |
|
412 @Override @DefinedBy(Api.COMPILER_TREE) |
|
413 public Void scan(DocTree tree, Void p) { |
|
414 if (exception[0] != null) { |
|
415 return null; |
|
416 } |
|
417 boolean prevInSynthetic = inSynthetic; |
|
418 try { |
|
419 inSynthetic |= syntheticTrees.containsKey(tree); |
|
420 return super.scan(tree, p); |
|
421 } finally { |
|
422 if (!inSynthetic) { |
|
423 lastPos = trees.getSourcePositions().getEndPosition(null, dcTree, tree); |
|
424 } |
|
425 inSynthetic = prevInSynthetic; |
|
426 } |
|
427 } |
|
428 |
|
429 private void insertTag(List<DocTree> tags, DocTree toInsert, List<String> parameters, List<String> throwsTypes) { |
|
430 Comparator<DocTree> comp = (tag1, tag2) -> { |
|
431 if (tag1.getKind() == tag2.getKind()) { |
|
432 switch (toInsert.getKind()) { |
|
433 case PARAM: { |
|
434 ParamTree p1 = (ParamTree) tag1; |
|
435 ParamTree p2 = (ParamTree) tag2; |
|
436 int i1 = parameters.indexOf(p1.getName().getName().toString()); |
|
437 int i2 = parameters.indexOf(p2.getName().getName().toString()); |
|
438 |
|
439 return i1 - i2; |
|
440 } |
|
441 case THROWS: { |
|
442 ThrowsTree t1 = (ThrowsTree) tag1; |
|
443 ThrowsTree t2 = (ThrowsTree) tag2; |
|
444 int i1 = throwsTypes.indexOf(getThrownException(task, el, docCommentTree, t1)); |
|
445 int i2 = throwsTypes.indexOf(getThrownException(task, el, docCommentTree, t2)); |
|
446 |
|
447 return i1 - i2; |
|
448 } |
|
449 } |
|
450 } |
|
451 |
|
452 int i1 = tagOrder.indexOf(tag1.getKind()); |
|
453 int i2 = tagOrder.indexOf(tag2.getKind()); |
|
454 |
|
455 return i1 - i2; |
|
456 }; |
|
457 |
|
458 for (int i = 0; i < tags.size(); i++) { |
|
459 if (comp.compare(tags.get(i), toInsert) >= 0) { |
|
460 tags.add(i, toInsert); |
|
461 return ; |
|
462 } |
|
463 } |
|
464 tags.add(toInsert); |
|
465 } |
|
466 |
|
467 private final List<DocTree.Kind> tagOrder = Arrays.asList(DocTree.Kind.PARAM, DocTree.Kind.THROWS, DocTree.Kind.RETURN); |
|
468 }.scan(docCommentTree, null); |
|
469 |
|
470 if (replace.isEmpty()) |
|
471 return docComment; |
|
472 |
|
473 StringBuilder replacedInheritDoc = new StringBuilder(docComment); |
|
474 int offset = (int) trees.getSourcePositions().getStartPosition(null, docCommentTree, docCommentTree); |
|
475 |
|
476 for (Entry<int[], String> e : replace.entrySet()) { |
|
477 replacedInheritDoc.delete(e.getKey()[0] - offset, e.getKey()[1] - offset + 1); |
|
478 replacedInheritDoc.insert(e.getKey()[0] - offset, e.getValue()); |
|
479 } |
|
480 |
|
481 return replacedInheritDoc.toString(); |
|
482 } |
|
483 |
|
484 private Stream<Element> superTypeForInheritDoc(JavacTask task, Element type) { |
|
485 TypeElement clazz = (TypeElement) type; |
|
486 Stream<Element> result = interfaces(clazz); |
|
487 result = Stream.concat(result, interfaces(clazz).flatMap(el -> superTypeForInheritDoc(task, el))); |
|
488 |
|
489 if (clazz.getSuperclass().getKind() == TypeKind.DECLARED) { |
|
490 Element superClass = ((DeclaredType) clazz.getSuperclass()).asElement(); |
|
491 result = Stream.concat(result, Stream.of(superClass)); |
|
492 result = Stream.concat(result, superTypeForInheritDoc(task, superClass)); |
|
493 } |
|
494 |
|
495 return result; |
|
496 } |
|
497 //where: |
|
498 private Stream<Element> interfaces(TypeElement clazz) { |
|
499 return clazz.getInterfaces() |
|
500 .stream() |
|
501 .filter(tm -> tm.getKind() == TypeKind.DECLARED) |
|
502 .map(tm -> ((DeclaredType) tm).asElement()); |
|
503 } |
|
504 |
|
505 private DocTree parseBlockTag(JavacTask task, String blockTag) { |
|
506 DocCommentTree dc = parseDocComment(task, blockTag); |
|
507 |
|
508 return dc.getBlockTags().get(0); |
|
509 } |
|
510 |
|
511 private DocCommentTree parseDocComment(JavacTask task, String javadoc) { |
|
512 DocTrees trees = DocTrees.instance(task); |
|
513 try { |
|
514 return trees.getDocCommentTree(new SimpleJavaFileObject(new URI("mem://doc.html"), javax.tools.JavaFileObject.Kind.HTML) { |
|
515 @Override @DefinedBy(Api.COMPILER) |
|
516 public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { |
|
517 return "<body>" + javadoc + "</body>"; |
|
518 } |
|
519 }); |
|
520 } catch (URISyntaxException ex) { |
|
521 return null; |
|
522 } |
|
523 } |
|
524 |
|
525 private String getThrownException(JavacTask task, TreePath rootOn, DocCommentTree comment, ThrowsTree tt) { |
|
526 DocTrees trees = DocTrees.instance(task); |
|
527 Element exc = trees.getElement(new DocTreePath(new DocTreePath(rootOn, comment), tt.getExceptionName())); |
|
528 return exc != null ? exc.toString() : null; |
|
529 } |
|
530 |
|
531 private Pair<JavacTask, TreePath> getSourceElement(JavacTask origin, Element el) throws IOException { |
|
532 String handle = elementSignature(el); |
|
533 Pair<JavacTask, TreePath> cached = signature2Source.get(handle); |
|
534 |
|
535 if (cached != null) { |
|
536 return cached.fst != null ? cached : null; |
|
537 } |
|
538 |
|
539 TypeElement type = topLevelType(el); |
|
540 |
|
541 if (type == null) |
|
542 return null; |
|
543 |
|
544 String binaryName = origin.getElements().getBinaryName(type).toString(); |
|
545 Pair<JavacTask, CompilationUnitTree> source = findSource(binaryName); |
|
546 |
|
547 if (source == null) |
|
548 return null; |
|
549 |
|
550 fillElementCache(source.fst, source.snd); |
|
551 |
|
552 cached = signature2Source.get(handle); |
|
553 |
|
554 if (cached != null) { |
|
555 return cached; |
|
556 } else { |
|
557 signature2Source.put(handle, Pair.of(null, null)); |
|
558 return null; |
|
559 } |
|
560 } |
|
561 //where: |
|
562 private String elementSignature(Element el) { |
|
563 switch (el.getKind()) { |
|
564 case ANNOTATION_TYPE: case CLASS: case ENUM: case INTERFACE: |
|
565 return ((TypeElement) el).getQualifiedName().toString(); |
|
566 case FIELD: |
|
567 return elementSignature(el.getEnclosingElement()) + "." + el.getSimpleName() + ":" + el.asType(); |
|
568 case ENUM_CONSTANT: |
|
569 return elementSignature(el.getEnclosingElement()) + "." + el.getSimpleName(); |
|
570 case EXCEPTION_PARAMETER: case LOCAL_VARIABLE: case PARAMETER: case RESOURCE_VARIABLE: |
|
571 return el.getSimpleName() + ":" + el.asType(); |
|
572 case CONSTRUCTOR: case METHOD: |
|
573 StringBuilder header = new StringBuilder(); |
|
574 header.append(elementSignature(el.getEnclosingElement())); |
|
575 if (el.getKind() == ElementKind.METHOD) { |
|
576 header.append("."); |
|
577 header.append(el.getSimpleName()); |
|
578 } |
|
579 header.append("("); |
|
580 String sep = ""; |
|
581 ExecutableElement method = (ExecutableElement) el; |
|
582 for (Iterator<? extends VariableElement> i = method.getParameters().iterator(); i.hasNext();) { |
|
583 VariableElement p = i.next(); |
|
584 header.append(sep); |
|
585 header.append(p.asType()); |
|
586 sep = ", "; |
|
587 } |
|
588 header.append(")"); |
|
589 return header.toString(); |
|
590 default: |
|
591 return el.toString(); |
|
592 } |
|
593 } |
|
594 |
|
595 private TypeElement topLevelType(Element el) { |
|
596 if (el.getKind() == ElementKind.PACKAGE) |
|
597 return null; |
|
598 |
|
599 while (el != null && el.getEnclosingElement().getKind() != ElementKind.PACKAGE) { |
|
600 el = el.getEnclosingElement(); |
|
601 } |
|
602 |
|
603 return el != null && (el.getKind().isClass() || el.getKind().isInterface()) ? (TypeElement) el : null; |
|
604 } |
|
605 |
|
606 private void fillElementCache(JavacTask task, CompilationUnitTree cut) throws IOException { |
|
607 Trees trees = Trees.instance(task); |
|
608 |
|
609 new TreePathScanner<Void, Void>() { |
|
610 @Override @DefinedBy(Api.COMPILER_TREE) |
|
611 public Void visitMethod(MethodTree node, Void p) { |
|
612 handleDeclaration(); |
|
613 return null; |
|
614 } |
|
615 |
|
616 @Override @DefinedBy(Api.COMPILER_TREE) |
|
617 public Void visitClass(ClassTree node, Void p) { |
|
618 handleDeclaration(); |
|
619 return super.visitClass(node, p); |
|
620 } |
|
621 |
|
622 @Override @DefinedBy(Api.COMPILER_TREE) |
|
623 public Void visitVariable(VariableTree node, Void p) { |
|
624 handleDeclaration(); |
|
625 return super.visitVariable(node, p); |
|
626 } |
|
627 |
|
628 private void handleDeclaration() { |
|
629 Element currentElement = trees.getElement(getCurrentPath()); |
|
630 |
|
631 if (currentElement != null) { |
|
632 signature2Source.put(elementSignature(currentElement), Pair.of(task, getCurrentPath())); |
|
633 } |
|
634 } |
|
635 }.scan(cut, null); |
|
636 } |
|
637 |
|
638 private Pair<JavacTask, CompilationUnitTree> findSource(String binaryName) throws IOException { |
|
639 JavaFileObject jfo = fm.getJavaFileForInput(StandardLocation.SOURCE_PATH, |
|
640 binaryName, |
|
641 JavaFileObject.Kind.SOURCE); |
|
642 |
|
643 if (jfo == null) |
|
644 return null; |
|
645 |
|
646 List<JavaFileObject> jfos = Arrays.asList(jfo); |
|
647 JavacTaskImpl task = (JavacTaskImpl) compiler.getTask(null, baseFileManager, d -> {}, null, null, jfos); |
|
648 Iterable<? extends CompilationUnitTree> cuts = task.parse(); |
|
649 |
|
650 task.enter(); |
|
651 |
|
652 return Pair.of(task, cuts.iterator().next()); |
|
653 } |
|
654 |
|
655 @Override |
|
656 public void close() throws IOException { |
|
657 fm.close(); |
|
658 } |
|
659 } |
|
660 |
|
661 } |