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