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