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