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