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