1 /*
   2  * Copyright (c) 1998, 2019, 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 
  26 package jdk.javadoc.internal.doclets.formats.html;
  27 
  28 import java.util.ArrayList;
  29 import java.util.Collections;
  30 import java.util.LinkedList;
  31 import java.util.List;
  32 import java.util.ListIterator;
  33 import java.util.Locale;
  34 import java.util.Map;
  35 import java.util.Set;
  36 import java.util.regex.Matcher;
  37 import java.util.regex.Pattern;
  38 
  39 import javax.lang.model.element.AnnotationMirror;
  40 import javax.lang.model.element.AnnotationValue;
  41 import javax.lang.model.element.Element;
  42 import javax.lang.model.element.ElementKind;
  43 import javax.lang.model.element.ExecutableElement;
  44 import javax.lang.model.element.ModuleElement;
  45 import javax.lang.model.element.Name;
  46 import javax.lang.model.element.PackageElement;
  47 import javax.lang.model.element.QualifiedNameable;
  48 import javax.lang.model.element.TypeElement;
  49 import javax.lang.model.element.VariableElement;
  50 import javax.lang.model.type.DeclaredType;
  51 import javax.lang.model.type.TypeMirror;
  52 import javax.lang.model.util.SimpleAnnotationValueVisitor9;
  53 import javax.lang.model.util.SimpleElementVisitor9;
  54 import javax.lang.model.util.SimpleTypeVisitor9;
  55 
  56 import com.sun.source.doctree.AttributeTree;
  57 import com.sun.source.doctree.AttributeTree.ValueKind;
  58 import com.sun.source.doctree.CommentTree;
  59 import com.sun.source.doctree.DocRootTree;
  60 import com.sun.source.doctree.DocTree;
  61 import com.sun.source.doctree.DocTree.Kind;
  62 import com.sun.source.doctree.EndElementTree;
  63 import com.sun.source.doctree.EntityTree;
  64 import com.sun.source.doctree.ErroneousTree;
  65 import com.sun.source.doctree.IndexTree;
  66 import com.sun.source.doctree.InheritDocTree;
  67 import com.sun.source.doctree.LinkTree;
  68 import com.sun.source.doctree.LiteralTree;
  69 import com.sun.source.doctree.SeeTree;
  70 import com.sun.source.doctree.StartElementTree;
  71 import com.sun.source.doctree.SummaryTree;
  72 import com.sun.source.doctree.SystemPropertyTree;
  73 import com.sun.source.doctree.TextTree;
  74 import com.sun.source.util.SimpleDocTreeVisitor;
  75 import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder;
  76 import jdk.javadoc.internal.doclets.formats.html.markup.Entity;
  77 import jdk.javadoc.internal.doclets.formats.html.markup.Head;
  78 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlAttr;
  79 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlDocument;
  80 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle;
  81 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag;
  82 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree;
  83 import jdk.javadoc.internal.doclets.formats.html.markup.Links;
  84 import jdk.javadoc.internal.doclets.formats.html.markup.RawHtml;
  85 import jdk.javadoc.internal.doclets.formats.html.markup.Script;
  86 import jdk.javadoc.internal.doclets.formats.html.markup.StringContent;
  87 import jdk.javadoc.internal.doclets.formats.html.markup.TableHeader;
  88 import jdk.javadoc.internal.doclets.toolkit.AnnotationTypeWriter;
  89 import jdk.javadoc.internal.doclets.toolkit.ClassWriter;
  90 import jdk.javadoc.internal.doclets.toolkit.Content;
  91 import jdk.javadoc.internal.doclets.toolkit.Messages;
  92 import jdk.javadoc.internal.doclets.toolkit.PackageSummaryWriter;
  93 import jdk.javadoc.internal.doclets.toolkit.Resources;
  94 import jdk.javadoc.internal.doclets.toolkit.taglets.DocRootTaglet;
  95 import jdk.javadoc.internal.doclets.toolkit.taglets.TagletWriter;
  96 import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper;
  97 import jdk.javadoc.internal.doclets.toolkit.util.DocFile;
  98 import jdk.javadoc.internal.doclets.toolkit.util.DocFileIOException;
  99 import jdk.javadoc.internal.doclets.toolkit.util.DocLink;
 100 import jdk.javadoc.internal.doclets.toolkit.util.DocPath;
 101 import jdk.javadoc.internal.doclets.toolkit.util.DocPaths;
 102 import jdk.javadoc.internal.doclets.toolkit.util.DocletConstants;
 103 import jdk.javadoc.internal.doclets.toolkit.util.Utils;
 104 import jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberTable;
 105 
 106 import static com.sun.source.doctree.DocTree.Kind.CODE;
 107 import static com.sun.source.doctree.DocTree.Kind.COMMENT;
 108 import static com.sun.source.doctree.DocTree.Kind.LINK;
 109 import static com.sun.source.doctree.DocTree.Kind.LINK_PLAIN;
 110 import static com.sun.source.doctree.DocTree.Kind.SEE;
 111 import static com.sun.source.doctree.DocTree.Kind.TEXT;
 112 import static jdk.javadoc.internal.doclets.toolkit.util.CommentHelper.SPACER;
 113 
 114 
 115 /**
 116  * Class for the Html Format Code Generation specific to JavaDoc.
 117  * This Class contains methods related to the Html Code Generation which
 118  * are used extensively while generating the entire documentation.
 119  *
 120  *  <p><b>This is NOT part of any supported API.
 121  *  If you write code that depends on this, you do so at your own risk.
 122  *  This code and its internal interfaces are subject to change or
 123  *  deletion without notice.</b>
 124  *
 125  * @author Atul M Dambalkar
 126  * @author Robert Field
 127  * @author Bhavesh Patel (Modified)
 128  */
 129 public class HtmlDocletWriter {
 130 
 131     /**
 132      * Relative path from the file getting generated to the destination
 133      * directory. For example, if the file getting generated is
 134      * "java/lang/Object.html", then the path to the root is "../..".
 135      * This string can be empty if the file getting generated is in
 136      * the destination directory.
 137      */
 138     public final DocPath pathToRoot;
 139 
 140     /**
 141      * Platform-independent path from the current or the
 142      * destination directory to the file getting generated.
 143      * Used when creating the file.
 144      */
 145     public final DocPath path;
 146 
 147     /**
 148      * Name of the file getting generated. If the file getting generated is
 149      * "java/lang/Object.html", then the filename is "Object.html".
 150      */
 151     public final DocPath filename;
 152 
 153     /**
 154      * The global configuration information for this run.
 155      */
 156     public final HtmlConfiguration configuration;
 157 
 158     protected final Utils utils;
 159 
 160     protected final Contents contents;
 161 
 162     protected final Messages messages;
 163 
 164     protected final Resources resources;
 165 
 166     protected final Links links;
 167 
 168     protected final DocPaths docPaths;
 169 
 170     /**
 171      * To check whether annotation heading is printed or not.
 172      */
 173     protected boolean printedAnnotationHeading = false;
 174 
 175     /**
 176      * To check whether annotation field heading is printed or not.
 177      */
 178     protected boolean printedAnnotationFieldHeading = false;
 179 
 180     /**
 181      * To check whether the repeated annotations is documented or not.
 182      */
 183     private boolean isAnnotationDocumented = false;
 184 
 185     /**
 186      * To check whether the container annotations is documented or not.
 187      */
 188     private boolean isContainerDocumented = false;
 189 
 190     HtmlTree fixedNavDiv = new HtmlTree(HtmlTag.DIV);
 191 
 192     /**
 193      * The window title of this file.
 194      */
 195     protected String winTitle;
 196 
 197     protected Script mainBodyScript;
 198 
 199     /**
 200      * Constructor to construct the HtmlStandardWriter object.
 201      *
 202      * @param configuration the configuration for this doclet
 203      * @param path the file to be generated.
 204      */
 205     public HtmlDocletWriter(HtmlConfiguration configuration, DocPath path) {
 206         this.configuration = configuration;
 207         this.contents = configuration.contents;
 208         this.messages = configuration.messages;
 209         this.resources = configuration.resources;
 210         this.links = new Links(path);
 211         this.utils = configuration.utils;
 212         this.path = path;
 213         this.pathToRoot = path.parent().invert();
 214         this.filename = path.basename();
 215         this.docPaths = configuration.docPaths;
 216         this.mainBodyScript = new Script();
 217 
 218         messages.notice("doclet.Generating_0",
 219             DocFile.createFileForOutput(configuration, path).getPath());
 220     }
 221 
 222     /**
 223      * Replace {@docRoot} tag used in options that accept HTML text, such
 224      * as -header, -footer, -top and -bottom, and when converting a relative
 225      * HREF where commentTagsToString inserts a {@docRoot} where one was
 226      * missing.  (Also see DocRootTaglet for {@docRoot} tags in doc
 227      * comments.)
 228      * <p>
 229      * Replace {@docRoot} tag in htmlstr with the relative path to the
 230      * destination directory from the directory where the file is being
 231      * written, looping to handle all such tags in htmlstr.
 232      * <p>
 233      * For example, for "-d docs" and -header containing {@docRoot}, when
 234      * the HTML page for source file p/C1.java is being generated, the
 235      * {@docRoot} tag would be inserted into the header as "../",
 236      * the relative path from docs/p/ to docs/ (the document root).
 237      * <p>
 238      * Note: This doc comment was written with '&amp;#064;' representing '@'
 239      * to prevent the inline tag from being interpreted.
 240      */
 241     public String replaceDocRootDir(String htmlstr) {
 242         // Return if no inline tags exist
 243         int index = htmlstr.indexOf("{@");
 244         if (index < 0) {
 245             return htmlstr;
 246         }
 247         Matcher docrootMatcher = docrootPattern.matcher(htmlstr);
 248         if (!docrootMatcher.find()) {
 249             return htmlstr;
 250         }
 251         StringBuilder buf = new StringBuilder();
 252         int prevEnd = 0;
 253         do {
 254             int match = docrootMatcher.start();
 255             // append htmlstr up to start of next {@docroot}
 256             buf.append(htmlstr.substring(prevEnd, match));
 257             prevEnd = docrootMatcher.end();
 258             if (configuration.docrootparent.length() > 0 && htmlstr.startsWith("/..", prevEnd)) {
 259                 // Insert the absolute link if {@docRoot} is followed by "/..".
 260                 buf.append(configuration.docrootparent);
 261                 prevEnd += 3;
 262             } else {
 263                 // Insert relative path where {@docRoot} was located
 264                 buf.append(pathToRoot.isEmpty() ? "." : pathToRoot.getPath());
 265             }
 266             // Append slash if next character is not a slash
 267             if (prevEnd < htmlstr.length() && htmlstr.charAt(prevEnd) != '/') {
 268                 buf.append('/');
 269             }
 270         } while (docrootMatcher.find());
 271         buf.append(htmlstr.substring(prevEnd));
 272         return buf.toString();
 273     }
 274     //where:
 275         // Note: {@docRoot} is not case sensitive when passed in w/command line option:
 276         private static final Pattern docrootPattern =
 277                 Pattern.compile(Pattern.quote("{@docroot}"), Pattern.CASE_INSENSITIVE);
 278 
 279     /**
 280      * Get the script to show or hide the All classes link.
 281      *
 282      * @param id id of the element to show or hide
 283      * @return a content tree for the script
 284      */
 285     public Content getAllClassesLinkScript(String id) {
 286         Script script = new Script("<!--\n" +
 287                 "  allClassesLink = document.getElementById(")
 288                 .appendStringLiteral(id)
 289                 .append(");\n" +
 290                 "  if(window==top) {\n" +
 291                 "    allClassesLink.style.display = \"block\";\n" +
 292                 "  }\n" +
 293                 "  else {\n" +
 294                 "    allClassesLink.style.display = \"none\";\n" +
 295                 "  }\n" +
 296                 "  //-->\n");
 297         Content div = HtmlTree.DIV(script.asContent());
 298         Content div_noscript = HtmlTree.DIV(contents.noScriptMessage);
 299         Content noScript = HtmlTree.NOSCRIPT(div_noscript);
 300         div.add(noScript);
 301         return div;
 302     }
 303 
 304     /**
 305      * Add method information.
 306      *
 307      * @param method the method to be documented
 308      * @param dl the content tree to which the method information will be added
 309      */
 310     private void addMethodInfo(ExecutableElement method, Content dl) {
 311         TypeElement enclosing = utils.getEnclosingTypeElement(method);
 312         List<? extends TypeMirror> intfacs = enclosing.getInterfaces();
 313         ExecutableElement overriddenMethod = utils.overriddenMethod(method);
 314         VisibleMemberTable vmt = configuration.getVisibleMemberTable(enclosing);
 315         // Check whether there is any implementation or overridden info to be
 316         // printed. If no overridden or implementation info needs to be
 317         // printed, do not print this section.
 318         if ((!intfacs.isEmpty()
 319                 && vmt.getImplementedMethods(method).isEmpty() == false)
 320                 || overriddenMethod != null) {
 321             MethodWriterImpl.addImplementsInfo(this, method, dl);
 322             if (overriddenMethod != null) {
 323                 MethodWriterImpl.addOverridden(this,
 324                         utils.overriddenType(method),
 325                         overriddenMethod,
 326                         dl);
 327             }
 328         }
 329     }
 330 
 331     /**
 332      * Adds the tags information.
 333      *
 334      * @param e the Element for which the tags will be generated
 335      * @param htmltree the documentation tree to which the tags will be added
 336      */
 337     protected void addTagsInfo(Element e, Content htmltree) {
 338         if (configuration.nocomment) {
 339             return;
 340         }
 341         Content dl = new HtmlTree(HtmlTag.DL);
 342         if (utils.isExecutableElement(e) && !utils.isConstructor(e)) {
 343             addMethodInfo((ExecutableElement)e, dl);
 344         }
 345         Content output = new ContentBuilder();
 346         TagletWriter.genTagOutput(configuration.tagletManager, e,
 347             configuration.tagletManager.getBlockTaglets(e),
 348                 getTagletWriterInstance(false), output);
 349         dl.add(output);
 350         htmltree.add(dl);
 351     }
 352 
 353     /**
 354      * Check whether there are any tags for Serialization Overview
 355      * section to be printed.
 356      *
 357      * @param field the VariableElement object to check for tags.
 358      * @return true if there are tags to be printed else return false.
 359      */
 360     protected boolean hasSerializationOverviewTags(VariableElement field) {
 361         Content output = new ContentBuilder();
 362         TagletWriter.genTagOutput(configuration.tagletManager, field,
 363                 configuration.tagletManager.getBlockTaglets(field),
 364                 getTagletWriterInstance(false), output);
 365         return !output.isEmpty();
 366     }
 367 
 368     /**
 369      * Returns a TagletWriter that knows how to write HTML.
 370      *
 371      * @param isFirstSentence  true if we want to write the first sentence
 372      * @return a TagletWriter that knows how to write HTML.
 373      */
 374     public TagletWriter getTagletWriterInstance(boolean isFirstSentence) {
 375         return new TagletWriterImpl(this, isFirstSentence);
 376     }
 377 
 378     /**
 379      * Returns a TagletWriter that knows how to write HTML.
 380      *
 381      * @param isFirstSentence  true if we want to write the first sentence
 382      * @param inSummary  true if tags are to be added in a summary section
 383      * @return a TagletWriter
 384      */
 385     public TagletWriter getTagletWriterInstance(boolean isFirstSentence, boolean inSummary) {
 386         return new TagletWriterImpl(this, isFirstSentence, inSummary);
 387     }
 388 
 389     /**
 390      * Generates the HTML document tree and prints it out.
 391      *
 392      * @param metakeywords Array of String keywords for META tag. Each element
 393      *                     of the array is assigned to a separate META tag.
 394      *                     Pass in null for no array
 395      * @param description the content for the description META tag.
 396      * @param body the body htmltree to be included in the document
 397      * @throws DocFileIOException if there is a problem writing the file
 398      */
 399     public void printHtmlDocument(List<String> metakeywords,
 400                                   String description,
 401                                   Content body)
 402             throws DocFileIOException {
 403         printHtmlDocument(metakeywords, description, new ContentBuilder(), Collections.emptyList(), body);
 404     }
 405 
 406     /**
 407      * Generates the HTML document tree and prints it out.
 408      *
 409      * @param metakeywords Array of String keywords for META tag. Each element
 410      *                     of the array is assigned to a separate META tag.
 411      *                     Pass in null for no array
 412      * @param description the content for the description META tag.
 413      * @param localStylesheets local stylesheets to be included in the HEAD element
 414      * @param body the body htmltree to be included in the document
 415      * @throws DocFileIOException if there is a problem writing the file
 416      */
 417     public void printHtmlDocument(List<String> metakeywords,
 418                                   String description,
 419                                   List<DocPath> localStylesheets,
 420                                   Content body)
 421             throws DocFileIOException {
 422         printHtmlDocument(metakeywords, description, new ContentBuilder(), localStylesheets, body);
 423     }
 424 
 425     /**
 426      * Generates the HTML document tree and prints it out.
 427      *
 428      * @param metakeywords Array of String keywords for META tag. Each element
 429      *                     of the array is assigned to a separate META tag.
 430      *                     Pass in null for no array
 431      * @param description the content for the description META tag.
 432      * @param extraHeadContent any additional content to be included in the HEAD element
 433      * @param localStylesheets local stylesheets to be included in the HEAD element
 434      * @param body the body htmltree to be included in the document
 435      * @throws DocFileIOException if there is a problem writing the file
 436      */
 437     public void printHtmlDocument(List<String> metakeywords,
 438                                   String description,
 439                                   Content extraHeadContent,
 440                                   List<DocPath> localStylesheets,
 441                                   Content body)
 442             throws DocFileIOException {
 443         Content htmlComment = contents.newPage;
 444         List<DocPath> additionalStylesheets = configuration.getAdditionalStylesheets();
 445         additionalStylesheets.addAll(localStylesheets);
 446         Head head = new Head(path, configuration.docletVersion)
 447                 .setTimestamp(!configuration.notimestamp)
 448                 .setDescription(description)
 449                 .setGenerator(getGenerator(getClass()))
 450                 .setTitle(winTitle)
 451                 .setCharset(configuration.charset)
 452                 .addKeywords(metakeywords)
 453                 .setStylesheets(configuration.getMainStylesheet(), additionalStylesheets)
 454                 .setIndex(configuration.createindex, mainBodyScript)
 455                 .addContent(extraHeadContent);
 456 
 457         Content htmlTree = HtmlTree.HTML(configuration.getLocale().getLanguage(), head.toContent(), body);
 458         HtmlDocument htmlDocument = new HtmlDocument(htmlComment, htmlTree);
 459         htmlDocument.write(DocFile.createFileForOutput(configuration, path));
 460     }
 461 
 462     /**
 463      * Get the window title.
 464      *
 465      * @param title the title string to construct the complete window title
 466      * @return the window title string
 467      */
 468     public String getWindowTitle(String title) {
 469         if (configuration.windowtitle.length() > 0) {
 470             title += " (" + configuration.windowtitle  + ")";
 471         }
 472         return title;
 473     }
 474 
 475     /**
 476      * Get user specified header and the footer.
 477      *
 478      * @param header if true print the user provided header else print the
 479      * user provided footer.
 480      */
 481     public Content getUserHeaderFooter(boolean header) {
 482         String content;
 483         if (header) {
 484             content = replaceDocRootDir(configuration.header);
 485         } else {
 486             if (configuration.footer.length() != 0) {
 487                 content = replaceDocRootDir(configuration.footer);
 488             } else {
 489                 content = replaceDocRootDir(configuration.header);
 490             }
 491         }
 492         Content rawContent = new RawHtml(content);
 493         return rawContent;
 494     }
 495 
 496     /**
 497      * Adds the user specified top.
 498      *
 499      * @param htmlTree the content tree to which user specified top will be added
 500      */
 501     public void addTop(Content htmlTree) {
 502         Content top = new RawHtml(replaceDocRootDir(configuration.top));
 503         fixedNavDiv.add(top);
 504     }
 505 
 506     /**
 507      * Adds the user specified bottom.
 508      *
 509      * @param htmlTree the content tree to which user specified bottom will be added
 510      */
 511     public void addBottom(Content htmlTree) {
 512         Content bottom = new RawHtml(replaceDocRootDir(configuration.bottom));
 513         Content small = HtmlTree.SMALL(bottom);
 514         Content p = HtmlTree.P(HtmlStyle.legalCopy, small);
 515         htmlTree.add(p);
 516     }
 517 
 518     /**
 519      * Get the overview tree link for the main tree.
 520      *
 521      * @param label the label for the link
 522      * @return a content tree for the link
 523      */
 524     protected Content getNavLinkMainTree(String label) {
 525         Content mainTreeContent = links.createLink(pathToRoot.resolve(DocPaths.OVERVIEW_TREE),
 526                 new StringContent(label));
 527         Content li = HtmlTree.LI(mainTreeContent);
 528         return li;
 529     }
 530 
 531     /**
 532      * Get table caption.
 533      *
 534      * @param title the content for the caption
 535      * @return a content tree for the caption
 536      */
 537     public Content getTableCaption(Content title) {
 538         Content captionSpan = HtmlTree.SPAN(title);
 539         Content space = Entity.NO_BREAK_SPACE;
 540         Content tabSpan = HtmlTree.SPAN(HtmlStyle.tabEnd, space);
 541         Content caption = HtmlTree.CAPTION(captionSpan);
 542         caption.add(tabSpan);
 543         return caption;
 544     }
 545 
 546     /**
 547      * Returns a packagename content.
 548      *
 549      * @param packageElement the package to check
 550      * @return package name content
 551      */
 552     public Content getPackageName(PackageElement packageElement) {
 553         return packageElement == null || packageElement.isUnnamed()
 554                 ? contents.defaultPackageLabel
 555                 : getPackageLabel(packageElement.getQualifiedName());
 556     }
 557 
 558     /**
 559      * Returns a package name label.
 560      *
 561      * @param packageName the package name
 562      * @return the package name content
 563      */
 564     public Content getPackageLabel(CharSequence packageName) {
 565         return new StringContent(packageName);
 566     }
 567 
 568     /**
 569      * Return the path to the class page for a typeElement.
 570      *
 571      * @param te   TypeElement for which the path is requested.
 572      * @param name Name of the file(doesn't include path).
 573      */
 574     protected DocPath pathString(TypeElement te, DocPath name) {
 575         return pathString(utils.containingPackage(te), name);
 576     }
 577 
 578     /**
 579      * Return path to the given file name in the given package. So if the name
 580      * passed is "Object.html" and the name of the package is "java.lang", and
 581      * if the relative path is "../.." then returned string will be
 582      * "../../java/lang/Object.html"
 583      *
 584      * @param packageElement Package in which the file name is assumed to be.
 585      * @param name File name, to which path string is.
 586      */
 587     protected DocPath pathString(PackageElement packageElement, DocPath name) {
 588         return pathToRoot.resolve(docPaths.forPackage(packageElement).resolve(name));
 589     }
 590 
 591     /**
 592      * Given a package, return the name to be used in HTML anchor tag.
 593      * @param packageElement the package.
 594      * @return the name to be used in HTML anchor tag.
 595      */
 596     public String getPackageAnchorName(PackageElement packageElement) {
 597         return packageElement == null || packageElement.isUnnamed()
 598                 ? SectionName.UNNAMED_PACKAGE_ANCHOR.getName()
 599                 : utils.getPackageName(packageElement);
 600     }
 601 
 602     /**
 603      * Return the link to the given package.
 604      *
 605      * @param packageElement the package to link to.
 606      * @param label the label for the link.
 607      * @return a content tree for the package link.
 608      */
 609     public Content getPackageLink(PackageElement packageElement, CharSequence label) {
 610         return getPackageLink(packageElement, new StringContent(label));
 611     }
 612 
 613     public Content getPackageLink(PackageElement packageElement) {
 614         StringContent content =  packageElement.isUnnamed()
 615                 ? new StringContent()
 616                 : new StringContent(utils.getPackageName(packageElement));
 617         return getPackageLink(packageElement, content);
 618     }
 619 
 620     /**
 621      * Return the link to the given package.
 622      *
 623      * @param packageElement the package to link to.
 624      * @param label the label for the link.
 625      * @return a content tree for the package link.
 626      */
 627     public Content getPackageLink(PackageElement packageElement, Content label) {
 628         boolean included = packageElement != null && utils.isIncluded(packageElement);
 629         if (!included) {
 630             for (PackageElement p : configuration.packages) {
 631                 if (p.equals(packageElement)) {
 632                     included = true;
 633                     break;
 634                 }
 635             }
 636         }
 637         if (included || packageElement == null) {
 638             return links.createLink(pathString(packageElement, DocPaths.PACKAGE_SUMMARY),
 639                     label);
 640         } else {
 641             DocLink crossPkgLink = getCrossPackageLink(packageElement);
 642             if (crossPkgLink != null) {
 643                 return links.createLink(crossPkgLink, label);
 644             } else {
 645                 return label;
 646             }
 647         }
 648     }
 649 
 650     /**
 651      * Get Module link.
 652      *
 653      * @param mdle the module being documented
 654      * @param label tag for the link
 655      * @return a content for the module link
 656      */
 657     public Content getModuleLink(ModuleElement mdle, Content label) {
 658         boolean included = utils.isIncluded(mdle);
 659         return (included)
 660                 ? links.createLink(pathToRoot.resolve(docPaths.moduleSummary(mdle)), label, "", "")
 661                 : label;
 662     }
 663 
 664     public Content interfaceName(TypeElement typeElement, boolean qual) {
 665         Content name = new StringContent((qual)
 666                 ? typeElement.getQualifiedName()
 667                 : utils.getSimpleName(typeElement));
 668         return (utils.isInterface(typeElement)) ?  HtmlTree.SPAN(HtmlStyle.interfaceName, name) : name;
 669     }
 670 
 671     /**
 672      * Add the link to the content tree.
 673      *
 674      * @param element program element for which the link will be added
 675      * @param label label for the link
 676      * @param htmltree the content tree to which the link will be added
 677      */
 678     public void addSrcLink(Element element, Content label, Content htmltree) {
 679         if (element == null) {
 680             return;
 681         }
 682         TypeElement te = utils.getEnclosingTypeElement(element);
 683         if (te == null) {
 684             // must be a typeElement since in has no containing class.
 685             te = (TypeElement) element;
 686         }
 687         if (utils.isIncluded(te)) {
 688             DocPath href = pathToRoot
 689                     .resolve(DocPaths.SOURCE_OUTPUT)
 690                     .resolve(docPaths.forClass(te));
 691             Content content = links.createLink(href
 692                     .fragment(SourceToHTMLConverter.getAnchorName(utils, element)), label, "", "");
 693             htmltree.add(content);
 694         } else {
 695             htmltree.add(label);
 696         }
 697     }
 698 
 699     /**
 700      * Return the link to the given class.
 701      *
 702      * @param linkInfo the information about the link.
 703      *
 704      * @return the link for the given class.
 705      */
 706     public Content getLink(LinkInfoImpl linkInfo) {
 707         LinkFactoryImpl factory = new LinkFactoryImpl(this);
 708         return factory.getLink(linkInfo);
 709     }
 710 
 711     /**
 712      * Return the type parameters for the given class.
 713      *
 714      * @param linkInfo the information about the link.
 715      * @return the type for the given class.
 716      */
 717     public Content getTypeParameterLinks(LinkInfoImpl linkInfo) {
 718         LinkFactoryImpl factory = new LinkFactoryImpl(this);
 719         return factory.getTypeParameterLinks(linkInfo, false);
 720     }
 721 
 722     /*************************************************************
 723      * Return a class cross link to external class documentation.
 724      * The -link option does not allow users to
 725      * link to external classes in the "default" package.
 726      *
 727      * @param classElement the class element
 728      * @param refMemName the name of the member being referenced.  This should
 729      * be null or empty string if no member is being referenced.
 730      * @param label the label for the external link.
 731      * @param strong true if the link should be strong.
 732      * @param code true if the label should be code font.
 733      * @return the link
 734      */
 735     public Content getCrossClassLink(TypeElement classElement, String refMemName,
 736                                     Content label, boolean strong, boolean code) {
 737         if (classElement != null) {
 738             String className = utils.getSimpleName(classElement);
 739             PackageElement packageElement = utils.containingPackage(classElement);
 740             Content defaultLabel = new StringContent(className);
 741             if (code)
 742                 defaultLabel = HtmlTree.CODE(defaultLabel);
 743             if (getCrossPackageLink(packageElement) != null) {
 744                 /*
 745                 The package exists in external documentation, so link to the external
 746                 class (assuming that it exists).  This is definitely a limitation of
 747                 the -link option.  There are ways to determine if an external package
 748                 exists, but no way to determine if the external class exists.  We just
 749                 have to assume that it does.
 750                 */
 751                 DocLink link = configuration.extern.getExternalLink(packageElement, pathToRoot,
 752                                 className + ".html", refMemName);
 753                 return links.createLink(link,
 754                     (label == null) || label.isEmpty() ? defaultLabel : label,
 755                     strong,
 756                     resources.getText("doclet.Href_Class_Or_Interface_Title",
 757                         utils.getPackageName(packageElement)), "", true);
 758             }
 759         }
 760         return null;
 761     }
 762 
 763     public boolean isClassLinkable(TypeElement typeElement) {
 764         if (utils.isIncluded(typeElement)) {
 765             return configuration.isGeneratedDoc(typeElement);
 766         }
 767         return configuration.extern.isExternal(typeElement);
 768     }
 769 
 770     public DocLink getCrossPackageLink(PackageElement element) {
 771         return configuration.extern.getExternalLink(element, pathToRoot,
 772             DocPaths.PACKAGE_SUMMARY.getPath());
 773     }
 774 
 775     public DocLink getCrossModuleLink(ModuleElement element) {
 776         return configuration.extern.getExternalLink(element, pathToRoot,
 777             docPaths.moduleSummary(utils.getModuleName(element)).getPath());
 778     }
 779 
 780     /**
 781      * Get the class link.
 782      *
 783      * @param context the id of the context where the link will be added
 784      * @param element to link to
 785      * @return a content tree for the link
 786      */
 787     public Content getQualifiedClassLink(LinkInfoImpl.Kind context, Element element) {
 788         LinkInfoImpl linkInfoImpl = new LinkInfoImpl(configuration, context, (TypeElement)element);
 789         return getLink(linkInfoImpl.label(utils.getFullyQualifiedName(element)));
 790     }
 791 
 792     /**
 793      * Add the class link.
 794      *
 795      * @param context the id of the context where the link will be added
 796      * @param typeElement to link to
 797      * @param contentTree the content tree to which the link will be added
 798      */
 799     public void addPreQualifiedClassLink(LinkInfoImpl.Kind context, TypeElement typeElement, Content contentTree) {
 800         addPreQualifiedClassLink(context, typeElement, false, contentTree);
 801     }
 802 
 803     /**
 804      * Retrieve the class link with the package portion of the label in
 805      * plain text.  If the qualifier is excluded, it will not be included in the
 806      * link label.
 807      *
 808      * @param typeElement the class to link to.
 809      * @param isStrong true if the link should be strong.
 810      * @return the link with the package portion of the label in plain text.
 811      */
 812     public Content getPreQualifiedClassLink(LinkInfoImpl.Kind context,
 813             TypeElement typeElement, boolean isStrong) {
 814         ContentBuilder classlink = new ContentBuilder();
 815         PackageElement pkg = utils.containingPackage(typeElement);
 816         if (pkg != null && ! configuration.shouldExcludeQualifier(pkg.getSimpleName().toString())) {
 817             classlink.add(getEnclosingPackageName(typeElement));
 818         }
 819         classlink.add(getLink(new LinkInfoImpl(configuration,
 820                 context, typeElement).label(utils.getSimpleName(typeElement)).strong(isStrong)));
 821         return classlink;
 822     }
 823 
 824     /**
 825      * Add the class link with the package portion of the label in
 826      * plain text. If the qualifier is excluded, it will not be included in the
 827      * link label.
 828      *
 829      * @param context the id of the context where the link will be added
 830      * @param typeElement the class to link to
 831      * @param isStrong true if the link should be strong
 832      * @param contentTree the content tree to which the link with be added
 833      */
 834     public void addPreQualifiedClassLink(LinkInfoImpl.Kind context,
 835             TypeElement typeElement, boolean isStrong, Content contentTree) {
 836         PackageElement pkg = utils.containingPackage(typeElement);
 837         if(pkg != null && ! configuration.shouldExcludeQualifier(pkg.getSimpleName().toString())) {
 838             contentTree.add(getEnclosingPackageName(typeElement));
 839         }
 840         LinkInfoImpl linkinfo = new LinkInfoImpl(configuration, context, typeElement)
 841                 .label(utils.getSimpleName(typeElement))
 842                 .strong(isStrong);
 843         Content link = getLink(linkinfo);
 844         contentTree.add(link);
 845     }
 846 
 847     /**
 848      * Get the enclosed name of the package
 849      *
 850      * @param te  TypeElement
 851      * @return the name
 852      */
 853     public String getEnclosingPackageName(TypeElement te) {
 854 
 855         PackageElement encl = configuration.utils.containingPackage(te);
 856         return (encl.isUnnamed()) ? "" : (encl.getQualifiedName() + ".");
 857     }
 858 
 859     /**
 860      * Add the class link, with only class name as the strong link and prefixing
 861      * plain package name.
 862      *
 863      * @param context the id of the context where the link will be added
 864      * @param typeElement the class to link to
 865      * @param contentTree the content tree to which the link with be added
 866      */
 867     public void addPreQualifiedStrongClassLink(LinkInfoImpl.Kind context, TypeElement typeElement, Content contentTree) {
 868         addPreQualifiedClassLink(context, typeElement, true, contentTree);
 869     }
 870 
 871     /**
 872      * Get the link for the given member.
 873      *
 874      * @param context the id of the context where the link will be added
 875      * @param element the member being linked to
 876      * @param label the label for the link
 877      * @return a content tree for the element link
 878      */
 879     public Content getDocLink(LinkInfoImpl.Kind context, Element element, CharSequence label) {
 880         return getDocLink(context, utils.getEnclosingTypeElement(element), element,
 881                 new StringContent(label));
 882     }
 883 
 884     /**
 885      * Return the link for the given member.
 886      *
 887      * @param context the id of the context where the link will be printed.
 888      * @param element the member being linked to.
 889      * @param label the label for the link.
 890      * @param strong true if the link should be strong.
 891      * @return the link for the given member.
 892      */
 893     public Content getDocLink(LinkInfoImpl.Kind context, Element element, CharSequence label,
 894             boolean strong) {
 895         return getDocLink(context, utils.getEnclosingTypeElement(element), element, label, strong);
 896     }
 897 
 898     /**
 899      * Return the link for the given member.
 900      *
 901      * @param context the id of the context where the link will be printed.
 902      * @param typeElement the typeElement that we should link to.  This is not
 903                  necessarily equal to element.containingClass().  We may be
 904                  inheriting comments.
 905      * @param element the member being linked to.
 906      * @param label the label for the link.
 907      * @param strong true if the link should be strong.
 908      * @return the link for the given member.
 909      */
 910     public Content getDocLink(LinkInfoImpl.Kind context, TypeElement typeElement, Element element,
 911             CharSequence label, boolean strong) {
 912         return getDocLink(context, typeElement, element, label, strong, false);
 913     }
 914 
 915     public Content getDocLink(LinkInfoImpl.Kind context, TypeElement typeElement, Element element,
 916             Content label, boolean strong) {
 917         return getDocLink(context, typeElement, element, label, strong, false);
 918     }
 919 
 920     /**
 921      * Return the link for the given member.
 922      *
 923      * @param context the id of the context where the link will be printed.
 924      * @param typeElement the typeElement that we should link to.  This is not
 925                  necessarily equal to element.containingClass().  We may be
 926                  inheriting comments.
 927      * @param element the member being linked to.
 928      * @param label the label for the link.
 929      * @param strong true if the link should be strong.
 930      * @param isProperty true if the element parameter is a JavaFX property.
 931      * @return the link for the given member.
 932      */
 933     public Content getDocLink(LinkInfoImpl.Kind context, TypeElement typeElement, Element element,
 934             CharSequence label, boolean strong, boolean isProperty) {
 935         return getDocLink(context, typeElement, element, new StringContent(label), strong, isProperty);
 936     }
 937 
 938     public Content getDocLink(LinkInfoImpl.Kind context, TypeElement typeElement, Element element,
 939             Content label, boolean strong, boolean isProperty) {
 940         if (!utils.isLinkable(typeElement, element)) {
 941             return label;
 942         }
 943 
 944         if (utils.isExecutableElement(element)) {
 945             ExecutableElement ee = (ExecutableElement)element;
 946             return getLink(new LinkInfoImpl(configuration, context, typeElement)
 947                 .label(label)
 948                 .where(links.getName(getAnchor(ee, isProperty)))
 949                 .strong(strong));
 950         }
 951 
 952         if (utils.isVariableElement(element) || utils.isTypeElement(element)) {
 953             return getLink(new LinkInfoImpl(configuration, context, typeElement)
 954                 .label(label)
 955                 .where(links.getName(element.getSimpleName().toString()))
 956                 .strong(strong));
 957         }
 958 
 959         return label;
 960     }
 961 
 962     /**
 963      * Return the link for the given member.
 964      *
 965      * @param context the id of the context where the link will be added
 966      * @param typeElement the typeElement that we should link to.  This is not
 967                  necessarily equal to element.containingClass().  We may be
 968                  inheriting comments
 969      * @param element the member being linked to
 970      * @param label the label for the link
 971      * @return the link for the given member
 972      */
 973     public Content getDocLink(LinkInfoImpl.Kind context, TypeElement typeElement, Element element,
 974             Content label) {
 975         if (! (utils.isIncluded(element) || utils.isLinkable(typeElement))) {
 976             return label;
 977         } else if (utils.isExecutableElement(element)) {
 978             ExecutableElement emd = (ExecutableElement) element;
 979             return getLink(new LinkInfoImpl(configuration, context, typeElement)
 980                 .label(label)
 981                 .where(links.getName(getAnchor(emd))));
 982         } else if (utils.isVariableElement(element) || utils.isTypeElement(element)) {
 983             return getLink(new LinkInfoImpl(configuration, context, typeElement)
 984                 .label(label).where(links.getName(element.getSimpleName().toString())));
 985         } else {
 986             return label;
 987         }
 988     }
 989 
 990     public String getAnchor(ExecutableElement executableElement) {
 991         return getAnchor(executableElement, false);
 992     }
 993 
 994     public String getAnchor(ExecutableElement executableElement, boolean isProperty) {
 995         if (isProperty) {
 996             return executableElement.getSimpleName().toString();
 997         }
 998         String member = anchorName(executableElement);
 999         String erasedSignature = utils.makeSignature(executableElement, true, true);
1000         return member + erasedSignature;
1001     }
1002 
1003     public String anchorName(Element member) {
1004         if (member.getKind() == ElementKind.CONSTRUCTOR) {
1005             return "<init>";
1006         } else {
1007             return utils.getSimpleName(member);
1008         }
1009     }
1010 
1011     public Content seeTagToContent(Element element, DocTree see) {
1012         Kind kind = see.getKind();
1013         if (!(kind == LINK || kind == SEE || kind == LINK_PLAIN)) {
1014             return new ContentBuilder();
1015         }
1016 
1017         CommentHelper ch = utils.getCommentHelper(element);
1018         String tagName = ch.getTagName(see);
1019         String seetext = replaceDocRootDir(utils.normalizeNewlines(ch.getText(see)).toString());
1020         // Check if @see is an href or "string"
1021         if (seetext.startsWith("<") || seetext.startsWith("\"")) {
1022             return new RawHtml(seetext);
1023         }
1024         boolean isLinkPlain = kind == LINK_PLAIN;
1025         Content label = plainOrCode(isLinkPlain, new RawHtml(ch.getLabel(configuration, see)));
1026 
1027         //The text from the @see tag.  We will output this text when a label is not specified.
1028         Content text = plainOrCode(kind == LINK_PLAIN, new RawHtml(seetext));
1029 
1030         TypeElement refClass = ch.getReferencedClass(configuration, see);
1031         String refClassName =  ch.getReferencedClassName(configuration, see);
1032         Element refMem =       ch.getReferencedMember(configuration, see);
1033         String refMemName =    ch.getReferencedMemberName(see);
1034 
1035         if (refMemName == null && refMem != null) {
1036             refMemName = refMem.toString();
1037         }
1038         if (refClass == null) {
1039             //@see is not referencing an included class
1040             PackageElement refPackage = ch.getReferencedPackage(configuration, see);
1041             if (refPackage != null && utils.isIncluded(refPackage)) {
1042                 //@see is referencing an included package
1043                 if (label.isEmpty())
1044                     label = plainOrCode(isLinkPlain,
1045                             new StringContent(refPackage.getQualifiedName()));
1046                 return getPackageLink(refPackage, label);
1047             } else {
1048                 // @see is not referencing an included class, module or package. Check for cross links.
1049                 DocLink elementCrossLink = (configuration.extern.isModule(refClassName))
1050                         ? getCrossModuleLink(utils.elementUtils.getModuleElement(refClassName)) :
1051                         (refPackage != null) ? getCrossPackageLink(refPackage) : null;
1052                 if (elementCrossLink != null) {
1053                     // Element cross link found
1054                     return links.createLink(elementCrossLink,
1055                             (label.isEmpty() ? text : label), true);
1056                 } else {
1057                     // No cross link found so print warning
1058                     messages.warning(ch.getDocTreePath(see),
1059                             "doclet.see.class_or_package_not_found",
1060                             "@" + tagName,
1061                             seetext);
1062                     return (label.isEmpty() ? text: label);
1063                 }
1064             }
1065         } else if (refMemName == null) {
1066             // Must be a class reference since refClass is not null and refMemName is null.
1067             if (label.isEmpty()) {
1068                 /*
1069                  * it seems to me this is the right thing to do, but it causes comparator failures.
1070                  */
1071                 if (!configuration.backwardCompatibility) {
1072                     StringContent content = utils.isEnclosingPackageIncluded(refClass)
1073                             ? new StringContent(utils.getSimpleName(refClass))
1074                             : new StringContent(utils.getFullyQualifiedName(refClass));
1075                     label = plainOrCode(isLinkPlain, content);
1076                 } else {
1077                     label = plainOrCode(isLinkPlain,
1078                             new StringContent(utils.getSimpleName(refClass)));
1079                 }
1080 
1081             }
1082             return getLink(new LinkInfoImpl(configuration, LinkInfoImpl.Kind.DEFAULT, refClass)
1083                     .label(label));
1084         } else if (refMem == null) {
1085             // Must be a member reference since refClass is not null and refMemName is not null.
1086             // However, refMem is null, so this referenced member does not exist.
1087             return (label.isEmpty() ? text: label);
1088         } else {
1089             // Must be a member reference since refClass is not null and refMemName is not null.
1090             // refMem is not null, so this @see tag must be referencing a valid member.
1091             TypeElement containing = utils.getEnclosingTypeElement(refMem);
1092 
1093             // Find the enclosing type where the method is actually visible
1094             // in the inheritance hierarchy.
1095             ExecutableElement overriddenMethod = null;
1096             if (refMem.getKind() == ElementKind.METHOD) {
1097                 VisibleMemberTable vmt = configuration.getVisibleMemberTable(containing);
1098                 overriddenMethod = vmt.getOverriddenMethod((ExecutableElement)refMem);
1099 
1100                 if (overriddenMethod != null)
1101                     containing = utils.getEnclosingTypeElement(overriddenMethod);
1102             }
1103             if (ch.getText(see).trim().startsWith("#") &&
1104                 ! (utils.isPublic(containing) || utils.isLinkable(containing))) {
1105                 // Since the link is relative and the holder is not even being
1106                 // documented, this must be an inherited link.  Redirect it.
1107                 // The current class either overrides the referenced member or
1108                 // inherits it automatically.
1109                 if (this instanceof ClassWriterImpl) {
1110                     containing = ((ClassWriterImpl) this).getTypeElement();
1111                 } else if (!utils.isPublic(containing)) {
1112                     messages.warning(
1113                         ch.getDocTreePath(see), "doclet.see.class_or_package_not_accessible",
1114                         tagName, utils.getFullyQualifiedName(containing));
1115                 } else {
1116                     messages.warning(
1117                         ch.getDocTreePath(see), "doclet.see.class_or_package_not_found",
1118                         tagName, seetext);
1119                 }
1120             }
1121             if (configuration.currentTypeElement != containing) {
1122                 refMemName = (utils.isConstructor(refMem))
1123                         ? refMemName
1124                         : utils.getSimpleName(containing) + "." + refMemName;
1125             }
1126             if (utils.isExecutableElement(refMem)) {
1127                 if (refMemName.indexOf('(') < 0) {
1128                     refMemName += utils.makeSignature((ExecutableElement)refMem, true);
1129                 }
1130                 if (overriddenMethod != null) {
1131                     // The method to actually link.
1132                     refMem = overriddenMethod;
1133                 }
1134             }
1135 
1136             text = plainOrCode(kind == LINK_PLAIN, new StringContent(refMemName));
1137 
1138             return getDocLink(LinkInfoImpl.Kind.SEE_TAG, containing,
1139                     refMem, (label.isEmpty() ? text: label), false);
1140         }
1141     }
1142 
1143     private Content plainOrCode(boolean plain, Content body) {
1144         return (plain || body.isEmpty()) ? body : HtmlTree.CODE(body);
1145     }
1146 
1147     /**
1148      * Add the inline comment.
1149      *
1150      * @param element the Element for which the inline comment will be added
1151      * @param tag the inline tag to be added
1152      * @param htmltree the content tree to which the comment will be added
1153      */
1154     public void addInlineComment(Element element, DocTree tag, Content htmltree) {
1155         CommentHelper ch = utils.getCommentHelper(element);
1156         List<? extends DocTree> description = ch.getDescription(configuration, tag);
1157         addCommentTags(element, tag, description, false, false, false, htmltree);
1158     }
1159 
1160     /**
1161      * Get the deprecated phrase as content.
1162      *
1163      * @param e the Element for which the inline deprecated comment will be added
1164      * @return a content tree for the deprecated phrase.
1165      */
1166     public Content getDeprecatedPhrase(Element e) {
1167         return (utils.isDeprecatedForRemoval(e))
1168                 ? contents.deprecatedForRemovalPhrase
1169                 : contents.deprecatedPhrase;
1170     }
1171 
1172     /**
1173      * Add the inline deprecated comment.
1174      *
1175      * @param e the Element for which the inline deprecated comment will be added
1176      * @param tag the inline tag to be added
1177      * @param htmltree the content tree to which the comment will be added
1178      */
1179     public void addInlineDeprecatedComment(Element e, DocTree tag, Content htmltree) {
1180         CommentHelper ch = utils.getCommentHelper(e);
1181         addCommentTags(e, ch.getBody(configuration, tag), true, false, false, htmltree);
1182     }
1183 
1184     /**
1185      * Adds the summary content.
1186      *
1187      * @param element the Element for which the summary will be generated
1188      * @param htmltree the documentation tree to which the summary will be added
1189      */
1190     public void addSummaryComment(Element element, Content htmltree) {
1191         addSummaryComment(element, utils.getFirstSentenceTrees(element), htmltree);
1192     }
1193 
1194     /**
1195      * Adds the summary content.
1196      *
1197      * @param element the Element for which the summary will be generated
1198      * @param firstSentenceTags the first sentence tags for the doc
1199      * @param htmltree the documentation tree to which the summary will be added
1200      */
1201     public void addSummaryComment(Element element, List<? extends DocTree> firstSentenceTags, Content htmltree) {
1202         addCommentTags(element, firstSentenceTags, false, true, true, htmltree);
1203     }
1204 
1205     public void addSummaryDeprecatedComment(Element element, DocTree tag, Content htmltree) {
1206         CommentHelper ch = utils.getCommentHelper(element);
1207         List<? extends DocTree> body = ch.getBody(configuration, tag);
1208         addCommentTags(element, ch.getFirstSentenceTrees(configuration, body), true, true, true, htmltree);
1209     }
1210 
1211     /**
1212      * Adds the inline comment.
1213      *
1214      * @param element the Element for which the inline comments will be generated
1215      * @param htmltree the documentation tree to which the inline comments will be added
1216      */
1217     public void addInlineComment(Element element, Content htmltree) {
1218         addCommentTags(element, utils.getFullBody(element), false, false, false, htmltree);
1219     }
1220 
1221     /**
1222      * Adds the comment tags.
1223      *
1224      * @param element the Element for which the comment tags will be generated
1225      * @param tags the first sentence tags for the doc
1226      * @param depr true if it is deprecated
1227      * @param first true if the first sentence tags should be added
1228      * @param inSummary true if the comment tags are added into the summary section
1229      * @param htmltree the documentation tree to which the comment tags will be added
1230      */
1231     private void addCommentTags(Element element, List<? extends DocTree> tags, boolean depr,
1232             boolean first, boolean inSummary, Content htmltree) {
1233         addCommentTags(element, null, tags, depr, first, inSummary, htmltree);
1234     }
1235 
1236     /**
1237      * Adds the comment tags.
1238      *
1239      * @param element for which the comment tags will be generated
1240      * @param holderTag the block tag context for the inline tags
1241      * @param tags the first sentence tags for the doc
1242      * @param depr true if it is deprecated
1243      * @param first true if the first sentence tags should be added
1244      * @param inSummary true if the comment tags are added into the summary section
1245      * @param htmltree the documentation tree to which the comment tags will be added
1246      */
1247     private void addCommentTags(Element element, DocTree holderTag, List<? extends DocTree> tags, boolean depr,
1248             boolean first, boolean inSummary, Content htmltree) {
1249         if(configuration.nocomment){
1250             return;
1251         }
1252         Content div;
1253         Content result = commentTagsToContent(null, element, tags, first, inSummary);
1254         if (depr) {
1255             div = HtmlTree.DIV(HtmlStyle.deprecationComment, result);
1256             htmltree.add(div);
1257         }
1258         else {
1259             div = HtmlTree.DIV(HtmlStyle.block, result);
1260             htmltree.add(div);
1261         }
1262         if (tags.isEmpty()) {
1263             htmltree.add(Entity.NO_BREAK_SPACE);
1264         }
1265     }
1266 
1267     boolean ignoreNonInlineTag(DocTree dtree) {
1268         Name name = null;
1269         if (dtree.getKind() == Kind.START_ELEMENT) {
1270             StartElementTree setree = (StartElementTree)dtree;
1271             name = setree.getName();
1272         } else if (dtree.getKind() == Kind.END_ELEMENT) {
1273             EndElementTree eetree = (EndElementTree)dtree;
1274             name = eetree.getName();
1275         }
1276 
1277         if (name != null) {
1278             com.sun.tools.doclint.HtmlTag htmlTag = com.sun.tools.doclint.HtmlTag.get(name);
1279             if (htmlTag != null &&
1280                     htmlTag.blockType != com.sun.tools.doclint.HtmlTag.BlockType.INLINE) {
1281                 return true;
1282             }
1283         }
1284         return false;
1285     }
1286 
1287     boolean isAllWhiteSpace(String body) {
1288         for (int i = 0 ; i < body.length(); i++) {
1289             if (!Character.isWhitespace(body.charAt(i)))
1290                 return false;
1291         }
1292         return true;
1293     }
1294 
1295     // Notify the next DocTree handler to take necessary action
1296     private boolean commentRemoved = false;
1297 
1298     /**
1299      * Converts inline tags and text to Content, expanding the
1300      * inline tags along the way.  Called wherever text can contain
1301      * an inline tag, such as in comments or in free-form text arguments
1302      * to block tags.
1303      *
1304      * @param holderTag    specific tag where comment resides
1305      * @param element    specific element where comment resides
1306      * @param tags   array of text tags and inline tags (often alternating)
1307                present in the text of interest for this element
1308      * @param isFirstSentence  true if text is first sentence
1309      * @return a Content object
1310      */
1311     public Content commentTagsToContent(DocTree holderTag, Element element,
1312             List<? extends DocTree> tags, boolean isFirstSentence) {
1313         return commentTagsToContent(holderTag, element, tags, isFirstSentence, false);
1314     }
1315 
1316     /**
1317      * Converts inline tags and text to text strings, expanding the
1318      * inline tags along the way.  Called wherever text can contain
1319      * an inline tag, such as in comments or in free-form text arguments
1320      * to block tags.
1321      *
1322      * @param holderTag    specific tag where comment resides
1323      * @param element    specific element where comment resides
1324      * @param tags   array of text tags and inline tags (often alternating)
1325     present in the text of interest for this element
1326      * @param isFirstSentence  true if text is first sentence
1327      * @param inSummary   if the comment tags are added into the summary section
1328      * @return a Content object
1329      */
1330     public Content commentTagsToContent(DocTree holderTag, Element element,
1331             List<? extends DocTree> tags, boolean isFirstSentence, boolean inSummary) {
1332 
1333         final Content result = new ContentBuilder() {
1334             @Override
1335             public void add(CharSequence text) {
1336                 super.add(utils.normalizeNewlines(text));
1337             }
1338         };
1339         CommentHelper ch = utils.getCommentHelper(element);
1340         // Array of all possible inline tags for this javadoc run
1341         configuration.tagletManager.checkTags(element, tags, true);
1342         commentRemoved = false;
1343 
1344         for (ListIterator<? extends DocTree> iterator = tags.listIterator(); iterator.hasNext();) {
1345             boolean isFirstNode = !iterator.hasPrevious();
1346             DocTree tag = iterator.next();
1347             boolean isLastNode  = !iterator.hasNext();
1348 
1349             if (isFirstSentence) {
1350                 // Ignore block tags
1351                 if (ignoreNonInlineTag(tag))
1352                     continue;
1353 
1354                 // Ignore any trailing whitespace OR whitespace after removed html comment
1355                 if ((isLastNode || commentRemoved)
1356                         && tag.getKind() == TEXT
1357                         && isAllWhiteSpace(ch.getText(tag)))
1358                     continue;
1359 
1360                 // Ignore any leading html comments
1361                 if ((isFirstNode || commentRemoved) && tag.getKind() == COMMENT) {
1362                     commentRemoved = true;
1363                     continue;
1364                 }
1365             }
1366 
1367             boolean allDone = new SimpleDocTreeVisitor<Boolean, Content>() {
1368 
1369                 private boolean inAnAtag() {
1370                     if (utils.isStartElement(tag)) {
1371                         StartElementTree st = (StartElementTree)tag;
1372                         Name name = st.getName();
1373                         if (name != null) {
1374                             com.sun.tools.doclint.HtmlTag htag =
1375                                     com.sun.tools.doclint.HtmlTag.get(name);
1376                             return htag != null && htag.equals(com.sun.tools.doclint.HtmlTag.A);
1377                         }
1378                     }
1379                     return false;
1380                 }
1381 
1382                 @Override
1383                 public Boolean visitAttribute(AttributeTree node, Content c) {
1384                     StringBuilder sb = new StringBuilder(SPACER).append(node.getName());
1385                     if (node.getValueKind() == ValueKind.EMPTY) {
1386                         result.add(sb);
1387                         return false;
1388                     }
1389                     sb.append("=");
1390                     String quote;
1391                     switch (node.getValueKind()) {
1392                         case DOUBLE:
1393                             quote = "\"";
1394                             break;
1395                         case SINGLE:
1396                             quote = "\'";
1397                             break;
1398                         default:
1399                             quote = "";
1400                             break;
1401                     }
1402                     sb.append(quote);
1403                     result.add(sb);
1404                     Content docRootContent = new ContentBuilder();
1405 
1406                     boolean isHRef = inAnAtag() && node.getName().toString().equalsIgnoreCase("href");
1407                     for (DocTree dt : node.getValue()) {
1408                         if (utils.isText(dt) && isHRef) {
1409                             String text = ((TextTree) dt).getBody();
1410                             if (text.startsWith("/..") && !configuration.docrootparent.isEmpty()) {
1411                                 result.add(configuration.docrootparent);
1412                                 docRootContent = new ContentBuilder();
1413                                 result.add(textCleanup(text.substring(3), isLastNode));
1414                             } else {
1415                                 if (!docRootContent.isEmpty()) {
1416                                     docRootContent = copyDocRootContent(docRootContent);
1417                                 } else {
1418                                     text = redirectRelativeLinks(element, (TextTree) dt);
1419                                 }
1420                                 result.add(textCleanup(text, isLastNode));
1421                             }
1422                         } else {
1423                             docRootContent = copyDocRootContent(docRootContent);
1424                             dt.accept(this, docRootContent);
1425                         }
1426                     }
1427                     copyDocRootContent(docRootContent);
1428                     result.add(quote);
1429                     return false;
1430                 }
1431 
1432                 @Override
1433                 public Boolean visitComment(CommentTree node, Content c) {
1434                     result.add(new RawHtml(node.getBody()));
1435                     return false;
1436                 }
1437 
1438                 private Content copyDocRootContent(Content content) {
1439                     if (!content.isEmpty()) {
1440                         result.add(content);
1441                         return new ContentBuilder();
1442                     }
1443                     return content;
1444                 }
1445 
1446                 @Override
1447                 public Boolean visitDocRoot(DocRootTree node, Content c) {
1448                     Content docRootContent = TagletWriter.getInlineTagOutput(element,
1449                             configuration.tagletManager,
1450                             holderTag,
1451                             node,
1452                             getTagletWriterInstance(isFirstSentence));
1453                     if (c != null) {
1454                         c.add(docRootContent);
1455                     } else {
1456                         result.add(docRootContent);
1457                     }
1458                     return false;
1459                 }
1460 
1461                 @Override
1462                 public Boolean visitEndElement(EndElementTree node, Content c) {
1463                     RawHtml rawHtml = new RawHtml("</" + node.getName() + ">");
1464                     result.add(rawHtml);
1465                     return false;
1466                 }
1467 
1468                 @Override
1469                 public Boolean visitEntity(EntityTree node, Content c) {
1470                     result.add(new RawHtml(node.toString()));
1471                     return false;
1472                 }
1473 
1474                 @Override
1475                 public Boolean visitErroneous(ErroneousTree node, Content c) {
1476                     messages.warning(ch.getDocTreePath(node),
1477                             "doclet.tag.invalid_usage", node);
1478                     result.add(new RawHtml(node.toString()));
1479                     return false;
1480                 }
1481 
1482                 @Override
1483                 public Boolean visitInheritDoc(InheritDocTree node, Content c) {
1484                     Content output = TagletWriter.getInlineTagOutput(element,
1485                             configuration.tagletManager, holderTag,
1486                             tag, getTagletWriterInstance(isFirstSentence));
1487                     result.add(output);
1488                     // if we obtained the first sentence successfully, nothing more to do
1489                     return (isFirstSentence && !output.isEmpty());
1490                 }
1491 
1492                 @Override
1493                 public Boolean visitIndex(IndexTree node, Content p) {
1494                     Content output = TagletWriter.getInlineTagOutput(element,
1495                             configuration.tagletManager, holderTag, tag,
1496                             getTagletWriterInstance(isFirstSentence, inSummary));
1497                     if (output != null) {
1498                         result.add(output);
1499                     }
1500                     return false;
1501                 }
1502 
1503                 @Override
1504                 public Boolean visitLink(LinkTree node, Content c) {
1505                     // we need to pass the DocTreeImpl here, so ignore node
1506                     result.add(seeTagToContent(element, tag));
1507                     return false;
1508                 }
1509 
1510                 @Override
1511                 public Boolean visitLiteral(LiteralTree node, Content c) {
1512                     String s = node.getBody().getBody();
1513                     Content content = new StringContent(utils.normalizeNewlines(s));
1514                     if (node.getKind() == CODE)
1515                         content = HtmlTree.CODE(content);
1516                     result.add(content);
1517                     return false;
1518                 }
1519 
1520                 @Override
1521                 public Boolean visitSee(SeeTree node, Content c) {
1522                     // we need to pass the DocTreeImpl here, so ignore node
1523                     result.add(seeTagToContent(element, tag));
1524                     return false;
1525                 }
1526 
1527                 @Override
1528                 public Boolean visitStartElement(StartElementTree node, Content c) {
1529                     String text = "<" + node.getName();
1530                     RawHtml rawHtml = new RawHtml(utils.normalizeNewlines(text));
1531                     result.add(rawHtml);
1532 
1533                     for (DocTree dt : node.getAttributes()) {
1534                         dt.accept(this, null);
1535                     }
1536                     result.add(new RawHtml(node.isSelfClosing() ? "/>" : ">"));
1537                     return false;
1538                 }
1539 
1540                 @Override
1541                 public Boolean visitSummary(SummaryTree node, Content c) {
1542                     Content output = TagletWriter.getInlineTagOutput(element,
1543                             configuration.tagletManager, holderTag, tag,
1544                             getTagletWriterInstance(isFirstSentence));
1545                     result.add(output);
1546                     return false;
1547                 }
1548 
1549                 @Override
1550                 public Boolean visitSystemProperty(SystemPropertyTree node, Content p) {
1551                     Content output = TagletWriter.getInlineTagOutput(element,
1552                             configuration.tagletManager, holderTag, tag,
1553                             getTagletWriterInstance(isFirstSentence, inSummary));
1554                     if (output != null) {
1555                         result.add(output);
1556                     }
1557                     return false;
1558                 }
1559 
1560                 private CharSequence textCleanup(String text, boolean isLast) {
1561                     return textCleanup(text, isLast, false);
1562                 }
1563 
1564                 private CharSequence textCleanup(String text, boolean isLast, boolean trimLeader) {
1565                     if (trimLeader) {
1566                         text = removeLeadingWhitespace(text);
1567                     }
1568                     if (isFirstSentence && isLast) {
1569                         text = removeTrailingWhitespace(text);
1570                     }
1571                     text = utils.replaceTabs(text);
1572                     return utils.normalizeNewlines(text);
1573                 }
1574 
1575                 @Override
1576                 public Boolean visitText(TextTree node, Content c) {
1577                     String text = node.getBody();
1578                     result.add(new RawHtml(textCleanup(text, isLastNode, commentRemoved)));
1579                     return false;
1580                 }
1581 
1582                 @Override
1583                 protected Boolean defaultAction(DocTree node, Content c) {
1584                     Content output = TagletWriter.getInlineTagOutput(element,
1585                             configuration.tagletManager, holderTag, tag,
1586                             getTagletWriterInstance(isFirstSentence));
1587                     if (output != null) {
1588                         result.add(output);
1589                     }
1590                     return false;
1591                 }
1592 
1593             }.visit(tag, null);
1594             commentRemoved = false;
1595             if (allDone)
1596                 break;
1597         }
1598         return result;
1599     }
1600 
1601     private String removeTrailingWhitespace(String text) {
1602         char[] buf = text.toCharArray();
1603         for (int i = buf.length - 1; i > 0 ; i--) {
1604             if (!Character.isWhitespace(buf[i]))
1605                 return text.substring(0, i + 1);
1606         }
1607         return text;
1608     }
1609 
1610     private String removeLeadingWhitespace(String text) {
1611         char[] buf = text.toCharArray();
1612         for (int i = 0; i < buf.length; i++) {
1613             if (!Character.isWhitespace(buf[i])) {
1614                 return text.substring(i);
1615             }
1616         }
1617         return text;
1618     }
1619 
1620     /**
1621      * Return true if relative links should not be redirected.
1622      *
1623      * @return Return true if a relative link should not be redirected.
1624      */
1625     private boolean shouldNotRedirectRelativeLinks() {
1626         return  this instanceof AnnotationTypeWriter ||
1627                 this instanceof ClassWriter ||
1628                 this instanceof PackageSummaryWriter;
1629     }
1630 
1631     /**
1632      * Suppose a piece of documentation has a relative link.  When you copy
1633      * that documentation to another place such as the index or class-use page,
1634      * that relative link will no longer work.  We should redirect those links
1635      * so that they will work again.
1636      * <p>
1637      * Here is the algorithm used to fix the link:
1638      * <p>
1639      * {@literal <relative link> => docRoot + <relative path to file> + <relative link> }
1640      * <p>
1641      * For example, suppose DocletEnvironment has this link:
1642      * {@literal <a href="package-summary.html">The package Page</a> }
1643      * <p>
1644      * If this link appeared in the index, we would redirect
1645      * the link like this:
1646      *
1647      * {@literal <a href="./jdk/javadoc/doclet/package-summary.html">The package Page</a>}
1648      *
1649      * @param element the Element object whose documentation is being written.
1650      * @param tt the text being written.
1651      *
1652      * @return the text, with all the relative links redirected to work.
1653      */
1654     private String redirectRelativeLinks(Element element, TextTree tt) {
1655         String text = tt.getBody();
1656         if (element == null || utils.isOverviewElement(element) || shouldNotRedirectRelativeLinks()) {
1657             return text;
1658         }
1659 
1660         DocPath redirectPathFromRoot = new SimpleElementVisitor9<DocPath, Void>() {
1661             @Override
1662             public DocPath visitType(TypeElement e, Void p) {
1663                 return docPaths.forPackage(utils.containingPackage(e));
1664             }
1665 
1666             @Override
1667             public DocPath visitPackage(PackageElement e, Void p) {
1668                 return docPaths.forPackage(e);
1669             }
1670 
1671             @Override
1672             public DocPath visitVariable(VariableElement e, Void p) {
1673                 return docPaths.forPackage(utils.containingPackage(e));
1674             }
1675 
1676             @Override
1677             public DocPath visitExecutable(ExecutableElement e, Void p) {
1678                 return docPaths.forPackage(utils.containingPackage(e));
1679             }
1680 
1681             @Override
1682             protected DocPath defaultAction(Element e, Void p) {
1683                 return null;
1684             }
1685         }.visit(element);
1686         if (redirectPathFromRoot == null) {
1687             return text;
1688         }
1689         String lower = Utils.toLowerCase(text);
1690         if (!(lower.startsWith("mailto:")
1691                 || lower.startsWith("http:")
1692                 || lower.startsWith("https:")
1693                 || lower.startsWith("file:"))) {
1694             text = "{@" + (new DocRootTaglet()).getName() + "}/"
1695                     + redirectPathFromRoot.resolve(text).getPath();
1696             text = replaceDocRootDir(text);
1697         }
1698         return text;
1699     }
1700 
1701     /**
1702      * According to
1703      * <cite>The Java&trade; Language Specification</cite>,
1704      * all the outer classes and static nested classes are core classes.
1705      */
1706     public boolean isCoreClass(TypeElement typeElement) {
1707         return utils.getEnclosingTypeElement(typeElement) == null || utils.isStatic(typeElement);
1708     }
1709 
1710     /**
1711      * Adds the annotation types for the given packageElement.
1712      *
1713      * @param packageElement the package to write annotations for.
1714      * @param htmltree the documentation tree to which the annotation info will be
1715      *        added
1716      */
1717     public void addAnnotationInfo(PackageElement packageElement, Content htmltree) {
1718         addAnnotationInfo(packageElement.getAnnotationMirrors(), htmltree);
1719     }
1720 
1721     /*
1722      * this is a hack to delay dealing with Annotations in the writers, the assumption
1723      * is that all necessary checks have been made to get here.
1724      */
1725     public void addReceiverAnnotationInfo(ExecutableElement method, TypeMirror rcvrTypeMirror,
1726             List<? extends AnnotationMirror> annotationMirrors, Content htmltree) {
1727         TypeMirror rcvrType = method.getReceiverType();
1728         List<? extends AnnotationMirror> annotationMirrors1 = rcvrType.getAnnotationMirrors();
1729         htmltree.add(getAnnotationInfo(annotationMirrors1, false));
1730     }
1731 
1732     /**
1733      * Adds the annotation types for the given element.
1734      *
1735      * @param element the package to write annotations for
1736      * @param htmltree the content tree to which the annotation types will be added
1737      */
1738     public void addAnnotationInfo(Element element, Content htmltree) {
1739         addAnnotationInfo(element.getAnnotationMirrors(), htmltree);
1740     }
1741 
1742     /**
1743      * Add the annotatation types for the given element and parameter.
1744      *
1745      * @param param the parameter to write annotations for.
1746      * @param tree the content tree to which the annotation types will be added
1747      */
1748     public boolean addAnnotationInfo(VariableElement param, Content tree) {
1749         Content annotaionInfo = getAnnotationInfo(param.getAnnotationMirrors(), false);
1750         if (annotaionInfo.isEmpty()) {
1751             return false;
1752         }
1753         tree.add(annotaionInfo);
1754         return true;
1755     }
1756 
1757     /**
1758      * Adds the annotatation types for the given Element.
1759      *
1760      * @param descList a list of annotation mirrors.
1761      * @param htmltree the documentation tree to which the annotation info will be
1762      *        added
1763      */
1764     private void addAnnotationInfo(List<? extends AnnotationMirror> descList, Content htmltree) {
1765         htmltree.add(getAnnotationInfo(descList, true));
1766     }
1767 
1768     /**
1769      * Return a content tree containing the annotation types for the given element.
1770      *
1771      * @param descList a list of annotation mirrors.
1772      * @return the documentation tree containing the annotation info.
1773      */
1774     Content getAnnotationInfo(List<? extends AnnotationMirror> descList, boolean lineBreak) {
1775         List<Content> annotations = getAnnotations(descList, lineBreak);
1776         String sep = "";
1777         ContentBuilder builder = new ContentBuilder();
1778         for (Content annotation: annotations) {
1779             builder.add(sep);
1780             builder.add(annotation);
1781             if (!lineBreak) {
1782                 sep = " ";
1783             }
1784         }
1785         return builder;
1786     }
1787 
1788     /**
1789      * Return the string representations of the annotation types for
1790      * the given doc.
1791      *
1792      * @param descList a list of annotation mirrors.
1793      * @param linkBreak if true, add new line between each member value.
1794      * @return a list of strings representing the annotations being
1795      *         documented.
1796      */
1797     public List<Content> getAnnotations(List<? extends AnnotationMirror> descList, boolean linkBreak) {
1798         List<Content> results = new ArrayList<>();
1799         ContentBuilder annotation;
1800         for (AnnotationMirror aDesc : descList) {
1801             TypeElement annotationElement = (TypeElement)aDesc.getAnnotationType().asElement();
1802             // If an annotation is not documented, do not add it to the list. If
1803             // the annotation is of a repeatable type, and if it is not documented
1804             // and also if its container annotation is not documented, do not add it
1805             // to the list. If an annotation of a repeatable type is not documented
1806             // but its container is documented, it will be added to the list.
1807             if (!utils.isDocumentedAnnotation(annotationElement) &&
1808                 (!isAnnotationDocumented && !isContainerDocumented)) {
1809                 continue;
1810             }
1811             annotation = new ContentBuilder();
1812             isAnnotationDocumented = false;
1813             LinkInfoImpl linkInfo = new LinkInfoImpl(configuration,
1814                                                      LinkInfoImpl.Kind.ANNOTATION, annotationElement);
1815             Map<? extends ExecutableElement, ? extends AnnotationValue> pairs = aDesc.getElementValues();
1816             // If the annotation is synthesized, do not print the container.
1817             if (utils.configuration.workArounds.isSynthesized(aDesc)) {
1818                 for (ExecutableElement ee : pairs.keySet()) {
1819                     AnnotationValue annotationValue = pairs.get(ee);
1820                     List<AnnotationValue> annotationTypeValues = new ArrayList<>();
1821 
1822                     new SimpleAnnotationValueVisitor9<Void, List<AnnotationValue>>() {
1823                         @Override
1824                         public Void visitArray(List<? extends AnnotationValue> vals, List<AnnotationValue> p) {
1825                             p.addAll(vals);
1826                             return null;
1827                         }
1828 
1829                         @Override
1830                         protected Void defaultAction(Object o, List<AnnotationValue> p) {
1831                             p.add(annotationValue);
1832                             return null;
1833                         }
1834                     }.visit(annotationValue, annotationTypeValues);
1835 
1836                     String sep = "";
1837                     for (AnnotationValue av : annotationTypeValues) {
1838                         annotation.add(sep);
1839                         annotation.add(annotationValueToContent(av));
1840                         sep = " ";
1841                     }
1842                 }
1843             } else if (isAnnotationArray(pairs)) {
1844                 // If the container has 1 or more value defined and if the
1845                 // repeatable type annotation is not documented, do not print
1846                 // the container.
1847                 if (pairs.size() == 1 && isAnnotationDocumented) {
1848                     List<AnnotationValue> annotationTypeValues = new ArrayList<>();
1849                     for (AnnotationValue a :  pairs.values()) {
1850                         new SimpleAnnotationValueVisitor9<Void, List<AnnotationValue>>() {
1851                             @Override
1852                             public Void visitArray(List<? extends AnnotationValue> vals, List<AnnotationValue> annotationTypeValues) {
1853                                annotationTypeValues.addAll(vals);
1854                                return null;
1855                             }
1856                         }.visit(a, annotationTypeValues);
1857                     }
1858                     String sep = "";
1859                     for (AnnotationValue av : annotationTypeValues) {
1860                         annotation.add(sep);
1861                         annotation.add(annotationValueToContent(av));
1862                         sep = " ";
1863                     }
1864                 }
1865                 // If the container has 1 or more value defined and if the
1866                 // repeatable type annotation is not documented, print the container.
1867                 else {
1868                     addAnnotations(annotationElement, linkInfo, annotation, pairs, false);
1869                 }
1870             }
1871             else {
1872                 addAnnotations(annotationElement, linkInfo, annotation, pairs, linkBreak);
1873             }
1874             annotation.add(linkBreak ? DocletConstants.NL : "");
1875             results.add(annotation);
1876         }
1877         return results;
1878     }
1879 
1880     /**
1881      * Add annotation to the annotation string.
1882      *
1883      * @param annotationDoc the annotation being documented
1884      * @param linkInfo the information about the link
1885      * @param annotation the annotation string to which the annotation will be added
1886      * @param map annotation type element to annotation value pairs
1887      * @param linkBreak if true, add new line between each member value
1888      */
1889     private void addAnnotations(TypeElement annotationDoc, LinkInfoImpl linkInfo,
1890                                 ContentBuilder annotation,
1891                                 Map<? extends ExecutableElement, ? extends AnnotationValue> map,
1892                                 boolean linkBreak) {
1893         linkInfo.label = new StringContent("@");
1894         linkInfo.label.add(annotationDoc.getSimpleName());
1895         annotation.add(getLink(linkInfo));
1896         if (!map.isEmpty()) {
1897             annotation.add("(");
1898             boolean isFirst = true;
1899             Set<? extends ExecutableElement> keys = map.keySet();
1900             boolean multipleValues = keys.size() > 1;
1901             for (ExecutableElement element : keys) {
1902                 if (isFirst) {
1903                     isFirst = false;
1904                 } else {
1905                     annotation.add(",");
1906                     if (linkBreak) {
1907                         annotation.add(DocletConstants.NL);
1908                         int spaces = annotationDoc.getSimpleName().length() + 2;
1909                         for (int k = 0; k < (spaces); k++) {
1910                             annotation.add(" ");
1911                         }
1912                     }
1913                 }
1914                 String simpleName = element.getSimpleName().toString();
1915                 if (multipleValues || !"value".equals(simpleName)) { // Omit "value=" where unnecessary
1916                     annotation.add(getDocLink(LinkInfoImpl.Kind.ANNOTATION,
1917                                                      element, simpleName, false));
1918                     annotation.add("=");
1919                 }
1920                 AnnotationValue annotationValue = map.get(element);
1921                 List<AnnotationValue> annotationTypeValues = new ArrayList<>();
1922                 new SimpleAnnotationValueVisitor9<Void, AnnotationValue>() {
1923                     @Override
1924                     public Void visitArray(List<? extends AnnotationValue> vals, AnnotationValue p) {
1925                         annotationTypeValues.addAll(vals);
1926                         return null;
1927                     }
1928                     @Override
1929                     protected Void defaultAction(Object o, AnnotationValue p) {
1930                         annotationTypeValues.add(p);
1931                         return null;
1932                     }
1933                 }.visit(annotationValue, annotationValue);
1934                 annotation.add(annotationTypeValues.size() == 1 ? "" : "{");
1935                 String sep = "";
1936                 for (AnnotationValue av : annotationTypeValues) {
1937                     annotation.add(sep);
1938                     annotation.add(annotationValueToContent(av));
1939                     sep = ",";
1940                 }
1941                 annotation.add(annotationTypeValues.size() == 1 ? "" : "}");
1942                 isContainerDocumented = false;
1943             }
1944             annotation.add(")");
1945         }
1946     }
1947 
1948     /**
1949      * Check if the annotation contains an array of annotation as a value. This
1950      * check is to verify if a repeatable type annotation is present or not.
1951      *
1952      * @param pairs annotation type element and value pairs
1953      *
1954      * @return true if the annotation contains an array of annotation as a value.
1955      */
1956     private boolean isAnnotationArray(Map<? extends ExecutableElement, ? extends AnnotationValue> pairs) {
1957         AnnotationValue annotationValue;
1958         for (ExecutableElement ee : pairs.keySet()) {
1959             annotationValue = pairs.get(ee);
1960             boolean rvalue = new SimpleAnnotationValueVisitor9<Boolean, Void>() {
1961                 @Override
1962                 public Boolean visitArray(List<? extends AnnotationValue> vals, Void p) {
1963                     if (vals.size() > 1) {
1964                         if (vals.get(0) instanceof AnnotationMirror) {
1965                             isContainerDocumented = true;
1966                             return new SimpleAnnotationValueVisitor9<Boolean, Void>() {
1967                                 @Override
1968                                 public Boolean visitAnnotation(AnnotationMirror a, Void p) {
1969                                     isContainerDocumented = true;
1970                                     Element asElement = a.getAnnotationType().asElement();
1971                                     if (utils.isDocumentedAnnotation((TypeElement)asElement)) {
1972                                         isAnnotationDocumented = true;
1973                                     }
1974                                     return true;
1975                                 }
1976                                 @Override
1977                                 protected Boolean defaultAction(Object o, Void p) {
1978                                     return false;
1979                                 }
1980                             }.visit(vals.get(0));
1981                         }
1982                     }
1983                     return false;
1984                 }
1985 
1986                 @Override
1987                 protected Boolean defaultAction(Object o, Void p) {
1988                     return false;
1989                 }
1990             }.visit(annotationValue);
1991             if (rvalue) {
1992                 return true;
1993             }
1994         }
1995         return false;
1996     }
1997 
1998     private Content annotationValueToContent(AnnotationValue annotationValue) {
1999         return new SimpleAnnotationValueVisitor9<Content, Void>() {
2000 
2001             @Override
2002             public Content visitType(TypeMirror t, Void p) {
2003                 return new SimpleTypeVisitor9<Content, Void>() {
2004                     @Override
2005                     public Content visitDeclared(DeclaredType t, Void p) {
2006                         LinkInfoImpl linkInfo = new LinkInfoImpl(configuration,
2007                                 LinkInfoImpl.Kind.ANNOTATION, t);
2008                         String name = utils.isIncluded(t.asElement())
2009                                 ? t.asElement().getSimpleName().toString()
2010                                 : utils.getFullyQualifiedName(t.asElement());
2011                         linkInfo.label = new StringContent(name + utils.getDimension(t) + ".class");
2012                         return getLink(linkInfo);
2013                     }
2014                     @Override
2015                     protected Content defaultAction(TypeMirror e, Void p) {
2016                         return new StringContent(t + utils.getDimension(t) + ".class");
2017                     }
2018                 }.visit(t);
2019             }
2020             @Override
2021             public Content visitAnnotation(AnnotationMirror a, Void p) {
2022                 List<Content> list = getAnnotations(List.of(a), false);
2023                 ContentBuilder buf = new ContentBuilder();
2024                 for (Content c : list) {
2025                     buf.add(c);
2026                 }
2027                 return buf;
2028             }
2029             @Override
2030             public Content visitEnumConstant(VariableElement c, Void p) {
2031                 return getDocLink(LinkInfoImpl.Kind.ANNOTATION,
2032                         c, c.getSimpleName(), false);
2033             }
2034             @Override
2035             public Content visitArray(List<? extends AnnotationValue> vals, Void p) {
2036                 ContentBuilder buf = new ContentBuilder();
2037                 String sep = "";
2038                 for (AnnotationValue av : vals) {
2039                     buf.add(sep);
2040                     buf.add(visit(av));
2041                     sep = " ";
2042                 }
2043                 return buf;
2044             }
2045             @Override
2046             protected Content defaultAction(Object o, Void p) {
2047                 return new StringContent(annotationValue.toString());
2048             }
2049         }.visit(annotationValue);
2050     }
2051 
2052     protected TableHeader getPackageTableHeader() {
2053         return new TableHeader(contents.packageLabel, contents.descriptionLabel);
2054     }
2055 
2056     /**
2057      * Generates a string for use in a description meta element,
2058      * based on an element and its enclosing elements
2059      * @param prefix a prefix for the string
2060      * @param elem the element
2061      * @return the description
2062      */
2063     static String getDescription(String prefix, Element elem) {
2064         LinkedList<Element> chain = new LinkedList<>();
2065         for (Element e = elem; e != null; e = e.getEnclosingElement()) {
2066             // ignore unnamed enclosing elements
2067             if (e.getSimpleName().length() == 0 && e != elem) {
2068                 break;
2069             }
2070             chain.addFirst(e);
2071         }
2072         StringBuilder sb = new StringBuilder();
2073         for (Element e: chain) {
2074             CharSequence name;
2075             switch (e.getKind()) {
2076                 case MODULE:
2077                 case PACKAGE:
2078                     name = ((QualifiedNameable) e).getQualifiedName();
2079                     if (name.length() == 0) {
2080                         name = "<unnamed>";
2081                     }
2082                     break;
2083 
2084                 default:
2085                     name = e.getSimpleName();
2086                     break;
2087             }
2088 
2089             if (sb.length() == 0) {
2090                 sb.append(prefix).append(": ");
2091             } else {
2092                 sb.append(", ");
2093             }
2094             sb.append(e.getKind().toString().toLowerCase(Locale.US).replace("_", " "))
2095                     .append(": ")
2096                     .append(name);
2097         }
2098         return sb.toString();
2099     }
2100 
2101     static String getGenerator(Class<?> clazz) {
2102         return "javadoc/" + clazz.getSimpleName();
2103     }
2104 
2105     /**
2106      * Returns an HtmlTree for the SCRIPT tag.
2107      *
2108      * @return an HtmlTree for the SCRIPT tag
2109      */
2110     protected Script getWinTitleScript() {
2111         Script script = new Script();
2112         if (winTitle != null && winTitle.length() > 0) {
2113             script.append("<!--\n" +
2114                     "    try {\n" +
2115                     "        if (location.href.indexOf('is-external=true') == -1) {\n" +
2116                     "            parent.document.title=")
2117                     .appendStringLiteral(winTitle)
2118                     .append(";\n" +
2119                     "        }\n" +
2120                     "    }\n" +
2121                     "    catch(err) {\n" +
2122                     "    }\n" +
2123                     "//-->\n");
2124         }
2125         return script;
2126     }
2127 
2128     /**
2129      * Returns an HtmlTree for the BODY tag.
2130      *
2131      * @param title title for the window
2132      * @return an HtmlTree for the BODY tag
2133      */
2134     public HtmlTree getBody(String title) {
2135         HtmlTree body = new HtmlTree(HtmlTag.BODY);
2136         body.put(HtmlAttr.CLASS, getBodyClass());
2137 
2138         this.winTitle = title;
2139         // Don't print windowtitle script for overview-frame, allclasses-frame
2140         // and package-frame
2141         body.add(mainBodyScript.asContent());
2142         Content noScript = HtmlTree.NOSCRIPT(HtmlTree.DIV(contents.noScriptMessage));
2143         body.add(noScript);
2144         return body;
2145     }
2146 
2147     public String getBodyClass() {
2148         return getClass().getSimpleName()
2149                 .replaceAll("(Writer)?(Impl)?$", "")
2150                 .replaceAll("AnnotationType", "Class")
2151                 .replaceAll("(.)([A-Z])", "$1-$2")
2152                 .replaceAll("(?i)^(module|package|class)$", "$1-declaration")
2153                 .toLowerCase(Locale.US);
2154     }
2155 
2156     Script getMainBodyScript() {
2157         return mainBodyScript;
2158     }
2159 
2160     /**
2161      * Returns the path of module/package specific stylesheets for the element.
2162      * @param element module/Package element
2163      * @return list of path of module/package specific stylesheets
2164      * @throws DocFileIOException
2165      */
2166     List<DocPath> getLocalStylesheets(Element element) throws DocFileIOException {
2167         List<DocPath> stylesheets = new ArrayList<>();
2168         DocPath basePath = null;
2169         if (element instanceof PackageElement) {
2170             stylesheets.addAll(getModuleStylesheets((PackageElement)element));
2171             basePath = docPaths.forPackage((PackageElement)element);
2172         } else if (element instanceof ModuleElement) {
2173             basePath = DocPaths.forModule((ModuleElement)element);
2174         }
2175         for (DocPath stylesheet : getStylesheets(element)) {
2176             stylesheets.add(basePath.resolve(stylesheet.getPath()));
2177         }
2178         return stylesheets;
2179     }
2180 
2181     private List<DocPath> getModuleStylesheets(PackageElement pkgElement) throws
2182             DocFileIOException {
2183         List<DocPath> moduleStylesheets = new ArrayList<>();
2184         ModuleElement moduleElement = utils.containingModule(pkgElement);
2185         if (moduleElement != null && !moduleElement.isUnnamed()) {
2186             List<DocPath> localStylesheets = getStylesheets(moduleElement);
2187             DocPath basePath = DocPaths.forModule(moduleElement);
2188             for (DocPath stylesheet : localStylesheets) {
2189                 moduleStylesheets.add(basePath.resolve(stylesheet));
2190             }
2191         }
2192         return moduleStylesheets;
2193     }
2194 
2195     private List<DocPath> getStylesheets(Element element) throws DocFileIOException {
2196         List<DocPath> localStylesheets = configuration.localStylesheetMap.get(element);
2197         if (localStylesheets == null) {
2198             DocFilesHandlerImpl docFilesHandler = (DocFilesHandlerImpl)configuration
2199                     .getWriterFactory().getDocFilesHandler(element);
2200             localStylesheets = docFilesHandler.getStylesheets();
2201             configuration.localStylesheetMap.put(element, localStylesheets);
2202         }
2203         return localStylesheets;
2204     }
2205 
2206 }