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