JavadocHelper.java revision 3738:6ef8a1453577
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 */
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.TypeElement;
53import javax.lang.model.element.VariableElement;
54import javax.lang.model.type.DeclaredType;
55import javax.lang.model.type.TypeKind;
56import javax.lang.model.util.ElementFilter;
57import javax.tools.JavaCompiler;
58import javax.tools.JavaFileManager;
59import javax.tools.JavaFileObject;
60import javax.tools.SimpleJavaFileObject;
61import javax.tools.StandardJavaFileManager;
62import javax.tools.StandardLocation;
63import javax.tools.ToolProvider;
64
65import com.sun.source.doctree.DocCommentTree;
66import com.sun.source.doctree.DocTree;
67import com.sun.source.doctree.InheritDocTree;
68import com.sun.source.doctree.ParamTree;
69import com.sun.source.doctree.ReturnTree;
70import com.sun.source.doctree.ThrowsTree;
71import com.sun.source.tree.ClassTree;
72import com.sun.source.tree.CompilationUnitTree;
73import com.sun.source.tree.MethodTree;
74import com.sun.source.tree.VariableTree;
75import com.sun.source.util.DocTreePath;
76import com.sun.source.util.DocTreeScanner;
77import com.sun.source.util.DocTrees;
78import com.sun.source.util.JavacTask;
79import com.sun.source.util.TreePath;
80import com.sun.source.util.TreePathScanner;
81import com.sun.source.util.Trees;
82import com.sun.tools.javac.api.JavacTaskImpl;
83import com.sun.tools.javac.util.DefinedBy;
84import com.sun.tools.javac.util.DefinedBy.Api;
85import com.sun.tools.javac.util.Pair;
86
87/**Helper to find javadoc and resolve @inheritDoc.
88 */
89public 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}
662