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.SimpleElementVisitor9;
  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                     result.add(seeTagToContent(element, tag));
1516                     return false;
1517                 }
1518 
1519                 @Override
1520                 public Boolean visitLiteral(LiteralTree node, Content c) {
1521                     String s = node.getBody().getBody();
1522                     Content content = new StringContent(utils.normalizeNewlines(s));
1523                     if (node.getKind() == CODE)
1524                         content = HtmlTree.CODE(content);
1525                     result.add(content);
1526                     return false;
1527                 }
1528 
1529                 @Override
1530                 public Boolean visitSee(SeeTree node, Content c) {
1531                     // we need to pass the DocTreeImpl here, so ignore node
1532                     result.add(seeTagToContent(element, tag));
1533                     return false;
1534                 }
1535 
1536                 @Override
1537                 public Boolean visitStartElement(StartElementTree node, Content c) {
1538                     String text = "<" + node.getName();
1539                     RawHtml rawHtml = new RawHtml(utils.normalizeNewlines(text));
1540                     result.add(rawHtml);
1541 
1542                     for (DocTree dt : node.getAttributes()) {
1543                         dt.accept(this, null);
1544                     }
1545                     result.add(new RawHtml(node.isSelfClosing() ? "/>" : ">"));
1546                     return false;
1547                 }
1548 
1549                 @Override
1550                 public Boolean visitSummary(SummaryTree node, Content c) {
1551                     Content output = TagletWriter.getInlineTagOutput(element,
1552                             configuration.tagletManager, holderTag, tag,
1553                             getTagletWriterInstance(isFirstSentence));
1554                     result.add(output);
1555                     return false;
1556                 }
1557 
1558                 @Override
1559                 public Boolean visitSystemProperty(SystemPropertyTree node, Content p) {
1560                     Content output = TagletWriter.getInlineTagOutput(element,
1561                             configuration.tagletManager, holderTag, tag,
1562                             getTagletWriterInstance(isFirstSentence, inSummary));
1563                     if (output != null) {
1564                         result.add(output);
1565                     }
1566                     return false;
1567                 }
1568 
1569                 private CharSequence textCleanup(String text, boolean isLast) {
1570                     return textCleanup(text, isLast, false);
1571                 }
1572 
1573                 private CharSequence textCleanup(String text, boolean isLast, boolean trimLeader) {
1574                     if (trimLeader) {
1575                         text = removeLeadingWhitespace(text);
1576                     }
1577                     if (isFirstSentence && isLast) {
1578                         text = removeTrailingWhitespace(text);
1579                     }
1580                     text = utils.replaceTabs(text);
1581                     return utils.normalizeNewlines(text);
1582                 }
1583 
1584                 @Override
1585                 public Boolean visitText(TextTree node, Content c) {
1586                     String text = node.getBody();
1587                     result.add(new RawHtml(textCleanup(text, isLastNode, commentRemoved)));
1588                     return false;
1589                 }
1590 
1591                 @Override
1592                 protected Boolean defaultAction(DocTree node, Content c) {
1593                     Content output = TagletWriter.getInlineTagOutput(element,
1594                             configuration.tagletManager, holderTag, tag,
1595                             getTagletWriterInstance(isFirstSentence));
1596                     if (output != null) {
1597                         result.add(output);
1598                     }
1599                     return false;
1600                 }
1601 
1602             }.visit(tag, null);
1603             commentRemoved = false;
1604             if (allDone)
1605                 break;
1606         }
1607         return result;
1608     }
1609 
1610     private String removeTrailingWhitespace(String text) {
1611         char[] buf = text.toCharArray();
1612         for (int i = buf.length - 1; i > 0 ; i--) {
1613             if (!Character.isWhitespace(buf[i]))
1614                 return text.substring(0, i + 1);
1615         }
1616         return text;
1617     }
1618 
1619     private String removeLeadingWhitespace(String text) {
1620         char[] buf = text.toCharArray();
1621         for (int i = 0; i < buf.length; i++) {
1622             if (!Character.isWhitespace(buf[i])) {
1623                 return text.substring(i);
1624             }
1625         }
1626         return text;
1627     }
1628 
1629     /**
1630      * Return true if relative links should not be redirected.
1631      *
1632      * @return Return true if a relative link should not be redirected.
1633      */
1634     private boolean shouldNotRedirectRelativeLinks() {
1635         return  this instanceof AnnotationTypeWriter ||
1636                 this instanceof ClassWriter ||
1637                 this instanceof PackageSummaryWriter;
1638     }
1639 
1640     /**
1641      * Suppose a piece of documentation has a relative link.  When you copy
1642      * that documentation to another place such as the index or class-use page,
1643      * that relative link will no longer work.  We should redirect those links
1644      * so that they will work again.
1645      * <p>
1646      * Here is the algorithm used to fix the link:
1647      * <p>
1648      * {@literal <relative link> => docRoot + <relative path to file> + <relative link> }
1649      * <p>
1650      * For example, suppose DocletEnvironment has this link:
1651      * {@literal <a href="package-summary.html">The package Page</a> }
1652      * <p>
1653      * If this link appeared in the index, we would redirect
1654      * the link like this:
1655      *
1656      * {@literal <a href="./jdk/javadoc/doclet/package-summary.html">The package Page</a>}
1657      *
1658      * @param element the Element object whose documentation is being written.
1659      * @param tt the text being written.
1660      *
1661      * @return the text, with all the relative links redirected to work.
1662      */
1663     private String redirectRelativeLinks(Element element, TextTree tt) {
1664         String text = tt.getBody();
1665         if (element == null || utils.isOverviewElement(element) || shouldNotRedirectRelativeLinks()) {
1666             return text;
1667         }
1668 
1669         DocPath redirectPathFromRoot = new SimpleElementVisitor9<DocPath, Void>() {
1670             @Override
1671             public DocPath visitType(TypeElement e, Void p) {
1672                 return docPaths.forPackage(utils.containingPackage(e));
1673             }
1674 
1675             @Override
1676             public DocPath visitPackage(PackageElement e, Void p) {
1677                 return docPaths.forPackage(e);
1678             }
1679 
1680             @Override
1681             public DocPath visitVariable(VariableElement e, Void p) {
1682                 return docPaths.forPackage(utils.containingPackage(e));
1683             }
1684 
1685             @Override
1686             public DocPath visitExecutable(ExecutableElement e, Void p) {
1687                 return docPaths.forPackage(utils.containingPackage(e));
1688             }
1689 
1690             @Override
1691             protected DocPath defaultAction(Element e, Void p) {
1692                 return null;
1693             }
1694         }.visit(element);
1695         if (redirectPathFromRoot == null) {
1696             return text;
1697         }
1698         String lower = Utils.toLowerCase(text);
1699         if (!(lower.startsWith("mailto:")
1700                 || lower.startsWith("http:")
1701                 || lower.startsWith("https:")
1702                 || lower.startsWith("file:"))) {
1703             text = "{@" + (new DocRootTaglet()).getName() + "}/"
1704                     + redirectPathFromRoot.resolve(text).getPath();
1705             text = replaceDocRootDir(text);
1706         }
1707         return text;
1708     }
1709 
1710     /**
1711      * According to
1712      * <cite>The Java&trade; Language Specification</cite>,
1713      * all the outer classes and static nested classes are core classes.
1714      */
1715     public boolean isCoreClass(TypeElement typeElement) {
1716         return utils.getEnclosingTypeElement(typeElement) == null || utils.isStatic(typeElement);
1717     }
1718 
1719     /**
1720      * Adds the annotation types for the given packageElement.
1721      *
1722      * @param packageElement the package to write annotations for.
1723      * @param htmltree the documentation tree to which the annotation info will be
1724      *        added
1725      */
1726     public void addAnnotationInfo(PackageElement packageElement, Content htmltree) {
1727         addAnnotationInfo(packageElement.getAnnotationMirrors(), htmltree);
1728     }
1729 
1730     /*
1731      * this is a hack to delay dealing with Annotations in the writers, the assumption
1732      * is that all necessary checks have been made to get here.
1733      */
1734     public void addReceiverAnnotationInfo(ExecutableElement method, TypeMirror rcvrTypeMirror,
1735             List<? extends AnnotationMirror> annotationMirrors, Content htmltree) {
1736         TypeMirror rcvrType = method.getReceiverType();
1737         List<? extends AnnotationMirror> annotationMirrors1 = rcvrType.getAnnotationMirrors();
1738         htmltree.add(getAnnotationInfo(annotationMirrors1, false));
1739     }
1740 
1741     /**
1742      * Adds the annotation types for the given element.
1743      *
1744      * @param element the package to write annotations for
1745      * @param htmltree the content tree to which the annotation types will be added
1746      */
1747     public void addAnnotationInfo(Element element, Content htmltree) {
1748         addAnnotationInfo(element.getAnnotationMirrors(), htmltree);
1749     }
1750 
1751     /**
1752      * Add the annotatation types for the given element and parameter.
1753      *
1754      * @param param the parameter to write annotations for.
1755      * @param tree the content tree to which the annotation types will be added
1756      */
1757     public boolean addAnnotationInfo(VariableElement param, Content tree) {
1758         Content annotaionInfo = getAnnotationInfo(param.getAnnotationMirrors(), false);
1759         if (annotaionInfo.isEmpty()) {
1760             return false;
1761         }
1762         tree.add(annotaionInfo);
1763         return true;
1764     }
1765 
1766     /**
1767      * Adds the annotatation types for the given Element.
1768      *
1769      * @param descList a list of annotation mirrors.
1770      * @param htmltree the documentation tree to which the annotation info will be
1771      *        added
1772      */
1773     private void addAnnotationInfo(List<? extends AnnotationMirror> descList, Content htmltree) {
1774         htmltree.add(getAnnotationInfo(descList, true));
1775     }
1776 
1777     /**
1778      * Return a content tree containing the annotation types for the given element.
1779      *
1780      * @param descList a list of annotation mirrors.
1781      * @return the documentation tree containing the annotation info.
1782      */
1783     Content getAnnotationInfo(List<? extends AnnotationMirror> descList, boolean lineBreak) {
1784         List<Content> annotations = getAnnotations(descList, lineBreak);
1785         String sep = "";
1786         ContentBuilder builder = new ContentBuilder();
1787         for (Content annotation: annotations) {
1788             builder.add(sep);
1789             builder.add(annotation);
1790             if (!lineBreak) {
1791                 sep = " ";
1792             }
1793         }
1794         return builder;
1795     }
1796 
1797     /**
1798      * Return the string representations of the annotation types for
1799      * the given doc.
1800      *
1801      * @param descList a list of annotation mirrors.
1802      * @param linkBreak if true, add new line between each member value.
1803      * @return a list of strings representing the annotations being
1804      *         documented.
1805      */
1806     public List<Content> getAnnotations(List<? extends AnnotationMirror> descList, boolean linkBreak) {
1807         List<Content> results = new ArrayList<>();
1808         ContentBuilder annotation;
1809         for (AnnotationMirror aDesc : descList) {
1810             TypeElement annotationElement = (TypeElement)aDesc.getAnnotationType().asElement();
1811             // If an annotation is not documented, do not add it to the list. If
1812             // the annotation is of a repeatable type, and if it is not documented
1813             // and also if its container annotation is not documented, do not add it
1814             // to the list. If an annotation of a repeatable type is not documented
1815             // but its container is documented, it will be added to the list.
1816             if (!utils.isDocumentedAnnotation(annotationElement) &&
1817                 (!isAnnotationDocumented && !isContainerDocumented)) {
1818                 continue;
1819             }
1820             annotation = new ContentBuilder();
1821             isAnnotationDocumented = false;
1822             LinkInfoImpl linkInfo = new LinkInfoImpl(configuration,
1823                                                      LinkInfoImpl.Kind.ANNOTATION, annotationElement);
1824             Map<? extends ExecutableElement, ? extends AnnotationValue> pairs = aDesc.getElementValues();
1825             // If the annotation is synthesized, do not print the container.
1826             if (utils.configuration.workArounds.isSynthesized(aDesc)) {
1827                 for (ExecutableElement ee : pairs.keySet()) {
1828                     AnnotationValue annotationValue = pairs.get(ee);
1829                     List<AnnotationValue> annotationTypeValues = new ArrayList<>();
1830 
1831                     new SimpleAnnotationValueVisitor9<Void, List<AnnotationValue>>() {
1832                         @Override
1833                         public Void visitArray(List<? extends AnnotationValue> vals, List<AnnotationValue> p) {
1834                             p.addAll(vals);
1835                             return null;
1836                         }
1837 
1838                         @Override
1839                         protected Void defaultAction(Object o, List<AnnotationValue> p) {
1840                             p.add(annotationValue);
1841                             return null;
1842                         }
1843                     }.visit(annotationValue, annotationTypeValues);
1844 
1845                     String sep = "";
1846                     for (AnnotationValue av : annotationTypeValues) {
1847                         annotation.add(sep);
1848                         annotation.add(annotationValueToContent(av));
1849                         sep = " ";
1850                     }
1851                 }
1852             } else if (isAnnotationArray(pairs)) {
1853                 // If the container has 1 or more value defined and if the
1854                 // repeatable type annotation is not documented, do not print
1855                 // the container.
1856                 if (pairs.size() == 1 && isAnnotationDocumented) {
1857                     List<AnnotationValue> annotationTypeValues = new ArrayList<>();
1858                     for (AnnotationValue a :  pairs.values()) {
1859                         new SimpleAnnotationValueVisitor9<Void, List<AnnotationValue>>() {
1860                             @Override
1861                             public Void visitArray(List<? extends AnnotationValue> vals, List<AnnotationValue> annotationTypeValues) {
1862                                annotationTypeValues.addAll(vals);
1863                                return null;
1864                             }
1865                         }.visit(a, annotationTypeValues);
1866                     }
1867                     String sep = "";
1868                     for (AnnotationValue av : annotationTypeValues) {
1869                         annotation.add(sep);
1870                         annotation.add(annotationValueToContent(av));
1871                         sep = " ";
1872                     }
1873                 }
1874                 // If the container has 1 or more value defined and if the
1875                 // repeatable type annotation is not documented, print the container.
1876                 else {
1877                     addAnnotations(annotationElement, linkInfo, annotation, pairs, false);
1878                 }
1879             }
1880             else {
1881                 addAnnotations(annotationElement, linkInfo, annotation, pairs, linkBreak);
1882             }
1883             annotation.add(linkBreak ? DocletConstants.NL : "");
1884             results.add(annotation);
1885         }
1886         return results;
1887     }
1888 
1889     /**
1890      * Add annotation to the annotation string.
1891      *
1892      * @param annotationDoc the annotation being documented
1893      * @param linkInfo the information about the link
1894      * @param annotation the annotation string to which the annotation will be added
1895      * @param map annotation type element to annotation value pairs
1896      * @param linkBreak if true, add new line between each member value
1897      */
1898     private void addAnnotations(TypeElement annotationDoc, LinkInfoImpl linkInfo,
1899                                 ContentBuilder annotation,
1900                                 Map<? extends ExecutableElement, ? extends AnnotationValue> map,
1901                                 boolean linkBreak) {
1902         linkInfo.label = new StringContent("@");
1903         linkInfo.label.add(annotationDoc.getSimpleName());
1904         annotation.add(getLink(linkInfo));
1905         if (!map.isEmpty()) {
1906             annotation.add("(");
1907             boolean isFirst = true;
1908             Set<? extends ExecutableElement> keys = map.keySet();
1909             boolean multipleValues = keys.size() > 1;
1910             for (ExecutableElement element : keys) {
1911                 if (isFirst) {
1912                     isFirst = false;
1913                 } else {
1914                     annotation.add(",");
1915                     if (linkBreak) {
1916                         annotation.add(DocletConstants.NL);
1917                         int spaces = annotationDoc.getSimpleName().length() + 2;
1918                         for (int k = 0; k < (spaces); k++) {
1919                             annotation.add(" ");
1920                         }
1921                     }
1922                 }
1923                 String simpleName = element.getSimpleName().toString();
1924                 if (multipleValues || !"value".equals(simpleName)) { // Omit "value=" where unnecessary
1925                     annotation.add(getDocLink(LinkInfoImpl.Kind.ANNOTATION,
1926                                                      element, simpleName, false));
1927                     annotation.add("=");
1928                 }
1929                 AnnotationValue annotationValue = map.get(element);
1930                 List<AnnotationValue> annotationTypeValues = new ArrayList<>();
1931                 new SimpleAnnotationValueVisitor9<Void, AnnotationValue>() {
1932                     @Override
1933                     public Void visitArray(List<? extends AnnotationValue> vals, AnnotationValue p) {
1934                         annotationTypeValues.addAll(vals);
1935                         return null;
1936                     }
1937                     @Override
1938                     protected Void defaultAction(Object o, AnnotationValue p) {
1939                         annotationTypeValues.add(p);
1940                         return null;
1941                     }
1942                 }.visit(annotationValue, annotationValue);
1943                 annotation.add(annotationTypeValues.size() == 1 ? "" : "{");
1944                 String sep = "";
1945                 for (AnnotationValue av : annotationTypeValues) {
1946                     annotation.add(sep);
1947                     annotation.add(annotationValueToContent(av));
1948                     sep = ",";
1949                 }
1950                 annotation.add(annotationTypeValues.size() == 1 ? "" : "}");
1951                 isContainerDocumented = false;
1952             }
1953             annotation.add(")");
1954         }
1955     }
1956 
1957     /**
1958      * Check if the annotation contains an array of annotation as a value. This
1959      * check is to verify if a repeatable type annotation is present or not.
1960      *
1961      * @param pairs annotation type element and value pairs
1962      *
1963      * @return true if the annotation contains an array of annotation as a value.
1964      */
1965     private boolean isAnnotationArray(Map<? extends ExecutableElement, ? extends AnnotationValue> pairs) {
1966         AnnotationValue annotationValue;
1967         for (ExecutableElement ee : pairs.keySet()) {
1968             annotationValue = pairs.get(ee);
1969             boolean rvalue = new SimpleAnnotationValueVisitor9<Boolean, Void>() {
1970                 @Override
1971                 public Boolean visitArray(List<? extends AnnotationValue> vals, Void p) {
1972                     if (vals.size() > 1) {
1973                         if (vals.get(0) instanceof AnnotationMirror) {
1974                             isContainerDocumented = true;
1975                             return new SimpleAnnotationValueVisitor9<Boolean, Void>() {
1976                                 @Override
1977                                 public Boolean visitAnnotation(AnnotationMirror a, Void p) {
1978                                     isContainerDocumented = true;
1979                                     Element asElement = a.getAnnotationType().asElement();
1980                                     if (utils.isDocumentedAnnotation((TypeElement)asElement)) {
1981                                         isAnnotationDocumented = true;
1982                                     }
1983                                     return true;
1984                                 }
1985                                 @Override
1986                                 protected Boolean defaultAction(Object o, Void p) {
1987                                     return false;
1988                                 }
1989                             }.visit(vals.get(0));
1990                         }
1991                     }
1992                     return false;
1993                 }
1994 
1995                 @Override
1996                 protected Boolean defaultAction(Object o, Void p) {
1997                     return false;
1998                 }
1999             }.visit(annotationValue);
2000             if (rvalue) {
2001                 return true;
2002             }
2003         }
2004         return false;
2005     }
2006 
2007     private Content annotationValueToContent(AnnotationValue annotationValue) {
2008         return new SimpleAnnotationValueVisitor9<Content, Void>() {
2009 
2010             @Override
2011             public Content visitType(TypeMirror t, Void p) {
2012                 return new SimpleTypeVisitor9<Content, Void>() {
2013                     @Override
2014                     public Content visitDeclared(DeclaredType t, Void p) {
2015                         LinkInfoImpl linkInfo = new LinkInfoImpl(configuration,
2016                                 LinkInfoImpl.Kind.ANNOTATION, t);
2017                         String name = utils.isIncluded(t.asElement())
2018                                 ? t.asElement().getSimpleName().toString()
2019                                 : utils.getFullyQualifiedName(t.asElement());
2020                         linkInfo.label = new StringContent(name + utils.getDimension(t) + ".class");
2021                         return getLink(linkInfo);
2022                     }
2023                     @Override
2024                     protected Content defaultAction(TypeMirror e, Void p) {
2025                         return new StringContent(t + utils.getDimension(t) + ".class");
2026                     }
2027                 }.visit(t);
2028             }
2029             @Override
2030             public Content visitAnnotation(AnnotationMirror a, Void p) {
2031                 List<Content> list = getAnnotations(List.of(a), false);
2032                 ContentBuilder buf = new ContentBuilder();
2033                 for (Content c : list) {
2034                     buf.add(c);
2035                 }
2036                 return buf;
2037             }
2038             @Override
2039             public Content visitEnumConstant(VariableElement c, Void p) {
2040                 return getDocLink(LinkInfoImpl.Kind.ANNOTATION,
2041                         c, c.getSimpleName(), false);
2042             }
2043             @Override
2044             public Content visitArray(List<? extends AnnotationValue> vals, Void p) {
2045                 ContentBuilder buf = new ContentBuilder();
2046                 String sep = "";
2047                 for (AnnotationValue av : vals) {
2048                     buf.add(sep);
2049                     buf.add(visit(av));
2050                     sep = " ";
2051                 }
2052                 return buf;
2053             }
2054             @Override
2055             protected Content defaultAction(Object o, Void p) {
2056                 return new StringContent(annotationValue.toString());
2057             }
2058         }.visit(annotationValue);
2059     }
2060 
2061     protected TableHeader getPackageTableHeader() {
2062         return new TableHeader(contents.packageLabel, contents.descriptionLabel);
2063     }
2064 
2065     /**
2066      * Generates a string for use in a description meta element,
2067      * based on an element and its enclosing elements
2068      * @param prefix a prefix for the string
2069      * @param elem the element
2070      * @return the description
2071      */
2072     static String getDescription(String prefix, Element elem) {
2073         LinkedList<Element> chain = new LinkedList<>();
2074         for (Element e = elem; e != null; e = e.getEnclosingElement()) {
2075             // ignore unnamed enclosing elements
2076             if (e.getSimpleName().length() == 0 && e != elem) {
2077                 break;
2078             }
2079             chain.addFirst(e);
2080         }
2081         StringBuilder sb = new StringBuilder();
2082         for (Element e: chain) {
2083             CharSequence name;
2084             switch (e.getKind()) {
2085                 case MODULE:
2086                 case PACKAGE:
2087                     name = ((QualifiedNameable) e).getQualifiedName();
2088                     if (name.length() == 0) {
2089                         name = "<unnamed>";
2090                     }
2091                     break;
2092 
2093                 default:
2094                     name = e.getSimpleName();
2095                     break;
2096             }
2097 
2098             if (sb.length() == 0) {
2099                 sb.append(prefix).append(": ");
2100             } else {
2101                 sb.append(", ");
2102             }
2103             sb.append(e.getKind().toString().toLowerCase(Locale.US).replace("_", " "))
2104                     .append(": ")
2105                     .append(name);
2106         }
2107         return sb.toString();
2108     }
2109 
2110     static String getGenerator(Class<?> clazz) {
2111         return "javadoc/" + clazz.getSimpleName();
2112     }
2113 
2114     /**
2115      * Returns an HtmlTree for the SCRIPT tag.
2116      *
2117      * @return an HtmlTree for the SCRIPT tag
2118      */
2119     protected Script getWinTitleScript() {
2120         Script script = new Script();
2121         if (winTitle != null && winTitle.length() > 0) {
2122             script.append("<!--\n" +
2123                     "    try {\n" +
2124                     "        if (location.href.indexOf('is-external=true') == -1) {\n" +
2125                     "            parent.document.title=")
2126                     .appendStringLiteral(winTitle)
2127                     .append(";\n" +
2128                     "        }\n" +
2129                     "    }\n" +
2130                     "    catch(err) {\n" +
2131                     "    }\n" +
2132                     "//-->\n");
2133         }
2134         return script;
2135     }
2136 
2137     /**
2138      * Returns an HtmlTree for the BODY tag.
2139      *
2140      * @param title title for the window
2141      * @return an HtmlTree for the BODY tag
2142      */
2143     public HtmlTree getBody(String title) {
2144         HtmlTree body = new HtmlTree(HtmlTag.BODY);
2145         body.put(HtmlAttr.CLASS, getBodyClass());
2146 
2147         this.winTitle = title;
2148         // Don't print windowtitle script for overview-frame, allclasses-frame
2149         // and package-frame
2150         body.add(mainBodyScript.asContent());
2151         Content noScript = HtmlTree.NOSCRIPT(HtmlTree.DIV(contents.noScriptMessage));
2152         body.add(noScript);
2153         return body;
2154     }
2155 
2156     public String getBodyClass() {
2157         return getClass().getSimpleName()
2158                 .replaceAll("(Writer)?(Impl)?$", "")
2159                 .replaceAll("AnnotationType", "Class")
2160                 .replaceAll("(.)([A-Z])", "$1-$2")
2161                 .replaceAll("(?i)^(module|package|class)$", "$1-declaration")
2162                 .toLowerCase(Locale.US);
2163     }
2164 
2165     Script getMainBodyScript() {
2166         return mainBodyScript;
2167     }
2168 
2169     /**
2170      * Returns the path of module/package specific stylesheets for the element.
2171      * @param element module/Package element
2172      * @return list of path of module/package specific stylesheets
2173      * @throws DocFileIOException
2174      */
2175     List<DocPath> getLocalStylesheets(Element element) throws DocFileIOException {
2176         List<DocPath> stylesheets = new ArrayList<>();
2177         DocPath basePath = null;
2178         if (element instanceof PackageElement) {
2179             stylesheets.addAll(getModuleStylesheets((PackageElement)element));
2180             basePath = docPaths.forPackage((PackageElement)element);
2181         } else if (element instanceof ModuleElement) {
2182             basePath = DocPaths.forModule((ModuleElement)element);
2183         }
2184         for (DocPath stylesheet : getStylesheets(element)) {
2185             stylesheets.add(basePath.resolve(stylesheet.getPath()));
2186         }
2187         return stylesheets;
2188     }
2189 
2190     private List<DocPath> getModuleStylesheets(PackageElement pkgElement) throws
2191             DocFileIOException {
2192         List<DocPath> moduleStylesheets = new ArrayList<>();
2193         ModuleElement moduleElement = utils.containingModule(pkgElement);
2194         if (moduleElement != null && !moduleElement.isUnnamed()) {
2195             List<DocPath> localStylesheets = getStylesheets(moduleElement);
2196             DocPath basePath = DocPaths.forModule(moduleElement);
2197             for (DocPath stylesheet : localStylesheets) {
2198                 moduleStylesheets.add(basePath.resolve(stylesheet));
2199             }
2200         }
2201         return moduleStylesheets;
2202     }
2203 
2204     private List<DocPath> getStylesheets(Element element) throws DocFileIOException {
2205         List<DocPath> localStylesheets = configuration.localStylesheetMap.get(element);
2206         if (localStylesheets == null) {
2207             DocFilesHandlerImpl docFilesHandler = (DocFilesHandlerImpl)configuration
2208                     .getWriterFactory().getDocFilesHandler(element);
2209             localStylesheets = docFilesHandler.getStylesheets();
2210             configuration.localStylesheetMap.put(element, localStylesheets);
2211         }
2212         return localStylesheets;
2213     }
2214 
2215 }