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