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