HtmlDocletWriter.java revision 3692:87b48a8fb3cf
1217309Snwhitehorn/*
2251843Sbapt * Copyright (c) 1998, 2016, Oracle and/or its affiliates. All rights reserved.
3217309Snwhitehorn * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4217309Snwhitehorn *
5217309Snwhitehorn * This code is free software; you can redistribute it and/or modify it
6251843Sbapt * under the terms of the GNU General Public License version 2 only, as
7217309Snwhitehorn * published by the Free Software Foundation.  Oracle designates this
8217309Snwhitehorn * particular file as subject to the "Classpath" exception as provided
9217309Snwhitehorn * by Oracle in the LICENSE file that accompanied this code.
10217309Snwhitehorn *
11217309Snwhitehorn * This code is distributed in the hope that it will be useful, but WITHOUT
12217309Snwhitehorn * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13217309Snwhitehorn * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14217309Snwhitehorn * version 2 for more details (a copy is included in the LICENSE file that
15217309Snwhitehorn * accompanied this code).
16217309Snwhitehorn *
17217309Snwhitehorn * You should have received a copy of the GNU General Public License version
18217309Snwhitehorn * 2 along with this work; if not, write to the Free Software Foundation,
19217309Snwhitehorn * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20217309Snwhitehorn *
21217309Snwhitehorn * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22217309Snwhitehorn * or visit www.oracle.com if you need additional information or have any
23217309Snwhitehorn * questions.
24217309Snwhitehorn */
25217309Snwhitehorn
26217309Snwhitehornpackage jdk.javadoc.internal.doclets.formats.html;
27217309Snwhitehorn
28217309Snwhitehornimport java.text.SimpleDateFormat;
29217309Snwhitehornimport java.util.*;
30217309Snwhitehornimport java.util.regex.Matcher;
31217309Snwhitehornimport java.util.regex.Pattern;
32217309Snwhitehorn
33217309Snwhitehornimport javax.lang.model.element.AnnotationMirror;
34217309Snwhitehornimport javax.lang.model.element.AnnotationValue;
35217309Snwhitehornimport javax.lang.model.element.Element;
36217309Snwhitehornimport javax.lang.model.element.ExecutableElement;
37217309Snwhitehornimport javax.lang.model.element.ModuleElement;
38217309Snwhitehornimport javax.lang.model.element.Name;
39217309Snwhitehornimport javax.lang.model.element.PackageElement;
40224014Snwhitehornimport javax.lang.model.element.TypeElement;
41217309Snwhitehornimport javax.lang.model.element.VariableElement;
42251843Sbaptimport javax.lang.model.type.DeclaredType;
43217309Snwhitehornimport javax.lang.model.type.TypeMirror;
44217309Snwhitehornimport javax.lang.model.util.SimpleAnnotationValueVisitor9;
45217309Snwhitehornimport javax.lang.model.util.SimpleElementVisitor9;
46217309Snwhitehornimport javax.lang.model.util.SimpleTypeVisitor9;
47217309Snwhitehorn
48217309Snwhitehornimport com.sun.source.doctree.AttributeTree;
49251843Sbaptimport com.sun.source.doctree.AttributeTree.ValueKind;
50217309Snwhitehornimport com.sun.source.doctree.CommentTree;
51217309Snwhitehornimport com.sun.source.doctree.DocRootTree;
52217309Snwhitehornimport com.sun.source.doctree.DocTree;
53217309Snwhitehornimport com.sun.source.doctree.DocTree.Kind;
54217309Snwhitehornimport com.sun.source.doctree.EndElementTree;
55217309Snwhitehornimport com.sun.source.doctree.EntityTree;
56217309Snwhitehornimport com.sun.source.doctree.ErroneousTree;
57217309Snwhitehornimport com.sun.source.doctree.IndexTree;
58217309Snwhitehornimport com.sun.source.doctree.InheritDocTree;
59251843Sbaptimport com.sun.source.doctree.LinkTree;
60251843Sbaptimport com.sun.source.doctree.LiteralTree;
61251843Sbaptimport com.sun.source.doctree.SeeTree;
62251843Sbaptimport com.sun.source.doctree.StartElementTree;
63251843Sbaptimport com.sun.source.doctree.TextTree;
64217309Snwhitehornimport com.sun.source.util.SimpleDocTreeVisitor;
65251843Sbapt
66251843Sbaptimport jdk.javadoc.internal.doclets.formats.html.markup.Comment;
67251843Sbaptimport jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder;
68217309Snwhitehornimport jdk.javadoc.internal.doclets.formats.html.markup.DocType;
69251843Sbaptimport jdk.javadoc.internal.doclets.formats.html.markup.HtmlAttr;
70251843Sbaptimport jdk.javadoc.internal.doclets.formats.html.markup.HtmlConstants;
71217309Snwhitehornimport jdk.javadoc.internal.doclets.formats.html.markup.HtmlDocWriter;
72217309Snwhitehornimport jdk.javadoc.internal.doclets.formats.html.markup.HtmlDocument;
73217309Snwhitehornimport jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle;
74217309Snwhitehornimport jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag;
75217309Snwhitehornimport jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree;
76217309Snwhitehornimport jdk.javadoc.internal.doclets.formats.html.markup.RawHtml;
77217309Snwhitehornimport jdk.javadoc.internal.doclets.formats.html.markup.StringContent;
78217309Snwhitehornimport jdk.javadoc.internal.doclets.toolkit.AnnotationTypeWriter;
79217309Snwhitehornimport jdk.javadoc.internal.doclets.toolkit.ClassWriter;
80217309Snwhitehornimport jdk.javadoc.internal.doclets.toolkit.Configuration;
81217309Snwhitehornimport jdk.javadoc.internal.doclets.toolkit.Content;
82217309Snwhitehornimport jdk.javadoc.internal.doclets.toolkit.Messages;
83217309Snwhitehornimport jdk.javadoc.internal.doclets.toolkit.PackageSummaryWriter;
84217309Snwhitehornimport jdk.javadoc.internal.doclets.toolkit.Resources;
85217309Snwhitehornimport jdk.javadoc.internal.doclets.toolkit.taglets.DocRootTaglet;
86217309Snwhitehornimport jdk.javadoc.internal.doclets.toolkit.taglets.TagletWriter;
87217309Snwhitehornimport jdk.javadoc.internal.doclets.toolkit.util.CommentHelper;
88217309Snwhitehornimport jdk.javadoc.internal.doclets.toolkit.util.DocFile;
89217309Snwhitehornimport jdk.javadoc.internal.doclets.toolkit.util.DocFileIOException;
90217309Snwhitehornimport jdk.javadoc.internal.doclets.toolkit.util.DocLink;
91217309Snwhitehornimport jdk.javadoc.internal.doclets.toolkit.util.DocPath;
92217309Snwhitehornimport jdk.javadoc.internal.doclets.toolkit.util.DocPaths;
93217309Snwhitehornimport jdk.javadoc.internal.doclets.toolkit.util.DocletConstants;
94217309Snwhitehornimport jdk.javadoc.internal.doclets.toolkit.util.ImplementedMethods;
95217309Snwhitehornimport jdk.javadoc.internal.doclets.toolkit.util.Utils;
96217309Snwhitehorn
97217309Snwhitehornimport static com.sun.source.doctree.AttributeTree.ValueKind.*;
98217309Snwhitehornimport static com.sun.source.doctree.DocTree.Kind.*;
99217309Snwhitehornimport static jdk.javadoc.internal.doclets.formats.html.markup.HtmlDocWriter.CONTENT_TYPE;
100251843Sbaptimport static jdk.javadoc.internal.doclets.toolkit.util.CommentHelper.SPACER;
101217309Snwhitehorn
102217309Snwhitehorn
103251843Sbapt/**
104217309Snwhitehorn * Class for the Html Format Code Generation specific to JavaDoc.
105217309Snwhitehorn * This Class contains methods related to the Html Code Generation which
106251843Sbapt * are used extensively while generating the entire documentation.
107217309Snwhitehorn *
108217309Snwhitehorn *  <p><b>This is NOT part of any supported API.
109224014Snwhitehorn *  If you write code that depends on this, you do so at your own risk.
110217309Snwhitehorn *  This code and its internal interfaces are subject to change or
111217309Snwhitehorn *  deletion without notice.</b>
112217309Snwhitehorn *
113217309Snwhitehorn * @author Atul M Dambalkar
114217309Snwhitehorn * @author Robert Field
115251843Sbapt * @author Bhavesh Patel (Modified)
116217309Snwhitehorn */
117217309Snwhitehornpublic class HtmlDocletWriter extends HtmlDocWriter {
118217309Snwhitehorn
119217309Snwhitehorn    /**
120217309Snwhitehorn     * Relative path from the file getting generated to the destination
121217309Snwhitehorn     * directory. For example, if the file getting generated is
122217309Snwhitehorn     * "java/lang/Object.html", then the path to the root is "../..".
123251843Sbapt     * This string can be empty if the file getting generated is in
124217309Snwhitehorn     * the destination directory.
125217309Snwhitehorn     */
126217309Snwhitehorn    public final DocPath pathToRoot;
127217309Snwhitehorn
128217309Snwhitehorn    /**
129217309Snwhitehorn     * Platform-independent path from the current or the
130217309Snwhitehorn     * destination directory to the file getting generated.
131217309Snwhitehorn     * Used when creating the file.
132217309Snwhitehorn     */
133217309Snwhitehorn    public final DocPath path;
134217309Snwhitehorn
135217309Snwhitehorn    /**
136217309Snwhitehorn     * Name of the file getting generated. If the file getting generated is
137217309Snwhitehorn     * "java/lang/Object.html", then the filename is "Object.html".
138217309Snwhitehorn     */
139217309Snwhitehorn    public final DocPath filename;
140217309Snwhitehorn
141217309Snwhitehorn    /**
142217309Snwhitehorn     * The global configuration information for this run.
143217309Snwhitehorn     */
144217309Snwhitehorn    public final ConfigurationImpl configuration;
145217309Snwhitehorn
146217309Snwhitehorn    protected final Utils utils;
147217309Snwhitehorn
148217309Snwhitehorn    protected final Contents contents;
149217309Snwhitehorn
150217309Snwhitehorn    protected final Messages messages;
151217309Snwhitehorn
152217309Snwhitehorn    protected final Resources resources;
153217309Snwhitehorn
154217309Snwhitehorn    /**
155217309Snwhitehorn     * To check whether annotation heading is printed or not.
156251843Sbapt     */
157217309Snwhitehorn    protected boolean printedAnnotationHeading = false;
158217309Snwhitehorn
159251843Sbapt    /**
160251843Sbapt     * To check whether annotation field heading is printed or not.
161251843Sbapt     */
162251843Sbapt    protected boolean printedAnnotationFieldHeading = false;
163251843Sbapt
164251843Sbapt    /**
165251843Sbapt     * To check whether the repeated annotations is documented or not.
166251843Sbapt     */
167251843Sbapt    private boolean isAnnotationDocumented = false;
168251843Sbapt
169251843Sbapt    /**
170251843Sbapt     * To check whether the container annotations is documented or not.
171217309Snwhitehorn     */
172217309Snwhitehorn    private boolean isContainerDocumented = false;
173217309Snwhitehorn
174217309Snwhitehorn    HtmlTree fixedNavDiv = new HtmlTree(HtmlTag.DIV);
175217309Snwhitehorn
176217309Snwhitehorn    final static Pattern IMPROPER_HTML_CHARS = Pattern.compile(".*[&<>].*");
177217309Snwhitehorn
178217309Snwhitehorn    /**
179224014Snwhitehorn     * Constructor to construct the HtmlStandardWriter object.
180217309Snwhitehorn     *
181251843Sbapt     * @param path File to be generated.
182217309Snwhitehorn     */
183217309Snwhitehorn    public HtmlDocletWriter(ConfigurationImpl configuration, DocPath path) {
184217309Snwhitehorn        super(configuration, path);
185217309Snwhitehorn        this.configuration = configuration;
186217309Snwhitehorn        this.contents = configuration.contents;
187217309Snwhitehorn        this.messages = configuration.messages;
188251843Sbapt        this.resources = configuration.resources;
189251843Sbapt        this.utils = configuration.utils;
190251843Sbapt        this.path = path;
191217309Snwhitehorn        this.pathToRoot = path.parent().invert();
192217309Snwhitehorn        this.filename = path.basename();
193    }
194
195    /**
196     * Replace {&#064;docRoot} tag used in options that accept HTML text, such
197     * as -header, -footer, -top and -bottom, and when converting a relative
198     * HREF where commentTagsToString inserts a {&#064;docRoot} where one was
199     * missing.  (Also see DocRootTaglet for {&#064;docRoot} tags in doc
200     * comments.)
201     * <p>
202     * Replace {&#064;docRoot} tag in htmlstr with the relative path to the
203     * destination directory from the directory where the file is being
204     * written, looping to handle all such tags in htmlstr.
205     * <p>
206     * For example, for "-d docs" and -header containing {&#064;docRoot}, when
207     * the HTML page for source file p/C1.java is being generated, the
208     * {&#064;docRoot} tag would be inserted into the header as "../",
209     * the relative path from docs/p/ to docs/ (the document root).
210     * <p>
211     * Note: This doc comment was written with '&amp;#064;' representing '@'
212     * to prevent the inline tag from being interpreted.
213     */
214    public String replaceDocRootDir(String htmlstr) {
215        // Return if no inline tags exist
216        int index = htmlstr.indexOf("{@");
217        if (index < 0) {
218            return htmlstr;
219        }
220        Matcher docrootMatcher = docrootPattern.matcher(htmlstr);
221        if (!docrootMatcher.find()) {
222            return htmlstr;
223        }
224        StringBuilder buf = new StringBuilder();
225        int prevEnd = 0;
226        do {
227            int match = docrootMatcher.start();
228            // append htmlstr up to start of next {@docroot}
229            buf.append(htmlstr.substring(prevEnd, match));
230            prevEnd = docrootMatcher.end();
231            if (configuration.docrootparent.length() > 0 && htmlstr.startsWith("/..", prevEnd)) {
232                // Insert the absolute link if {@docRoot} is followed by "/..".
233                buf.append(configuration.docrootparent);
234                prevEnd += 3;
235            } else {
236                // Insert relative path where {@docRoot} was located
237                buf.append(pathToRoot.isEmpty() ? "." : pathToRoot.getPath());
238            }
239            // Append slash if next character is not a slash
240            if (prevEnd < htmlstr.length() && htmlstr.charAt(prevEnd) != '/') {
241                buf.append('/');
242            }
243        } while (docrootMatcher.find());
244        buf.append(htmlstr.substring(prevEnd));
245        return buf.toString();
246    }
247    //where:
248        // Note: {@docRoot} is not case sensitive when passed in w/command line option:
249        private static final Pattern docrootPattern =
250                Pattern.compile(Pattern.quote("{@docroot}"), Pattern.CASE_INSENSITIVE);
251
252    /**
253     * Get the script to show or hide the All classes link.
254     *
255     * @param id id of the element to show or hide
256     * @return a content tree for the script
257     */
258    public Content getAllClassesLinkScript(String id) {
259        HtmlTree script = HtmlTree.SCRIPT();
260        String scriptCode = "<!--\n" +
261                "  allClassesLink = document.getElementById(\"" + id + "\");\n" +
262                "  if(window==top) {\n" +
263                "    allClassesLink.style.display = \"block\";\n" +
264                "  }\n" +
265                "  else {\n" +
266                "    allClassesLink.style.display = \"none\";\n" +
267                "  }\n" +
268                "  //-->\n";
269        Content scriptContent = new RawHtml(scriptCode.replace("\n", DocletConstants.NL));
270        script.addContent(scriptContent);
271        Content div = HtmlTree.DIV(script);
272        Content div_noscript = HtmlTree.DIV(contents.noScriptMessage);
273        Content noScript = HtmlTree.NOSCRIPT(div_noscript);
274        div.addContent(noScript);
275        return div;
276    }
277
278    /**
279     * Add method information.
280     *
281     * @param method the method to be documented
282     * @param dl the content tree to which the method information will be added
283     */
284    private void addMethodInfo(ExecutableElement method, Content dl) {
285        TypeElement enclosing = utils.getEnclosingTypeElement(method);
286        List<? extends TypeMirror> intfacs = enclosing.getInterfaces();
287        ExecutableElement overriddenMethod = utils.overriddenMethod(method);
288        // Check whether there is any implementation or overridden info to be
289        // printed. If no overridden or implementation info needs to be
290        // printed, do not print this section.
291        if ((!intfacs.isEmpty()
292                && new ImplementedMethods(method, this.configuration).build().isEmpty() == false)
293                || overriddenMethod != null) {
294            MethodWriterImpl.addImplementsInfo(this, method, dl);
295            if (overriddenMethod != null) {
296                MethodWriterImpl.addOverridden(this,
297                        utils.overriddenType(method),
298                        overriddenMethod,
299                        dl);
300            }
301        }
302    }
303
304    /**
305     * Adds the tags information.
306     *
307     * @param e the Element for which the tags will be generated
308     * @param htmltree the documentation tree to which the tags will be added
309     */
310    protected void addTagsInfo(Element e, Content htmltree) {
311        if (configuration.nocomment) {
312            return;
313        }
314        Content dl = new HtmlTree(HtmlTag.DL);
315        if (utils.isExecutableElement(e) && !utils.isConstructor(e)) {
316            addMethodInfo((ExecutableElement)e, dl);
317        }
318        Content output = new ContentBuilder();
319        TagletWriter.genTagOutput(configuration.tagletManager, e,
320            configuration.tagletManager.getCustomTaglets(e),
321                getTagletWriterInstance(false), output);
322        dl.addContent(output);
323        htmltree.addContent(dl);
324    }
325
326    /**
327     * Check whether there are any tags for Serialization Overview
328     * section to be printed.
329     *
330     * @param field the VariableElement object to check for tags.
331     * @return true if there are tags to be printed else return false.
332     */
333    protected boolean hasSerializationOverviewTags(VariableElement field) {
334        Content output = new ContentBuilder();
335        TagletWriter.genTagOutput(configuration.tagletManager, field,
336                configuration.tagletManager.getCustomTaglets(field),
337                getTagletWriterInstance(false), output);
338        return !output.isEmpty();
339    }
340
341    /**
342     * Returns a TagletWriter that knows how to write HTML.
343     *
344     * @return a TagletWriter that knows how to write HTML.
345     */
346    public TagletWriter getTagletWriterInstance(boolean isFirstSentence) {
347        return new TagletWriterImpl(this, isFirstSentence);
348    }
349
350    /**
351     * Get Package link, with target frame.
352     *
353     * @param pkg The link will be to the "package-summary.html" page for this package
354     * @param target name of the target frame
355     * @param label tag for the link
356     * @return a content for the target package link
357     */
358    public Content getTargetPackageLink(PackageElement pkg, String target,
359            Content label) {
360        return getHyperLink(pathString(pkg, DocPaths.PACKAGE_SUMMARY), label, "", target);
361    }
362
363    /**
364     * Get Module Package link, with target frame.
365     *
366     * @param pkg the PackageElement
367     * @param target name of the target frame
368     * @param label tag for the link
369     * @param mdle the module being documented
370     * @return a content for the target module packages link
371     */
372    public Content getTargetModulePackageLink(PackageElement pkg, String target,
373            Content label, ModuleElement mdle) {
374        return getHyperLink(pathString(pkg, DocPaths.PACKAGE_SUMMARY),
375                label, "", target);
376    }
377
378    /**
379     * Get Module link, with target frame.
380     *
381     * @param target name of the target frame
382     * @param label tag for the link
383     * @param mdle the module being documented
384     * @return a content for the target module link
385     */
386    public Content getTargetModuleLink(String target, Content label, ModuleElement mdle) {
387        return getHyperLink(pathToRoot.resolve(
388                DocPaths.moduleSummary(mdle)), label, "", target);
389    }
390
391    public void addClassesSummary(SortedSet<TypeElement> classes, String label,
392            String tableSummary, List<String> tableHeader, Content summaryContentTree) {
393        if (!classes.isEmpty()) {
394            Content caption = getTableCaption(new RawHtml(label));
395            Content table = (configuration.isOutputHtml5())
396                    ? HtmlTree.TABLE(HtmlStyle.typeSummary, caption)
397                    : HtmlTree.TABLE(HtmlStyle.typeSummary, tableSummary, caption);
398            table.addContent(getSummaryTableHeader(tableHeader, "col"));
399            Content tbody = new HtmlTree(HtmlTag.TBODY);
400            boolean altColor = true;
401            for (TypeElement te : classes) {
402                if (!utils.isCoreClass(te) ||
403                    !configuration.isGeneratedDoc(te)) {
404                    continue;
405                }
406                Content classContent = getLink(new LinkInfoImpl(
407                        configuration, LinkInfoImpl.Kind.PACKAGE, te));
408                Content tdClass = HtmlTree.TH_ROW_SCOPE(HtmlStyle.colFirst, classContent);
409                HtmlTree tr = HtmlTree.TR(tdClass);
410                tr.addStyle(altColor ? HtmlStyle.altColor : HtmlStyle.rowColor);
411                altColor = !altColor;
412                HtmlTree tdClassDescription = new HtmlTree(HtmlTag.TD);
413                tdClassDescription.addStyle(HtmlStyle.colLast);
414                if (utils.isDeprecated(te)) {
415                    tdClassDescription.addContent(contents.deprecatedLabel);
416                    List<? extends DocTree> tags = utils.getDeprecatedTrees(te);
417                    if (!tags.isEmpty()) {
418                        addSummaryDeprecatedComment(te, tags.get(0), tdClassDescription);
419                    }
420                } else {
421                    addSummaryComment(te, tdClassDescription);
422                }
423                tr.addContent(tdClassDescription);
424                tbody.addContent(tr);
425            }
426            table.addContent(tbody);
427            summaryContentTree.addContent(table);
428        }
429    }
430
431    /**
432     * Generates the HTML document tree and prints it out.
433     *
434     * @param metakeywords Array of String keywords for META tag. Each element
435     *                     of the array is assigned to a separate META tag.
436     *                     Pass in null for no array
437     * @param includeScript true if printing windowtitle script
438     *                      false for files that appear in the left-hand frames
439     * @param body the body htmltree to be included in the document
440     * @throws DocFileIOException if there is a problem writing the file
441     */
442    public void printHtmlDocument(List<String> metakeywords, boolean includeScript,
443            Content body) throws DocFileIOException {
444        Content htmlDocType = configuration.isOutputHtml5()
445                ? DocType.HTML5
446                : DocType.TRANSITIONAL;
447        Content htmlComment = new Comment(configuration.getText("doclet.New_Page"));
448        Content head = new HtmlTree(HtmlTag.HEAD);
449        head.addContent(getGeneratedBy(!configuration.notimestamp));
450        head.addContent(getTitle());
451        Content meta = HtmlTree.META("Content-Type", CONTENT_TYPE,
452                (configuration.charset.length() > 0) ?
453                        configuration.charset : HtmlConstants.HTML_DEFAULT_CHARSET);
454        head.addContent(meta);
455        if (!configuration.notimestamp) {
456            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
457            meta = HtmlTree.META(configuration.isOutputHtml5()
458                    ? "dc.created"
459                    : "date", dateFormat.format(new Date()));
460            head.addContent(meta);
461        }
462        if (metakeywords != null) {
463            for (String metakeyword : metakeywords) {
464                meta = HtmlTree.META("keywords", metakeyword);
465                head.addContent(meta);
466            }
467        }
468        addStyleSheetProperties(head);
469        addScriptProperties(head);
470        Content htmlTree = HtmlTree.HTML(configuration.getLocale().getLanguage(),
471                head, body);
472        Content htmlDocument = new HtmlDocument(htmlDocType,
473                htmlComment, htmlTree);
474        write(htmlDocument);
475    }
476
477    /**
478     * Get the window title.
479     *
480     * @param title the title string to construct the complete window title
481     * @return the window title string
482     */
483    public String getWindowTitle(String title) {
484        if (configuration.windowtitle.length() > 0) {
485            title += " (" + configuration.windowtitle  + ")";
486        }
487        return title;
488    }
489
490    /**
491     * Get user specified header and the footer.
492     *
493     * @param header if true print the user provided header else print the
494     * user provided footer.
495     */
496    public Content getUserHeaderFooter(boolean header) {
497        String content;
498        if (header) {
499            content = replaceDocRootDir(configuration.header);
500        } else {
501            if (configuration.footer.length() != 0) {
502                content = replaceDocRootDir(configuration.footer);
503            } else {
504                content = replaceDocRootDir(configuration.header);
505            }
506        }
507        Content rawContent = new RawHtml(content);
508        return rawContent;
509    }
510
511    /**
512     * Adds the user specified top.
513     *
514     * @param htmlTree the content tree to which user specified top will be added
515     */
516    public void addTop(Content htmlTree) {
517        Content top = new RawHtml(replaceDocRootDir(configuration.top));
518        fixedNavDiv.addContent(top);
519    }
520
521    /**
522     * Adds the user specified bottom.
523     *
524     * @param htmlTree the content tree to which user specified bottom will be added
525     */
526    public void addBottom(Content htmlTree) {
527        Content bottom = new RawHtml(replaceDocRootDir(configuration.bottom));
528        Content small = HtmlTree.SMALL(bottom);
529        Content p = HtmlTree.P(HtmlStyle.legalCopy, small);
530        htmlTree.addContent(p);
531    }
532
533    /**
534     * Adds the navigation bar for the Html page at the top and and the bottom.
535     *
536     * @param header If true print navigation bar at the top of the page else
537     * @param htmlTree the HtmlTree to which the nav links will be added
538     */
539    protected void addNavLinks(boolean header, Content htmlTree) {
540        if (!configuration.nonavbar) {
541            Content tree = (configuration.allowTag(HtmlTag.NAV))
542                    ? HtmlTree.NAV()
543                    : htmlTree;
544            String allClassesId = "allclasses_";
545            HtmlTree navDiv = new HtmlTree(HtmlTag.DIV);
546            fixedNavDiv.addStyle(HtmlStyle.fixedNav);
547            Content skipNavLinks = configuration.getContent("doclet.Skip_navigation_links");
548            if (header) {
549                fixedNavDiv.addContent(HtmlConstants.START_OF_TOP_NAVBAR);
550                navDiv.addStyle(HtmlStyle.topNav);
551                allClassesId += "navbar_top";
552                Content a = getMarkerAnchor(SectionName.NAVBAR_TOP);
553                //WCAG - Hyperlinks should contain text or an image with alt text - for AT tools
554                navDiv.addContent(a);
555                Content skipLinkContent = HtmlTree.DIV(HtmlStyle.skipNav, getHyperLink(
556                    getDocLink(SectionName.SKIP_NAVBAR_TOP), skipNavLinks,
557                    skipNavLinks.toString(), ""));
558                navDiv.addContent(skipLinkContent);
559            } else {
560                tree.addContent(HtmlConstants.START_OF_BOTTOM_NAVBAR);
561                navDiv.addStyle(HtmlStyle.bottomNav);
562                allClassesId += "navbar_bottom";
563                Content a = getMarkerAnchor(SectionName.NAVBAR_BOTTOM);
564                navDiv.addContent(a);
565                Content skipLinkContent = HtmlTree.DIV(HtmlStyle.skipNav, getHyperLink(
566                    getDocLink(SectionName.SKIP_NAVBAR_BOTTOM), skipNavLinks,
567                    skipNavLinks.toString(), ""));
568                navDiv.addContent(skipLinkContent);
569            }
570            if (header) {
571                navDiv.addContent(getMarkerAnchor(SectionName.NAVBAR_TOP_FIRSTROW));
572            } else {
573                navDiv.addContent(getMarkerAnchor(SectionName.NAVBAR_BOTTOM_FIRSTROW));
574            }
575            HtmlTree navList = new HtmlTree(HtmlTag.UL);
576            navList.addStyle(HtmlStyle.navList);
577            navList.addAttr(HtmlAttr.TITLE,
578                            configuration.getText("doclet.Navigation"));
579            if (configuration.createoverview) {
580                navList.addContent(getNavLinkContents());
581            }
582            if (configuration.showModules) {
583                if (configuration.modules.size() == 1) {
584                    navList.addContent(getNavLinkModule(configuration.modules.first()));
585                } else if (!configuration.modules.isEmpty()) {
586                    navList.addContent(getNavLinkModule());
587                }
588            }
589            if (configuration.packages.size() == 1) {
590                navList.addContent(getNavLinkPackage(configuration.packages.first()));
591            } else if (!configuration.packages.isEmpty()) {
592                navList.addContent(getNavLinkPackage());
593            }
594            navList.addContent(getNavLinkClass());
595            if(configuration.classuse) {
596                navList.addContent(getNavLinkClassUse());
597            }
598            if(configuration.createtree) {
599                navList.addContent(getNavLinkTree());
600            }
601            if(!(configuration.nodeprecated ||
602                     configuration.nodeprecatedlist)) {
603                navList.addContent(getNavLinkDeprecated());
604            }
605            if(configuration.createindex) {
606                navList.addContent(getNavLinkIndex());
607            }
608            if (!configuration.nohelp) {
609                navList.addContent(getNavLinkHelp());
610            }
611            navDiv.addContent(navList);
612            Content aboutDiv = HtmlTree.DIV(HtmlStyle.aboutLanguage, getUserHeaderFooter(header));
613            navDiv.addContent(aboutDiv);
614            if (header) {
615                fixedNavDiv.addContent(navDiv);
616            } else {
617                tree.addContent(navDiv);
618            }
619            Content ulNav = HtmlTree.UL(HtmlStyle.navList, getNavLinkPrevious(), getNavLinkNext());
620            Content subDiv = HtmlTree.DIV(HtmlStyle.subNav, ulNav);
621            if (configuration.frames) {
622                Content ulFrames = HtmlTree.UL(HtmlStyle.navList,
623                    getNavShowLists(), getNavHideLists(filename));
624                subDiv.addContent(ulFrames);
625            }
626            HtmlTree ulAllClasses = HtmlTree.UL(HtmlStyle.navList, getNavLinkClassIndex());
627            ulAllClasses.addAttr(HtmlAttr.ID, allClassesId);
628            subDiv.addContent(ulAllClasses);
629            if (header && configuration.createindex) {
630                HtmlTree inputText = HtmlTree.INPUT("text", "search");
631                HtmlTree inputReset = HtmlTree.INPUT("reset", "reset");
632                Content searchTxt = new ContentBuilder();
633                searchTxt.addContent(configuration.getContent("doclet.search"));
634                searchTxt.addContent(Contents.SPACE);
635                HtmlTree liInput = HtmlTree.LI(HtmlTree.SPAN(searchTxt));
636                liInput.addContent(inputText);
637                liInput.addContent(inputReset);
638                HtmlTree ulSearch = HtmlTree.UL(HtmlStyle.navListSearch, liInput);
639                subDiv.addContent(ulSearch);
640            }
641            subDiv.addContent(getAllClassesLinkScript(allClassesId));
642            addSummaryDetailLinks(subDiv);
643            if (header) {
644                subDiv.addContent(getMarkerAnchor(SectionName.SKIP_NAVBAR_TOP));
645                fixedNavDiv.addContent(subDiv);
646                fixedNavDiv.addContent(HtmlConstants.END_OF_TOP_NAVBAR);
647                tree.addContent(fixedNavDiv);
648                HtmlTree paddingDiv = HtmlTree.DIV(HtmlStyle.navPadding, Contents.SPACE);
649                tree.addContent(paddingDiv);
650            } else {
651                subDiv.addContent(getMarkerAnchor(SectionName.SKIP_NAVBAR_BOTTOM));
652                tree.addContent(subDiv);
653                tree.addContent(HtmlConstants.END_OF_BOTTOM_NAVBAR);
654            }
655            if (configuration.allowTag(HtmlTag.NAV)) {
656                htmlTree.addContent(tree);
657            }
658        }
659    }
660
661    /**
662     * Get the word "NEXT" to indicate that no link is available.  Override
663     * this method to customize next link.
664     *
665     * @return a content tree for the link
666     */
667    protected Content getNavLinkNext() {
668        return getNavLinkNext(null);
669    }
670
671    /**
672     * Get the word "PREV" to indicate that no link is available.  Override
673     * this method to customize prev link.
674     *
675     * @return a content tree for the link
676     */
677    protected Content getNavLinkPrevious() {
678        return getNavLinkPrevious(null);
679    }
680
681    /**
682     * Do nothing. This is the default method.
683     */
684    protected void addSummaryDetailLinks(Content navDiv) {
685    }
686
687    /**
688     * Get link to the "overview-summary.html" page.
689     *
690     * @return a content tree for the link
691     */
692    protected Content getNavLinkContents() {
693        Content linkContent = getHyperLink(pathToRoot.resolve(DocPaths.overviewSummary(configuration.frames)),
694                contents.overviewLabel, "", "");
695        Content li = HtmlTree.LI(linkContent);
696        return li;
697    }
698
699    /**
700     * Get link to the module summary page for the module passed.
701     *
702     * @param mdle Module to which link will be generated
703     * @return a content tree for the link
704     */
705    protected Content getNavLinkModule(ModuleElement mdle) {
706        Content linkContent = getModuleLink(mdle, contents.moduleLabel);
707        Content li = HtmlTree.LI(linkContent);
708        return li;
709    }
710
711    /**
712     * Get the word "Module", to indicate that link is not available here.
713     *
714     * @return a content tree for the link
715     */
716    protected Content getNavLinkModule() {
717        Content li = HtmlTree.LI(contents.moduleLabel);
718        return li;
719    }
720
721    /**
722     * Get link to the "package-summary.html" page for the package passed.
723     *
724     * @param pkg Package to which link will be generated
725     * @return a content tree for the link
726     */
727    protected Content getNavLinkPackage(PackageElement pkg) {
728        Content linkContent = getPackageLink(pkg, contents.packageLabel);
729        Content li = HtmlTree.LI(linkContent);
730        return li;
731    }
732
733    /**
734     * Get the word "Package" , to indicate that link is not available here.
735     *
736     * @return a content tree for the link
737     */
738    protected Content getNavLinkPackage() {
739        Content li = HtmlTree.LI(contents.packageLabel);
740        return li;
741    }
742
743    /**
744     * Get the word "Use", to indicate that link is not available.
745     *
746     * @return a content tree for the link
747     */
748    protected Content getNavLinkClassUse() {
749        Content li = HtmlTree.LI(contents.useLabel);
750        return li;
751    }
752
753    /**
754     * Get link for previous file.
755     *
756     * @param prev File name for the prev link
757     * @return a content tree for the link
758     */
759    public Content getNavLinkPrevious(DocPath prev) {
760        Content li;
761        if (prev != null) {
762            li = HtmlTree.LI(getHyperLink(prev, contents.prevLabel, "", ""));
763        }
764        else
765            li = HtmlTree.LI(contents.prevLabel);
766        return li;
767    }
768
769    /**
770     * Get link for next file.  If next is null, just print the label
771     * without linking it anywhere.
772     *
773     * @param next File name for the next link
774     * @return a content tree for the link
775     */
776    public Content getNavLinkNext(DocPath next) {
777        Content li;
778        if (next != null) {
779            li = HtmlTree.LI(getHyperLink(next, contents.nextLabel, "", ""));
780        }
781        else
782            li = HtmlTree.LI(contents.nextLabel);
783        return li;
784    }
785
786    /**
787     * Get "FRAMES" link, to switch to the frame version of the output.
788     *
789     * @param link File to be linked, "index.html"
790     * @return a content tree for the link
791     */
792    protected Content getNavShowLists(DocPath link) {
793        DocLink dl = new DocLink(link, path.getPath(), null);
794        Content framesContent = getHyperLink(dl, contents.framesLabel, "", "_top");
795        Content li = HtmlTree.LI(framesContent);
796        return li;
797    }
798
799    /**
800     * Get "FRAMES" link, to switch to the frame version of the output.
801     *
802     * @return a content tree for the link
803     */
804    protected Content getNavShowLists() {
805        return getNavShowLists(pathToRoot.resolve(DocPaths.INDEX));
806    }
807
808    /**
809     * Get "NO FRAMES" link, to switch to the non-frame version of the output.
810     *
811     * @param link File to be linked
812     * @return a content tree for the link
813     */
814    protected Content getNavHideLists(DocPath link) {
815        Content noFramesContent = getHyperLink(link, contents.noFramesLabel, "", "_top");
816        Content li = HtmlTree.LI(noFramesContent);
817        return li;
818    }
819
820    /**
821     * Get "Tree" link in the navigation bar. If there is only one package
822     * specified on the command line, then the "Tree" link will be to the
823     * only "package-tree.html" file otherwise it will be to the
824     * "overview-tree.html" file.
825     *
826     * @return a content tree for the link
827     */
828    protected Content getNavLinkTree() {
829        List<PackageElement> packages = new ArrayList<>(configuration.getSpecifiedPackages());
830        DocPath docPath = packages.size() == 1 && configuration.getSpecifiedClasses().isEmpty()
831                ? pathString(packages.get(0), DocPaths.PACKAGE_TREE)
832                : pathToRoot.resolve(DocPaths.OVERVIEW_TREE);
833        return HtmlTree.LI(getHyperLink(docPath, contents.treeLabel, "", ""));
834    }
835
836    /**
837     * Get the overview tree link for the main tree.
838     *
839     * @param label the label for the link
840     * @return a content tree for the link
841     */
842    protected Content getNavLinkMainTree(String label) {
843        Content mainTreeContent = getHyperLink(pathToRoot.resolve(DocPaths.OVERVIEW_TREE),
844                new StringContent(label));
845        Content li = HtmlTree.LI(mainTreeContent);
846        return li;
847    }
848
849    /**
850     * Get the word "Class", to indicate that class link is not available.
851     *
852     * @return a content tree for the link
853     */
854    protected Content getNavLinkClass() {
855        Content li = HtmlTree.LI(contents.classLabel);
856        return li;
857    }
858
859    /**
860     * Get "Deprecated" API link in the navigation bar.
861     *
862     * @return a content tree for the link
863     */
864    protected Content getNavLinkDeprecated() {
865        Content linkContent = getHyperLink(pathToRoot.resolve(DocPaths.DEPRECATED_LIST),
866                contents.deprecatedLabel, "", "");
867        Content li = HtmlTree.LI(linkContent);
868        return li;
869    }
870
871    /**
872     * Get link for generated index. If the user has used "-splitindex"
873     * command line option, then link to file "index-files/index-1.html" is
874     * generated otherwise link to file "index-all.html" is generated.
875     *
876     * @return a content tree for the link
877     */
878    protected Content getNavLinkClassIndex() {
879        Content allClassesContent = getHyperLink(pathToRoot.resolve(
880                DocPaths.AllClasses(configuration.frames)),
881                contents.allClassesLabel, "", "");
882        Content li = HtmlTree.LI(allClassesContent);
883        return li;
884    }
885
886    /**
887     * Get link for generated class index.
888     *
889     * @return a content tree for the link
890     */
891    protected Content getNavLinkIndex() {
892        Content linkContent = getHyperLink(pathToRoot.resolve(
893                (configuration.splitindex
894                    ? DocPaths.INDEX_FILES.resolve(DocPaths.indexN(1))
895                    : DocPaths.INDEX_ALL)),
896            contents.indexLabel, "", "");
897        Content li = HtmlTree.LI(linkContent);
898        return li;
899    }
900
901    /**
902     * Get help file link. If user has provided a help file, then generate a
903     * link to the user given file, which is already copied to current or
904     * destination directory.
905     *
906     * @return a content tree for the link
907     */
908    protected Content getNavLinkHelp() {
909        String helpfile = configuration.helpfile;
910        DocPath helpfilenm;
911        if (helpfile.isEmpty()) {
912            helpfilenm = DocPaths.HELP_DOC;
913        } else {
914            DocFile file = DocFile.createFileForInput(configuration, helpfile);
915            helpfilenm = DocPath.create(file.getName());
916        }
917        Content linkContent = getHyperLink(pathToRoot.resolve(helpfilenm),
918                contents.helpLabel, "", "");
919        Content li = HtmlTree.LI(linkContent);
920        return li;
921    }
922
923    /**
924     * Add gap between navigation bar elements.
925     *
926     * @param liNav the content tree to which the gap will be added
927     */
928    protected void addNavGap(Content liNav) {
929        liNav.addContent(Contents.SPACE);
930        liNav.addContent("|");
931        liNav.addContent(Contents.SPACE);
932    }
933
934    /**
935     * Get summary table header.
936     *
937     * @param header the header for the table
938     * @param scope the scope of the headers
939     * @return a content tree for the header
940     */
941    public Content getSummaryTableHeader(List<String> header, String scope) {
942        Content tr = new HtmlTree(HtmlTag.TR);
943        final int size = header.size();
944        Content tableHeader;
945        if (size == 2) {
946            tableHeader = new StringContent(header.get(0));
947            tr.addContent(HtmlTree.TH(HtmlStyle.colFirst, scope, tableHeader));
948            tableHeader = new StringContent(header.get(1));
949            tr.addContent(HtmlTree.TH(HtmlStyle.colLast, scope, tableHeader));
950            return tr;
951        }
952        for (int i = 0; i < size; i++) {
953            tableHeader = new StringContent(header.get(i));
954            if (i == 0)
955                tr.addContent(HtmlTree.TH(HtmlStyle.colFirst, scope, tableHeader));
956            else if (i == 1)
957                tr.addContent(HtmlTree.TH(HtmlStyle.colSecond, scope, tableHeader));
958            else if (i == (size - 1))
959                tr.addContent(HtmlTree.TH(HtmlStyle.colLast, scope, tableHeader));
960            else
961                tr.addContent(HtmlTree.TH(scope, tableHeader));
962        }
963        return tr;
964    }
965
966    /**
967     * Get table caption.
968     *
969     * @param rawText the caption for the table which could be raw Html
970     * @return a content tree for the caption
971     */
972    public Content getTableCaption(Content title) {
973        Content captionSpan = HtmlTree.SPAN(title);
974        Content space = Contents.SPACE;
975        Content tabSpan = HtmlTree.SPAN(HtmlStyle.tabEnd, space);
976        Content caption = HtmlTree.CAPTION(captionSpan);
977        caption.addContent(tabSpan);
978        return caption;
979    }
980
981    /**
982     * Get the marker anchor which will be added to the documentation tree.
983     *
984     * @param anchorName the anchor name attribute
985     * @return a content tree for the marker anchor
986     */
987    public Content getMarkerAnchor(String anchorName) {
988        return getMarkerAnchor(getName(anchorName), null);
989    }
990
991    /**
992     * Get the marker anchor which will be added to the documentation tree.
993     *
994     * @param sectionName the section name anchor attribute for page
995     * @return a content tree for the marker anchor
996     */
997    public Content getMarkerAnchor(SectionName sectionName) {
998        return getMarkerAnchor(sectionName.getName(), null);
999    }
1000
1001    /**
1002     * Get the marker anchor which will be added to the documentation tree.
1003     *
1004     * @param sectionName the section name anchor attribute for page
1005     * @param anchorName the anchor name combined with section name attribute for the page
1006     * @return a content tree for the marker anchor
1007     */
1008    public Content getMarkerAnchor(SectionName sectionName, String anchorName) {
1009        return getMarkerAnchor(sectionName.getName() + getName(anchorName), null);
1010    }
1011
1012    /**
1013     * Get the marker anchor which will be added to the documentation tree.
1014     *
1015     * @param anchorName the anchor name or id attribute
1016     * @param anchorContent the content that should be added to the anchor
1017     * @return a content tree for the marker anchor
1018     */
1019    public Content getMarkerAnchor(String anchorName, Content anchorContent) {
1020        if (anchorContent == null)
1021            anchorContent = new Comment(" ");
1022        Content markerAnchor = HtmlTree.A(configuration.htmlVersion, anchorName, anchorContent);
1023        return markerAnchor;
1024    }
1025
1026    /**
1027     * Returns a packagename content.
1028     *
1029     * @param packageElement the package to check
1030     * @return package name content
1031     */
1032    public Content getPackageName(PackageElement packageElement) {
1033        return packageElement == null || packageElement.isUnnamed()
1034                ? contents.defaultPackageLabel
1035                : getPackageLabel(packageElement.getQualifiedName());
1036    }
1037
1038    /**
1039     * Returns a package name label.
1040     *
1041     * @param packageName the package name
1042     * @return the package name content
1043     */
1044    public Content getPackageLabel(CharSequence packageName) {
1045        return new StringContent(packageName);
1046    }
1047
1048    /**
1049     * Add package deprecation information to the documentation tree
1050     *
1051     * @param deprPkgs list of deprecated packages
1052     * @param headingKey the caption for the deprecated package table
1053     * @param tableSummary the summary for the deprecated package table
1054     * @param tableHeader table headers for the deprecated package table
1055     * @param contentTree the content tree to which the deprecated package table will be added
1056     */
1057    protected void addPackageDeprecatedAPI(SortedSet<Element> deprPkgs, String headingKey,
1058            String tableSummary, List<String> tableHeader, Content contentTree) {
1059        if (deprPkgs.size() > 0) {
1060            Content caption = getTableCaption(configuration.getContent(headingKey));
1061            Content table = (configuration.isOutputHtml5())
1062                    ? HtmlTree.TABLE(HtmlStyle.deprecatedSummary, caption)
1063                    : HtmlTree.TABLE(HtmlStyle.deprecatedSummary, tableSummary, caption);
1064            table.addContent(getSummaryTableHeader(tableHeader, "col"));
1065            Content tbody = new HtmlTree(HtmlTag.TBODY);
1066            boolean altColor = true;
1067            for (Element e : deprPkgs) {
1068                PackageElement pkg = (PackageElement) e;
1069                HtmlTree thRow = HtmlTree.TH_ROW_SCOPE(HtmlStyle.colFirst,
1070                        getPackageLink(pkg, getPackageName(pkg)));
1071                HtmlTree tr = HtmlTree.TR(thRow);
1072                HtmlTree tdDesc = new HtmlTree(HtmlTag.TD);
1073                tdDesc.addStyle(HtmlStyle.colLast);
1074                List<? extends DocTree> tags = utils.getDeprecatedTrees(pkg);
1075                if (!tags.isEmpty()) {
1076                    addInlineDeprecatedComment(pkg, tags.get(0), tdDesc);
1077                }
1078                tr.addContent(tdDesc);
1079                tr.addStyle(altColor ? HtmlStyle.altColor : HtmlStyle.rowColor);
1080                altColor = !altColor;
1081                tbody.addContent(tr);
1082            }
1083            table.addContent(tbody);
1084            Content li = HtmlTree.LI(HtmlStyle.blockList, table);
1085            Content ul = HtmlTree.UL(HtmlStyle.blockList, li);
1086            contentTree.addContent(ul);
1087        }
1088    }
1089
1090    /**
1091     * Return the path to the class page for a typeElement.
1092     *
1093     * @param te   TypeElement for which the path is requested.
1094     * @param name Name of the file(doesn't include path).
1095     */
1096    protected DocPath pathString(TypeElement te, DocPath name) {
1097        return pathString(utils.containingPackage(te), name);
1098    }
1099
1100    /**
1101     * Return path to the given file name in the given package. So if the name
1102     * passed is "Object.html" and the name of the package is "java.lang", and
1103     * if the relative path is "../.." then returned string will be
1104     * "../../java/lang/Object.html"
1105     *
1106     * @param packageElement Package in which the file name is assumed to be.
1107     * @param name File name, to which path string is.
1108     */
1109    protected DocPath pathString(PackageElement packageElement, DocPath name) {
1110        return pathToRoot.resolve(DocPath.forPackage(packageElement).resolve(name));
1111    }
1112
1113    /**
1114     * Given a package, return the name to be used in HTML anchor tag.
1115     * @param packageElement the package.
1116     * @return the name to be used in HTML anchor tag.
1117     */
1118    public String getPackageAnchorName(PackageElement packageElement) {
1119        return packageElement == null || packageElement.isUnnamed()
1120                ? SectionName.UNNAMED_PACKAGE_ANCHOR.getName()
1121                : utils.getPackageName(packageElement);
1122    }
1123
1124    /**
1125     * Return the link to the given package.
1126     *
1127     * @param packageElement the package to link to.
1128     * @param label the label for the link.
1129     * @return a content tree for the package link.
1130     */
1131    public Content getPackageLink(PackageElement packageElement, CharSequence label) {
1132        return getPackageLink(packageElement, new StringContent(label));
1133    }
1134
1135    public Content getPackageLink(PackageElement packageElement) {
1136        StringContent content =  packageElement.isUnnamed()
1137                ? new StringContent()
1138                : new StringContent(utils.getPackageName(packageElement));
1139        return getPackageLink(packageElement, content);
1140    }
1141
1142    /**
1143     * Return the link to the given package.
1144     *
1145     * @param packageElement the package to link to.
1146     * @param label the label for the link.
1147     * @return a content tree for the package link.
1148     */
1149    public Content getPackageLink(PackageElement packageElement, Content label) {
1150        boolean included = packageElement != null && utils.isIncluded(packageElement);
1151        if (!included) {
1152            for (PackageElement p : configuration.packages) {
1153                if (p.equals(packageElement)) {
1154                    included = true;
1155                    break;
1156                }
1157            }
1158        }
1159        if (included || packageElement == null) {
1160            return getHyperLink(pathString(packageElement, DocPaths.PACKAGE_SUMMARY),
1161                    label);
1162        } else {
1163            DocLink crossPkgLink = getCrossPackageLink(utils.getPackageName(packageElement));
1164            if (crossPkgLink != null) {
1165                return getHyperLink(crossPkgLink, label);
1166            } else {
1167                return label;
1168            }
1169        }
1170    }
1171
1172    /**
1173     * Get Module link.
1174     *
1175     * @param mdle the module being documented
1176     * @param label tag for the link
1177     * @return a content for the module link
1178     */
1179    public Content getModuleLink(ModuleElement mdle, Content label) {
1180        return getHyperLink(pathToRoot.resolve(
1181                DocPaths.moduleSummary(mdle)), label, "", "");
1182    }
1183
1184    public Content interfaceName(TypeElement typeElement, boolean qual) {
1185        Content name = new StringContent((qual)
1186                ? typeElement.getQualifiedName()
1187                : utils.getSimpleName(typeElement));
1188        return (utils.isInterface(typeElement)) ?  HtmlTree.SPAN(HtmlStyle.interfaceName, name) : name;
1189    }
1190
1191    /**
1192     * Add the link to the content tree.
1193     *
1194     * @param typeElement program element typeElement for which the link will be added
1195     * @param label label for the link
1196     * @param htmltree the content tree to which the link will be added
1197     */
1198    public void addSrcLink(Element typeElement, Content label, Content htmltree) {
1199        if (typeElement == null) {
1200            return;
1201        }
1202        TypeElement te = utils.getEnclosingTypeElement(typeElement);
1203        if (te == null) {
1204            // must be a typeElement since in has no containing class.
1205            te = (TypeElement) typeElement;
1206        }
1207        DocPath href = pathToRoot
1208                .resolve(DocPaths.SOURCE_OUTPUT)
1209                .resolve(DocPath.forClass(utils, te));
1210        Content linkContent = getHyperLink(href
1211                .fragment(SourceToHTMLConverter.getAnchorName(utils, typeElement)), label, "", "");
1212        htmltree.addContent(linkContent);
1213    }
1214
1215    /**
1216     * Return the link to the given class.
1217     *
1218     * @param linkInfo the information about the link.
1219     *
1220     * @return the link for the given class.
1221     */
1222    public Content getLink(LinkInfoImpl linkInfo) {
1223        LinkFactoryImpl factory = new LinkFactoryImpl(this);
1224        return factory.getLink(linkInfo);
1225    }
1226
1227    /**
1228     * Return the type parameters for the given class.
1229     *
1230     * @param linkInfo the information about the link.
1231     * @return the type for the given class.
1232     */
1233    public Content getTypeParameterLinks(LinkInfoImpl linkInfo) {
1234        LinkFactoryImpl factory = new LinkFactoryImpl(this);
1235        return factory.getTypeParameterLinks(linkInfo, false);
1236    }
1237
1238    /*************************************************************
1239     * Return a class cross link to external class documentation.
1240     * The name must be fully qualified to determine which package
1241     * the class is in.  The -link option does not allow users to
1242     * link to external classes in the "default" package.
1243     *
1244     * @param qualifiedClassName the qualified name of the external class.
1245     * @param refMemName the name of the member being referenced.  This should
1246     * be null or empty string if no member is being referenced.
1247     * @param label the label for the external link.
1248     * @param strong true if the link should be strong.
1249     * @param style the style of the link.
1250     * @param code true if the label should be code font.
1251     */
1252    public Content getCrossClassLink(String qualifiedClassName, String refMemName,
1253                                    Content label, boolean strong, String style,
1254                                    boolean code) {
1255        String className = "";
1256        String packageName = qualifiedClassName == null ? "" : qualifiedClassName;
1257        int periodIndex;
1258        while ((periodIndex = packageName.lastIndexOf('.')) != -1) {
1259            className = packageName.substring(periodIndex + 1, packageName.length()) +
1260                (className.length() > 0 ? "." + className : "");
1261            Content defaultLabel = new StringContent(className);
1262            if (code)
1263                defaultLabel = HtmlTree.CODE(defaultLabel);
1264            packageName = packageName.substring(0, periodIndex);
1265            if (getCrossPackageLink(packageName) != null) {
1266                /*
1267                The package exists in external documentation, so link to the external
1268                class (assuming that it exists).  This is definitely a limitation of
1269                the -link option.  There are ways to determine if an external package
1270                exists, but no way to determine if the external class exists.  We just
1271                have to assume that it does.
1272                */
1273                DocLink link = configuration.extern.getExternalLink(packageName, pathToRoot,
1274                                className + ".html", refMemName);
1275                return getHyperLink(link,
1276                    (label == null) || label.isEmpty() ? defaultLabel : label,
1277                    strong, style,
1278                    configuration.getText("doclet.Href_Class_Or_Interface_Title", packageName),
1279                    "");
1280            }
1281        }
1282        return null;
1283    }
1284
1285    public boolean isClassLinkable(TypeElement typeElement) {
1286        if (utils.isIncluded(typeElement)) {
1287            return configuration.isGeneratedDoc(typeElement);
1288        }
1289        return configuration.extern.isExternal(typeElement);
1290    }
1291
1292    public DocLink getCrossPackageLink(String pkgName) {
1293        return configuration.extern.getExternalLink(pkgName, pathToRoot,
1294            DocPaths.PACKAGE_SUMMARY.getPath());
1295    }
1296
1297    /**
1298     * Get the class link.
1299     *
1300     * @param context the id of the context where the link will be added
1301     * @param element to link to
1302     * @return a content tree for the link
1303     */
1304    public Content getQualifiedClassLink(LinkInfoImpl.Kind context, Element element) {
1305        LinkInfoImpl linkInfoImpl = new LinkInfoImpl(configuration, context, (TypeElement)element);
1306        return getLink(linkInfoImpl.label(utils.getFullyQualifiedName(element)));
1307    }
1308
1309    /**
1310     * Add the class link.
1311     *
1312     * @param context the id of the context where the link will be added
1313     * @param typeElement to link to
1314     * @param contentTree the content tree to which the link will be added
1315     */
1316    public void addPreQualifiedClassLink(LinkInfoImpl.Kind context, TypeElement typeElement, Content contentTree) {
1317        addPreQualifiedClassLink(context, typeElement, false, contentTree);
1318    }
1319
1320    /**
1321     * Retrieve the class link with the package portion of the label in
1322     * plain text.  If the qualifier is excluded, it will not be included in the
1323     * link label.
1324     *
1325     * @param typeElement the class to link to.
1326     * @param isStrong true if the link should be strong.
1327     * @return the link with the package portion of the label in plain text.
1328     */
1329    public Content getPreQualifiedClassLink(LinkInfoImpl.Kind context,
1330            TypeElement typeElement, boolean isStrong) {
1331        ContentBuilder classlink = new ContentBuilder();
1332        PackageElement pkg = utils.containingPackage(typeElement);
1333        if (pkg != null && ! configuration.shouldExcludeQualifier(pkg.getSimpleName().toString())) {
1334            classlink.addContent(getEnclosingPackageName(typeElement));
1335        }
1336        classlink.addContent(getLink(new LinkInfoImpl(configuration,
1337                context, typeElement).label(utils.getSimpleName(typeElement)).strong(isStrong)));
1338        return classlink;
1339    }
1340
1341    /**
1342     * Add the class link with the package portion of the label in
1343     * plain text. If the qualifier is excluded, it will not be included in the
1344     * link label.
1345     *
1346     * @param context the id of the context where the link will be added
1347     * @param typeElement the class to link to
1348     * @param isStrong true if the link should be strong
1349     * @param contentTree the content tree to which the link with be added
1350     */
1351    public void addPreQualifiedClassLink(LinkInfoImpl.Kind context,
1352            TypeElement typeElement, boolean isStrong, Content contentTree) {
1353        PackageElement pkg = utils.containingPackage(typeElement);
1354        if(pkg != null && ! configuration.shouldExcludeQualifier(pkg.getSimpleName().toString())) {
1355            contentTree.addContent(getEnclosingPackageName(typeElement));
1356        }
1357        LinkInfoImpl linkinfo = new LinkInfoImpl(configuration, context, typeElement)
1358                .label(utils.getSimpleName(typeElement))
1359                .strong(isStrong);
1360        Content link = getLink(linkinfo);
1361        contentTree.addContent(link);
1362    }
1363
1364    /**
1365     * Add the class link, with only class name as the strong link and prefixing
1366     * plain package name.
1367     *
1368     * @param context the id of the context where the link will be added
1369     * @param typeElement the class to link to
1370     * @param contentTree the content tree to which the link with be added
1371     */
1372    public void addPreQualifiedStrongClassLink(LinkInfoImpl.Kind context, TypeElement typeElement, Content contentTree) {
1373        addPreQualifiedClassLink(context, typeElement, true, contentTree);
1374    }
1375
1376    /**
1377     * Get the link for the given member.
1378     *
1379     * @param context the id of the context where the link will be added
1380     * @param element the member being linked to
1381     * @param label the label for the link
1382     * @return a content tree for the element link
1383     */
1384    public Content getDocLink(LinkInfoImpl.Kind context, Element element, CharSequence label) {
1385        return getDocLink(context, utils.getEnclosingTypeElement(element), element,
1386                new StringContent(label));
1387    }
1388
1389    /**
1390     * Return the link for the given member.
1391     *
1392     * @param context the id of the context where the link will be printed.
1393     * @param element the member being linked to.
1394     * @param label the label for the link.
1395     * @param strong true if the link should be strong.
1396     * @return the link for the given member.
1397     */
1398    public Content getDocLink(LinkInfoImpl.Kind context, Element element, CharSequence label,
1399            boolean strong) {
1400        return getDocLink(context, utils.getEnclosingTypeElement(element), element, label, strong);
1401    }
1402
1403    /**
1404     * Return the link for the given member.
1405     *
1406     * @param context the id of the context where the link will be printed.
1407     * @param typeElement the typeElement that we should link to.  This is not
1408                 necessarily equal to element.containingClass().  We may be
1409                 inheriting comments.
1410     * @param element the member being linked to.
1411     * @param label the label for the link.
1412     * @param strong true if the link should be strong.
1413     * @return the link for the given member.
1414     */
1415    public Content getDocLink(LinkInfoImpl.Kind context, TypeElement typeElement, Element element,
1416            CharSequence label, boolean strong) {
1417        return getDocLink(context, typeElement, element, label, strong, false);
1418    }
1419
1420    public Content getDocLink(LinkInfoImpl.Kind context, TypeElement typeElement, Element element,
1421            Content label, boolean strong) {
1422        return getDocLink(context, typeElement, element, label, strong, false);
1423    }
1424
1425   /**
1426     * Return the link for the given member.
1427     *
1428     * @param context the id of the context where the link will be printed.
1429     * @param typeElement the typeElement that we should link to.  This is not
1430                 necessarily equal to element.containingClass().  We may be
1431                 inheriting comments.
1432     * @param element the member being linked to.
1433     * @param label the label for the link.
1434     * @param strong true if the link should be strong.
1435     * @param isProperty true if the element parameter is a JavaFX property.
1436     * @return the link for the given member.
1437     */
1438    public Content getDocLink(LinkInfoImpl.Kind context, TypeElement typeElement, Element element,
1439            CharSequence label, boolean strong, boolean isProperty) {
1440        return getDocLink(context, typeElement, element, new StringContent(check(label)), strong, isProperty);
1441    }
1442
1443    CharSequence check(CharSequence s) {
1444        Matcher m = IMPROPER_HTML_CHARS.matcher(s);
1445        if (m.matches()) {
1446            throw new IllegalArgumentException(s.toString());
1447        }
1448        return s;
1449    }
1450
1451    public Content getDocLink(LinkInfoImpl.Kind context, TypeElement typeElement, Element element,
1452            Content label, boolean strong, boolean isProperty) {
1453        if (! (utils.isIncluded(element) || utils.isLinkable(typeElement))) {
1454            return label;
1455        } else if (utils.isExecutableElement(element)) {
1456            ExecutableElement ee = (ExecutableElement)element;
1457            return getLink(new LinkInfoImpl(configuration, context, typeElement)
1458                .label(label)
1459                .where(getName(getAnchor(ee, isProperty)))
1460                .strong(strong));
1461        } else if (utils.isVariableElement(element) || utils.isTypeElement(element)) {
1462            return getLink(new LinkInfoImpl(configuration, context, typeElement)
1463                .label(label)
1464                .where(getName(element.getSimpleName().toString()))
1465                .strong(strong));
1466        } else {
1467            return label;
1468        }
1469    }
1470
1471    /**
1472     * Return the link for the given member.
1473     *
1474     * @param context the id of the context where the link will be added
1475     * @param typeElement the typeElement that we should link to.  This is not
1476                 necessarily equal to element.containingClass().  We may be
1477                 inheriting comments
1478     * @param element the member being linked to
1479     * @param label the label for the link
1480     * @return the link for the given member
1481     */
1482    public Content getDocLink(LinkInfoImpl.Kind context, TypeElement typeElement, Element element,
1483            Content label) {
1484        if (! (utils.isIncluded(element) || utils.isLinkable(typeElement))) {
1485            return label;
1486        } else if (utils.isExecutableElement(element)) {
1487            ExecutableElement emd = (ExecutableElement) element;
1488            return getLink(new LinkInfoImpl(configuration, context, typeElement)
1489                .label(label)
1490                .where(getName(getAnchor(emd))));
1491        } else if (utils.isVariableElement(element) || utils.isTypeElement(element)) {
1492            return getLink(new LinkInfoImpl(configuration, context, typeElement)
1493                .label(label).where(getName(element.getSimpleName().toString())));
1494        } else {
1495            return label;
1496        }
1497    }
1498
1499    public String getAnchor(ExecutableElement executableElement) {
1500        return getAnchor(executableElement, false);
1501    }
1502
1503    public String getAnchor(ExecutableElement executableElement, boolean isProperty) {
1504        if (isProperty) {
1505            return executableElement.getSimpleName().toString();
1506        }
1507        String signature = utils.signature(executableElement);
1508        StringBuilder signatureParsed = new StringBuilder();
1509        int counter = 0;
1510        for (int i = 0; i < signature.length(); i++) {
1511            char c = signature.charAt(i);
1512            if (c == '<') {
1513                counter++;
1514            } else if (c == '>') {
1515                counter--;
1516            } else if (counter == 0) {
1517                signatureParsed.append(c);
1518            }
1519        }
1520        return utils.getSimpleName(executableElement) + signatureParsed.toString();
1521    }
1522
1523    public Content seeTagToContent(Element element, DocTree see) {
1524
1525        Kind kind = see.getKind();
1526        if (!(kind == LINK || kind == SEE || kind == LINK_PLAIN)) {
1527            return new ContentBuilder();
1528        }
1529
1530        CommentHelper ch = utils.getCommentHelper(element);
1531        String tagName = ch.getTagName(see);
1532        String seetext = replaceDocRootDir(utils.normalizeNewlines(ch.getText(see)).toString());
1533        // Check if @see is an href or "string"
1534        if (seetext.startsWith("<") || seetext.startsWith("\"")) {
1535            return new RawHtml(seetext);
1536        }
1537        boolean isLinkPlain = kind == LINK_PLAIN;
1538        Content label = plainOrCode(isLinkPlain, new RawHtml(ch.getLabel(configuration, see)));
1539
1540        //The text from the @see tag.  We will output this text when a label is not specified.
1541        Content text = plainOrCode(kind == LINK_PLAIN, new RawHtml(seetext));
1542
1543        TypeElement refClass = ch.getReferencedClass(configuration, see);
1544        String refClassName =  ch.getReferencedClassName(configuration, see);
1545        Element refMem =       ch.getReferencedMember(configuration, see);
1546        String refMemName =    ch.getReferencedMemberName(see);
1547
1548        if (refMemName == null && refMem != null) {
1549            refMemName = refMem.toString();
1550        }
1551        if (refClass == null) {
1552            //@see is not referencing an included class
1553            PackageElement refPackage = ch.getReferencedPackage(configuration, see);
1554            if (refPackage != null && utils.isIncluded(refPackage)) {
1555                //@see is referencing an included package
1556                if (label.isEmpty())
1557                    label = plainOrCode(isLinkPlain,
1558                            new StringContent(refPackage.getQualifiedName()));
1559                return getPackageLink(refPackage, label);
1560            } else {
1561                // @see is not referencing an included class or package.  Check for cross links.
1562                Content classCrossLink;
1563                DocLink packageCrossLink = getCrossPackageLink(refClassName);
1564                if (packageCrossLink != null) {
1565                    // Package cross link found
1566                    return getHyperLink(packageCrossLink,
1567                        (label.isEmpty() ? text : label));
1568                } else if ((classCrossLink = getCrossClassLink(refClassName,
1569                        refMemName, label, false, "", !isLinkPlain)) != null) {
1570                    // Class cross link found (possibly to a member in the class)
1571                    return classCrossLink;
1572                } else {
1573                    // No cross link found so print warning
1574                    messages.warning(ch.getDocTreePath(see),
1575                            "doclet.see.class_or_package_not_found",
1576                            "@" + tagName,
1577                            seetext);
1578                    return (label.isEmpty() ? text: label);
1579                }
1580            }
1581        } else if (refMemName == null) {
1582            // Must be a class reference since refClass is not null and refMemName is null.
1583            if (label.isEmpty()) {
1584                /*
1585                 * it seems to me this is the right thing to do, but it causes comparator failures.
1586                 */
1587                if (!configuration.backwardCompatibility) {
1588                    StringContent content = utils.isEnclosingPackageIncluded(refClass)
1589                            ? new StringContent(utils.getSimpleName(refClass))
1590                            : new StringContent(utils.getFullyQualifiedName(refClass));
1591                    label = plainOrCode(isLinkPlain, content);
1592                } else {
1593                    label = plainOrCode(isLinkPlain,
1594                            new StringContent(utils.getSimpleName(refClass)));
1595                }
1596
1597            }
1598            return getLink(new LinkInfoImpl(configuration, LinkInfoImpl.Kind.DEFAULT, refClass)
1599                    .label(label));
1600        } else if (refMem == null) {
1601            // Must be a member reference since refClass is not null and refMemName is not null.
1602            // However, refMem is null, so this referenced member does not exist.
1603            return (label.isEmpty() ? text: label);
1604        } else {
1605            // Must be a member reference since refClass is not null and refMemName is not null.
1606            // refMem is not null, so this @see tag must be referencing a valid member.
1607            TypeElement containing = utils.getEnclosingTypeElement(refMem);
1608            if (ch.getText(see).trim().startsWith("#") &&
1609                ! (utils.isPublic(containing) || utils.isLinkable(containing))) {
1610                // Since the link is relative and the holder is not even being
1611                // documented, this must be an inherited link.  Redirect it.
1612                // The current class either overrides the referenced member or
1613                // inherits it automatically.
1614                if (this instanceof ClassWriterImpl) {
1615                    containing = ((ClassWriterImpl) this).getTypeElement();
1616                } else if (!utils.isPublic(containing)) {
1617                    messages.warning(
1618                        ch.getDocTreePath(see), "doclet.see.class_or_package_not_accessible",
1619                        tagName, utils.getFullyQualifiedName(containing));
1620                } else {
1621                    messages.warning(
1622                        ch.getDocTreePath(see), "doclet.see.class_or_package_not_found",
1623                        tagName, seetext);
1624                }
1625            }
1626            if (configuration.currentTypeElement != containing) {
1627                refMemName = (utils.isConstructor(refMem))
1628                        ? refMemName
1629                        : utils.getSimpleName(containing) + "." + refMemName;
1630            }
1631            if (utils.isExecutableElement(refMem)) {
1632                if (refMemName.indexOf('(') < 0) {
1633                    refMemName += utils.makeSignature((ExecutableElement)refMem, true);
1634                }
1635            }
1636
1637            text = plainOrCode(kind == LINK_PLAIN, new StringContent(refMemName));
1638
1639            return getDocLink(LinkInfoImpl.Kind.SEE_TAG, containing,
1640                    refMem, (label.isEmpty() ? text: label), false);
1641        }
1642    }
1643
1644    private Content plainOrCode(boolean plain, Content body) {
1645        return (plain || body.isEmpty()) ? body : HtmlTree.CODE(body);
1646    }
1647
1648    /**
1649     * Add the inline comment.
1650     *
1651     * @param element the Element for which the inline comment will be added
1652     * @param tag the inline tag to be added
1653     * @param htmltree the content tree to which the comment will be added
1654     */
1655    public void addInlineComment(Element element, DocTree tag, Content htmltree) {
1656        CommentHelper ch = utils.getCommentHelper(element);
1657        List<? extends DocTree> description = ch.getDescription(configuration, tag);
1658        addCommentTags(element, tag, description, false, false, htmltree);
1659    }
1660
1661    /**
1662     * Add the inline deprecated comment.
1663     *
1664     * @param e the Element for which the inline deprecated comment will be added
1665     * @param tag the inline tag to be added
1666     * @param htmltree the content tree to which the comment will be added
1667     */
1668    public void addInlineDeprecatedComment(Element e, DocTree tag, Content htmltree) {
1669        CommentHelper ch = utils.getCommentHelper(e);
1670        addCommentTags(e, ch.getBody(configuration, tag), true, false, htmltree);
1671    }
1672
1673    /**
1674     * Adds the summary content.
1675     *
1676     * @param element the Element for which the summary will be generated
1677     * @param htmltree the documentation tree to which the summary will be added
1678     */
1679    public void addSummaryComment(Element element, Content htmltree) {
1680        addSummaryComment(element, utils.getFirstSentenceTrees(element), htmltree);
1681    }
1682
1683    /**
1684     * Adds the summary content.
1685     *
1686     * @param element the Element for which the summary will be generated
1687     * @param firstSentenceTags the first sentence tags for the doc
1688     * @param htmltree the documentation tree to which the summary will be added
1689     */
1690    public void addSummaryComment(Element element, List<? extends DocTree> firstSentenceTags, Content htmltree) {
1691        addCommentTags(element, firstSentenceTags, false, true, htmltree);
1692    }
1693
1694    public void addSummaryDeprecatedComment(Element element, DocTree tag, Content htmltree) {
1695        CommentHelper ch = utils.getCommentHelper(element);
1696        List<? extends DocTree> body = ch.getBody(configuration, tag);
1697        addCommentTags(element, ch.getFirstSentenceTrees(configuration, body), true, true, htmltree);
1698    }
1699
1700    /**
1701     * Adds the inline comment.
1702     *
1703     * @param element the Element for which the inline comments will be generated
1704     * @param htmltree the documentation tree to which the inline comments will be added
1705     */
1706    public void addInlineComment(Element element, Content htmltree) {
1707        addCommentTags(element, utils.getFullBody(element), false, false, htmltree);
1708    }
1709
1710    /**
1711     * Adds the comment tags.
1712     *
1713     * @param element the Element for which the comment tags will be generated
1714     * @param tags the first sentence tags for the doc
1715     * @param depr true if it is deprecated
1716     * @param first true if the first sentence tags should be added
1717     * @param htmltree the documentation tree to which the comment tags will be added
1718     */
1719    private void addCommentTags(Element element, List<? extends DocTree> tags, boolean depr,
1720            boolean first, Content htmltree) {
1721        addCommentTags(element, null, tags, depr, first, htmltree);
1722    }
1723
1724    /**
1725     * Adds the comment tags.
1726     *
1727     * @param element for which the comment tags will be generated
1728     * @param holderTag the block tag context for the inline tags
1729     * @param tags the first sentence tags for the doc
1730     * @param depr true if it is deprecated
1731     * @param first true if the first sentence tags should be added
1732     * @param htmltree the documentation tree to which the comment tags will be added
1733     */
1734    private void addCommentTags(Element element, DocTree holderTag, List<? extends DocTree> tags, boolean depr,
1735            boolean first, Content htmltree) {
1736        if(configuration.nocomment){
1737            return;
1738        }
1739        Content div;
1740        Content result = commentTagsToContent(null, element, tags, first);
1741        if (depr) {
1742            Content italic = HtmlTree.SPAN(HtmlStyle.deprecationComment, result);
1743            div = HtmlTree.DIV(HtmlStyle.block, italic);
1744            htmltree.addContent(div);
1745        }
1746        else {
1747            div = HtmlTree.DIV(HtmlStyle.block, result);
1748            htmltree.addContent(div);
1749        }
1750        if (tags.isEmpty()) {
1751            htmltree.addContent(Contents.SPACE);
1752        }
1753    }
1754
1755    boolean ignoreNonInlineTag(DocTree dtree) {
1756        Name name = null;
1757        if (dtree.getKind() == Kind.START_ELEMENT) {
1758            StartElementTree setree = (StartElementTree)dtree;
1759            name = setree.getName();
1760        } else if (dtree.getKind() == Kind.END_ELEMENT) {
1761            EndElementTree eetree = (EndElementTree)dtree;
1762            name = eetree.getName();
1763        }
1764
1765        if (name != null) {
1766            com.sun.tools.doclint.HtmlTag htmlTag = com.sun.tools.doclint.HtmlTag.get(name);
1767            if (htmlTag != null &&
1768                    htmlTag.blockType != com.sun.tools.doclint.HtmlTag.BlockType.INLINE) {
1769                return true;
1770            }
1771        }
1772        return false;
1773    }
1774
1775    boolean isAllWhiteSpace(String body) {
1776        for (int i = 0 ; i < body.length(); i++) {
1777            if (!Character.isWhitespace(body.charAt(i)))
1778                return false;
1779        }
1780        return true;
1781    }
1782
1783    /**
1784     * Converts inline tags and text to text strings, expanding the
1785     * inline tags along the way.  Called wherever text can contain
1786     * an inline tag, such as in comments or in free-form text arguments
1787     * to non-inline tags.
1788     *
1789     * @param holderTag    specific tag where comment resides
1790     * @param element    specific element where comment resides
1791     * @param tags   array of text tags and inline tags (often alternating)
1792               present in the text of interest for this element
1793     * @param isFirstSentence  true if text is first sentence
1794     * @return a Content object
1795     */
1796    public Content commentTagsToContent(DocTree holderTag, Element element,
1797            List<? extends DocTree> tags, boolean isFirstSentence) {
1798
1799        final Content result = new ContentBuilder() {
1800            @Override
1801            public void addContent(CharSequence text) {
1802                super.addContent(utils.normalizeNewlines(text));
1803            }
1804        };
1805        CommentHelper ch = utils.getCommentHelper(element);
1806        // Array of all possible inline tags for this javadoc run
1807        configuration.tagletManager.checkTags(utils, element, tags, true);
1808        for (ListIterator<? extends DocTree> iterator = tags.listIterator(); iterator.hasNext();) {
1809            DocTree tag = iterator.next();
1810             // zap block tags
1811            if (isFirstSentence && ignoreNonInlineTag(tag))
1812                continue;
1813
1814            if (isFirstSentence && iterator.nextIndex() == tags.size() &&
1815                    (tag.getKind() == TEXT && isAllWhiteSpace(ch.getText(tag))))
1816                continue;
1817
1818            boolean allDone = new SimpleDocTreeVisitor<Boolean, Content>() {
1819                // notify the next DocTree handler to take necessary action
1820                boolean commentRemoved = false;
1821
1822                private boolean isLast(DocTree node) {
1823                    return node.equals(tags.get(tags.size() - 1));
1824                }
1825
1826                private boolean isFirst(DocTree node) {
1827                    return node.equals(tags.get(0));
1828                }
1829
1830                private boolean inAnAtag() {
1831                    if (utils.isStartElement(tag)) {
1832                        StartElementTree st = (StartElementTree)tag;
1833                        Name name = st.getName();
1834                        if (name != null) {
1835                            com.sun.tools.doclint.HtmlTag htag =
1836                                    com.sun.tools.doclint.HtmlTag.get(name);
1837                            return htag != null && htag.equals(com.sun.tools.doclint.HtmlTag.A);
1838                        }
1839                    }
1840                    return false;
1841                }
1842
1843                @Override
1844                public Boolean visitAttribute(AttributeTree node, Content c) {
1845                    StringBuilder sb = new StringBuilder(SPACER).append(node.getName());
1846                    if (node.getValueKind() == ValueKind.EMPTY) {
1847                        result.addContent(sb);
1848                        return false;
1849                    }
1850                    sb.append("=");
1851                    String quote;
1852                    switch (node.getValueKind()) {
1853                        case DOUBLE:
1854                            quote = "\"";
1855                            break;
1856                        case SINGLE:
1857                            quote = "\'";
1858                            break;
1859                        default:
1860                            quote = "";
1861                            break;
1862                    }
1863                    sb.append(quote);
1864                    result.addContent(sb);
1865                    Content docRootContent = new ContentBuilder();
1866
1867                    for (DocTree dt : node.getValue()) {
1868                        if (utils.isText(dt) && inAnAtag()) {
1869                            String text = ((TextTree) dt).getBody();
1870                            if (text.startsWith("/..") && !configuration.docrootparent.isEmpty()) {
1871                                result.addContent(configuration.docrootparent);
1872                                docRootContent = new ContentBuilder();
1873                                result.addContent(textCleanup(text.substring(3), isLast(node)));
1874                            } else {
1875                                if (!docRootContent.isEmpty()) {
1876                                    docRootContent = copyDocRootContent(docRootContent);
1877                                } else {
1878                                    text = redirectRelativeLinks(element, (TextTree) dt);
1879                                }
1880                                result.addContent(textCleanup(text, isLast(node)));
1881                            }
1882                        } else {
1883                            docRootContent = copyDocRootContent(docRootContent);
1884                            dt.accept(this, docRootContent);
1885                        }
1886                    }
1887                    copyDocRootContent(docRootContent);
1888                    result.addContent(quote);
1889                    return false;
1890                }
1891
1892                @Override
1893                public Boolean visitComment(CommentTree node, Content c) {
1894                    if (isFirstSentence && isFirst(node)) {
1895                        commentRemoved = true;
1896                        return this.visit(iterator.next(), c);
1897                    }
1898                    result.addContent(new RawHtml(node.getBody()));
1899                    return false;
1900                }
1901
1902                private Content copyDocRootContent(Content content) {
1903                    if (!content.isEmpty()) {
1904                        result.addContent(content);
1905                        return new ContentBuilder();
1906                    }
1907                    return content;
1908                }
1909
1910                @Override
1911                public Boolean visitDocRoot(DocRootTree node, Content c) {
1912                    Content docRootContent = TagletWriter.getInlineTagOutput(element,
1913                            configuration.tagletManager,
1914                            holderTag,
1915                            node,
1916                            getTagletWriterInstance(isFirstSentence));
1917                    if (c != null) {
1918                        c.addContent(docRootContent);
1919                    } else {
1920                        result.addContent(docRootContent);
1921                    }
1922                    return false;
1923                }
1924
1925                @Override
1926                public Boolean visitEndElement(EndElementTree node, Content c) {
1927                    RawHtml rawHtml = new RawHtml("</" + node.getName() + ">");
1928                    result.addContent(rawHtml);
1929                    return false;
1930                }
1931
1932                @Override
1933                public Boolean visitEntity(EntityTree node, Content c) {
1934                    result.addContent(new RawHtml(node.toString()));
1935                    return false;
1936                }
1937
1938                @Override
1939                public Boolean visitErroneous(ErroneousTree node, Content c) {
1940                    messages.warning(ch.getDocTreePath(node),
1941                            "doclet.tag.invalid_usage", node);
1942                    result.addContent(new RawHtml(node.toString()));
1943                    return false;
1944                }
1945
1946                @Override
1947                public Boolean visitInheritDoc(InheritDocTree node, Content c) {
1948                    Content output = TagletWriter.getInlineTagOutput(element,
1949                            configuration.tagletManager, holderTag,
1950                            tag, getTagletWriterInstance(isFirstSentence));
1951                    result.addContent(output);
1952                    // if we obtained the first sentence successfully, nothing more to do
1953                    return (isFirstSentence && !output.isEmpty());
1954                }
1955
1956                @Override
1957                public Boolean visitIndex(IndexTree node, Content p) {
1958                    Content output = TagletWriter.getInlineTagOutput(element,
1959                            configuration.tagletManager, holderTag, tag,
1960                            getTagletWriterInstance(isFirstSentence));
1961                    if (output != null) {
1962                        result.addContent(output);
1963                    }
1964                    return false;
1965                }
1966
1967                @Override
1968                public Boolean visitLink(LinkTree node, Content c) {
1969                    // we need to pass the DocTreeImpl here, so ignore node
1970                    result.addContent(seeTagToContent(element, tag));
1971                    return false;
1972                }
1973
1974                @Override
1975                public Boolean visitLiteral(LiteralTree node, Content c) {
1976                    String s = node.getBody().toString();
1977                    Content content = new StringContent(utils.normalizeNewlines(s));
1978                    if (node.getKind() == CODE)
1979                        content = HtmlTree.CODE(content);
1980                    result.addContent(content);
1981                    return false;
1982                }
1983
1984                @Override
1985                public Boolean visitSee(SeeTree node, Content c) {
1986                    // we need to pass the DocTreeImpl here, so ignore node
1987                    result.addContent(seeTagToContent(element, tag));
1988                    return false;
1989                }
1990
1991                @Override
1992                public Boolean visitStartElement(StartElementTree node, Content c) {
1993                    String text = "<" + node.getName();
1994                    RawHtml rawHtml = new RawHtml(utils.normalizeNewlines(text));
1995                    result.addContent(rawHtml);
1996
1997                    for (DocTree dt : node.getAttributes()) {
1998                        dt.accept(this, null);
1999                    }
2000                    result.addContent(new RawHtml(node.isSelfClosing() ? "/>" : ">"));
2001                    return false;
2002                }
2003
2004                private CharSequence textCleanup(String text, boolean isLast) {
2005                    return textCleanup(text, isLast, false);
2006                }
2007
2008                private CharSequence textCleanup(String text, boolean isLast, boolean trimLeader) {
2009                    if (trimLeader) {
2010                        text = removeLeadingWhitespace(text);
2011                    }
2012                    if (isFirstSentence && isLast) {
2013                        text = removeTrailingWhitespace(text);
2014                    }
2015                    text = utils.replaceTabs(text);
2016                    return utils.normalizeNewlines(text);
2017                }
2018
2019                @Override
2020                public Boolean visitText(TextTree node, Content c) {
2021                    String text = node.getBody();
2022                    result.addContent(new RawHtml(textCleanup(text, isLast(node), commentRemoved)));
2023                    commentRemoved = false;
2024                    return false;
2025                }
2026
2027                @Override
2028                protected Boolean defaultAction(DocTree node, Content c) {
2029                    Content output = TagletWriter.getInlineTagOutput(element,
2030                            configuration.tagletManager, holderTag, tag,
2031                            getTagletWriterInstance(isFirstSentence));
2032                    if (output != null) {
2033                        result.addContent(output);
2034                    }
2035                    return false;
2036                }
2037
2038            }.visit(tag, null);
2039            if (allDone)
2040                break;
2041        }
2042        return result;
2043    }
2044
2045    private String removeTrailingWhitespace(String text) {
2046        char[] buf = text.toCharArray();
2047        for (int i = buf.length - 1; i > 0 ; i--) {
2048            if (!Character.isWhitespace(buf[i]))
2049                return text.substring(0, i + 1);
2050        }
2051        return text;
2052    }
2053
2054    private String removeLeadingWhitespace(String text) {
2055        char[] buf = text.toCharArray();
2056        for (int i = 0; i < buf.length; i++) {
2057            if (!Character.isWhitespace(buf[i])) {
2058                return text.substring(i);
2059            }
2060        }
2061        return text;
2062    }
2063
2064    /**
2065     * Return true if relative links should not be redirected.
2066     *
2067     * @return Return true if a relative link should not be redirected.
2068     */
2069    private boolean shouldNotRedirectRelativeLinks() {
2070        return  this instanceof AnnotationTypeWriter ||
2071                this instanceof ClassWriter ||
2072                this instanceof PackageSummaryWriter;
2073    }
2074
2075    /**
2076     * Suppose a piece of documentation has a relative link.  When you copy
2077     * that documentation to another place such as the index or class-use page,
2078     * that relative link will no longer work.  We should redirect those links
2079     * so that they will work again.
2080     * <p>
2081     * Here is the algorithm used to fix the link:
2082     * <p>
2083     * {@literal <relative link> => docRoot + <relative path to file> + <relative link> }
2084     * <p>
2085     * For example, suppose DocletEnvironment has this link:
2086     * {@literal <a href="package-summary.html">The package Page</a> }
2087     * <p>
2088     * If this link appeared in the index, we would redirect
2089     * the link like this:
2090     *
2091     * {@literal <a href="./com/sun/javadoc/package-summary.html">The package Page</a>}
2092     *
2093     * @param element the Element object whose documentation is being written.
2094     * @param text the text being written.
2095     *
2096     * @return the text, with all the relative links redirected to work.
2097     */
2098    private String redirectRelativeLinks(Element element, TextTree tt) {
2099        String text = tt.getBody();
2100        if (element == null || utils.isOverviewElement(element) || shouldNotRedirectRelativeLinks()) {
2101            return text;
2102        }
2103
2104        DocPath redirectPathFromRoot = new SimpleElementVisitor9<DocPath, Void>() {
2105            @Override
2106            public DocPath visitType(TypeElement e, Void p) {
2107                return DocPath.forPackage(utils.containingPackage(e));
2108            }
2109
2110            @Override
2111            public DocPath visitPackage(PackageElement e, Void p) {
2112                return DocPath.forPackage(e);
2113            }
2114
2115            @Override
2116            public DocPath visitVariable(VariableElement e, Void p) {
2117                return DocPath.forPackage(utils.containingPackage(e));
2118            }
2119
2120            @Override
2121            public DocPath visitExecutable(ExecutableElement e, Void p) {
2122                return DocPath.forPackage(utils.containingPackage(e));
2123            }
2124
2125            @Override
2126            protected DocPath defaultAction(Element e, Void p) {
2127                return null;
2128            }
2129        }.visit(element);
2130        if (redirectPathFromRoot == null) {
2131            return text;
2132        }
2133        String lower = Utils.toLowerCase(text);
2134        if (!(lower.startsWith("mailto:")
2135                || lower.startsWith("http:")
2136                || lower.startsWith("https:")
2137                || lower.startsWith("file:"))) {
2138            text = "{@" + (new DocRootTaglet()).getName() + "}/"
2139                    + redirectPathFromRoot.resolve(text).getPath();
2140            text = replaceDocRootDir(text);
2141        }
2142        return text;
2143    }
2144
2145    static final Set<String> blockTags = new HashSet<>();
2146    static {
2147        for (HtmlTag t: HtmlTag.values()) {
2148            if (t.blockType == HtmlTag.BlockType.BLOCK)
2149                blockTags.add(t.value);
2150        }
2151    }
2152
2153    /**
2154     * Add a link to the stylesheet file.
2155     *
2156     * @param head the content tree to which the files will be added
2157     */
2158    public void addStyleSheetProperties(Content head) {
2159        String stylesheetfile = configuration.stylesheetfile;
2160        DocPath stylesheet;
2161        if (stylesheetfile.isEmpty()) {
2162            stylesheet = DocPaths.STYLESHEET;
2163        } else {
2164            DocFile file = DocFile.createFileForInput(configuration, stylesheetfile);
2165            stylesheet = DocPath.create(file.getName());
2166        }
2167        HtmlTree link = HtmlTree.LINK("stylesheet", "text/css",
2168                pathToRoot.resolve(stylesheet).getPath(),
2169                "Style");
2170        head.addContent(link);
2171        if (configuration.createindex) {
2172            HtmlTree jq_link = HtmlTree.LINK("stylesheet", "text/css",
2173                    pathToRoot.resolve(DocPaths.JQUERY_FILES.resolve(DocPaths.JQUERY_STYLESHEET_FILE)).getPath(),
2174                    "Style");
2175            head.addContent(jq_link);
2176        }
2177    }
2178
2179    /**
2180     * Add a link to the JavaScript file.
2181     *
2182     * @param head the content tree to which the files will be added
2183     */
2184    public void addScriptProperties(Content head) {
2185        HtmlTree javascript = HtmlTree.SCRIPT(pathToRoot.resolve(DocPaths.JAVASCRIPT).getPath());
2186        head.addContent(javascript);
2187        if (configuration.createindex) {
2188            if (pathToRoot != null && script != null) {
2189                String ptrPath = pathToRoot.isEmpty() ? "." : pathToRoot.getPath();
2190                script.addContent(new RawHtml("var pathtoroot = \"" + ptrPath + "/\";loadScripts(document, \'script\');"));
2191            }
2192            addJQueryFile(head, DocPaths.JSZIP_MIN);
2193            addJQueryFile(head, DocPaths.JSZIPUTILS_MIN);
2194            head.addContent(new RawHtml("<!--[if IE]>"));
2195            addJQueryFile(head, DocPaths.JSZIPUTILS_IE_MIN);
2196            head.addContent(new RawHtml("<![endif]-->"));
2197            addJQueryFile(head, DocPaths.JQUERY_JS_1_10);
2198            addJQueryFile(head, DocPaths.JQUERY_JS);
2199        }
2200    }
2201
2202    /**
2203     * Add a link to the JQuery javascript file.
2204     *
2205     * @param head the content tree to which the files will be added
2206     * @param filePath the DocPath of the file that needs to be added
2207     */
2208    private void addJQueryFile(Content head, DocPath filePath) {
2209        HtmlTree jqyeryScriptFile = HtmlTree.SCRIPT(
2210                pathToRoot.resolve(DocPaths.JQUERY_FILES.resolve(filePath)).getPath());
2211        head.addContent(jqyeryScriptFile);
2212    }
2213
2214    /**
2215     * According to
2216     * <cite>The Java&trade; Language Specification</cite>,
2217     * all the outer classes and static nested classes are core classes.
2218     */
2219    public boolean isCoreClass(TypeElement typeElement) {
2220        return utils.getEnclosingTypeElement(typeElement) == null || utils.isStatic(typeElement);
2221    }
2222
2223    /**
2224     * Adds the annotation types for the given packageElement.
2225     *
2226     * @param packageElement the package to write annotations for.
2227     * @param htmltree the documentation tree to which the annotation info will be
2228     *        added
2229     */
2230    public void addAnnotationInfo(PackageElement packageElement, Content htmltree) {
2231        addAnnotationInfo(packageElement, packageElement.getAnnotationMirrors(), htmltree);
2232    }
2233
2234    /**
2235     * Add the annotation types of the executable receiver.
2236     *
2237     * @param method the executable to write the receiver annotations for.
2238     * @param descList list of annotation description.
2239     * @param htmltree the documentation tree to which the annotation info will be
2240     *        added
2241     */
2242    public void addReceiverAnnotationInfo(ExecutableElement method, List<AnnotationMirror> descList,
2243            Content htmltree) {
2244        addAnnotationInfo(0, method, descList, false, htmltree);
2245    }
2246
2247    /*
2248     * this is a hack to delay dealing with Annotations in the writers, the assumption
2249     * is that all necessary checks have been made to get here.
2250     */
2251    public void addReceiverAnnotationInfo(ExecutableElement method, TypeMirror rcvrTypeMirror,
2252            List<? extends AnnotationMirror> annotationMirrors, Content htmltree) {
2253        TypeMirror rcvrType = method.getReceiverType();
2254        List<? extends AnnotationMirror> annotationMirrors1 = rcvrType.getAnnotationMirrors();
2255        addAnnotationInfo(0, method, annotationMirrors1, false, htmltree);
2256    }
2257
2258    /**
2259     * Adds the annotatation types for the given element.
2260     *
2261     * @param element the package to write annotations for
2262     * @param htmltree the content tree to which the annotation types will be added
2263     */
2264    public void addAnnotationInfo(Element element, Content htmltree) {
2265        addAnnotationInfo(element, element.getAnnotationMirrors(), htmltree);
2266    }
2267
2268    /**
2269     * Add the annotatation types for the given element and parameter.
2270     *
2271     * @param indent the number of spaces to indent the parameters.
2272     * @param element the element to write annotations for.
2273     * @param param the parameter to write annotations for.
2274     * @param tree the content tree to which the annotation types will be added
2275     */
2276    public boolean addAnnotationInfo(int indent, Element element, VariableElement param,
2277            Content tree) {
2278        return addAnnotationInfo(indent, element, param.getAnnotationMirrors(), false, tree);
2279    }
2280
2281    /**
2282     * Adds the annotatation types for the given Element.
2283     *
2284     * @param element the element to write annotations for.
2285     * @param descList the array of {@link AnnotationDesc}.
2286     * @param htmltree the documentation tree to which the annotation info will be
2287     *        added
2288     */
2289    private void addAnnotationInfo(Element element, List<? extends AnnotationMirror> descList,
2290            Content htmltree) {
2291        addAnnotationInfo(0, element, descList, true, htmltree);
2292    }
2293
2294    /**
2295     * Adds the annotation types for the given element.
2296     *
2297     * @param indent the number of extra spaces to indent the annotations.
2298     * @param element the element to write annotations for.
2299     * @param descList the array of {@link AnnotationDesc}.
2300     * @param htmltree the documentation tree to which the annotation info will be
2301     *        added
2302     */
2303    private boolean addAnnotationInfo(int indent, Element element,
2304            List<? extends AnnotationMirror> descList, boolean lineBreak, Content htmltree) {
2305        List<Content> annotations = getAnnotations(indent, descList, lineBreak);
2306        String sep = "";
2307        if (annotations.isEmpty()) {
2308            return false;
2309        }
2310        for (Content annotation: annotations) {
2311            htmltree.addContent(sep);
2312            htmltree.addContent(annotation);
2313            if (!lineBreak) {
2314                sep = " ";
2315            }
2316        }
2317        return true;
2318    }
2319
2320   /**
2321     * Return the string representations of the annotation types for
2322     * the given doc.
2323     *
2324     * @param indent the number of extra spaces to indent the annotations.
2325     * @param descList the array of {@link AnnotationDesc}.
2326     * @param linkBreak if true, add new line between each member value.
2327     * @return an array of strings representing the annotations being
2328     *         documented.
2329     */
2330    private List<Content> getAnnotations(int indent, List<? extends AnnotationMirror> descList, boolean linkBreak) {
2331        return getAnnotations(indent, descList, linkBreak, true);
2332    }
2333
2334    private List<Content> getAnnotations(int indent, AnnotationMirror amirror, boolean linkBreak) {
2335        List<AnnotationMirror> descList = new ArrayList<>();
2336        descList.add(amirror);
2337        return getAnnotations(indent, descList, linkBreak, true);
2338    }
2339
2340    /**
2341     * Return the string representations of the annotation types for
2342     * the given doc.
2343     *
2344     * A {@code null} {@code elementType} indicates that all the
2345     * annotations should be returned without any filtering.
2346     *
2347     * @param indent the number of extra spaces to indent the annotations.
2348     * @param descList the array of {@link AnnotationDesc}.
2349     * @param linkBreak if true, add new line between each member value.
2350     * @param isJava5DeclarationLocation
2351     * @return an array of strings representing the annotations being
2352     *         documented.
2353     */
2354    public List<Content> getAnnotations(int indent, List<? extends AnnotationMirror> descList,
2355            boolean linkBreak, boolean isJava5DeclarationLocation) {
2356        List<Content> results = new ArrayList<>();
2357        ContentBuilder annotation;
2358        for (AnnotationMirror aDesc : descList) {
2359            TypeElement annotationElement = (TypeElement)aDesc.getAnnotationType().asElement();
2360            // If an annotation is not documented, do not add it to the list. If
2361            // the annotation is of a repeatable type, and if it is not documented
2362            // and also if its container annotation is not documented, do not add it
2363            // to the list. If an annotation of a repeatable type is not documented
2364            // but its container is documented, it will be added to the list.
2365            if (!utils.isDocumentedAnnotation(annotationElement) &&
2366                (!isAnnotationDocumented && !isContainerDocumented)) {
2367                continue;
2368            }
2369            /* TODO: check logic here to correctly handle declaration
2370             * and type annotations.
2371            if  (utils.isDeclarationAnnotation(annotationElement, isJava5DeclarationLocation)) {
2372                continue;
2373            }*/
2374            annotation = new ContentBuilder();
2375            isAnnotationDocumented = false;
2376            LinkInfoImpl linkInfo = new LinkInfoImpl(configuration,
2377                                                     LinkInfoImpl.Kind.ANNOTATION, annotationElement);
2378            Map<? extends ExecutableElement, ? extends AnnotationValue> pairs = aDesc.getElementValues();
2379            // If the annotation is synthesized, do not print the container.
2380            if (utils.configuration.workArounds.isSynthesized(aDesc)) {
2381                for (ExecutableElement ee : pairs.keySet()) {
2382                    AnnotationValue annotationValue = pairs.get(ee);
2383                    List<AnnotationValue> annotationTypeValues = new ArrayList<>();
2384
2385                    new SimpleAnnotationValueVisitor9<Void, List<AnnotationValue>>() {
2386                        @Override
2387                        public Void visitArray(List<? extends AnnotationValue> vals, List<AnnotationValue> p) {
2388                            p.addAll(vals);
2389                            return null;
2390                        }
2391
2392                        @Override
2393                        protected Void defaultAction(Object o, List<AnnotationValue> p) {
2394                            p.add(annotationValue);
2395                            return null;
2396                        }
2397                    }.visit(annotationValue, annotationTypeValues);
2398
2399                    String sep = "";
2400                    for (AnnotationValue av : annotationTypeValues) {
2401                        annotation.addContent(sep);
2402                        annotation.addContent(annotationValueToContent(av));
2403                        sep = " ";
2404                    }
2405                }
2406            } else if (isAnnotationArray(pairs)) {
2407                // If the container has 1 or more value defined and if the
2408                // repeatable type annotation is not documented, do not print
2409                // the container.
2410                if (pairs.size() == 1 && isAnnotationDocumented) {
2411                    List<AnnotationValue> annotationTypeValues = new ArrayList<>();
2412                    for (AnnotationValue a :  pairs.values()) {
2413                        new SimpleAnnotationValueVisitor9<Void, List<AnnotationValue>>() {
2414                            @Override
2415                            public Void visitArray(List<? extends AnnotationValue> vals, List<AnnotationValue> annotationTypeValues) {
2416                               for (AnnotationValue av : vals) {
2417                                   annotationTypeValues.add(av);
2418                               }
2419                               return null;
2420                            }
2421                        }.visit(a, annotationTypeValues);
2422                    }
2423                    String sep = "";
2424                    for (AnnotationValue av : annotationTypeValues) {
2425                        annotation.addContent(sep);
2426                        annotation.addContent(annotationValueToContent(av));
2427                        sep = " ";
2428                    }
2429                }
2430                // If the container has 1 or more value defined and if the
2431                // repeatable type annotation is not documented, print the container.
2432                else {
2433                    addAnnotations(annotationElement, linkInfo, annotation, pairs,
2434                                   indent, false);
2435                }
2436            }
2437            else {
2438                addAnnotations(annotationElement, linkInfo, annotation, pairs,
2439                               indent, linkBreak);
2440            }
2441            annotation.addContent(linkBreak ? DocletConstants.NL : "");
2442            results.add(annotation);
2443        }
2444        return results;
2445    }
2446
2447    /**
2448     * Add annotation to the annotation string.
2449     *
2450     * @param annotationDoc the annotation being documented
2451     * @param linkInfo the information about the link
2452     * @param annotation the annotation string to which the annotation will be added
2453     * @param pairs annotation type element and value pairs
2454     * @param indent the number of extra spaces to indent the annotations.
2455     * @param linkBreak if true, add new line between each member value
2456     */
2457    private void addAnnotations(TypeElement annotationDoc, LinkInfoImpl linkInfo,
2458        ContentBuilder annotation, Map<? extends ExecutableElement,? extends AnnotationValue>map,
2459        int indent, boolean linkBreak) {
2460        linkInfo.label = new StringContent("@");
2461        linkInfo.label.addContent(annotationDoc.getSimpleName());
2462        annotation.addContent(getLink(linkInfo));
2463        if (!map.isEmpty()) {
2464            annotation.addContent("(");
2465            boolean isFirst = true;
2466            Set<? extends ExecutableElement> keys = map.keySet();
2467            boolean multipleValues = keys.size() > 1;
2468            for (ExecutableElement element : keys) {
2469                if (isFirst) {
2470                    isFirst = false;
2471                } else {
2472                    annotation.addContent(",");
2473                    if (linkBreak) {
2474                        annotation.addContent(DocletConstants.NL);
2475                        int spaces = annotationDoc.getSimpleName().length() + 2;
2476                        for (int k = 0; k < (spaces + indent); k++) {
2477                            annotation.addContent(" ");
2478                        }
2479                    }
2480                }
2481                String simpleName = element.getSimpleName().toString();
2482                if (multipleValues || !"value".equals(simpleName)) { // Omit "value=" where unnecessary
2483                    annotation.addContent(getDocLink(LinkInfoImpl.Kind.ANNOTATION,
2484                                                     element, simpleName, false));
2485                    annotation.addContent("=");
2486                }
2487                AnnotationValue annotationValue = map.get(element);
2488                List<AnnotationValue> annotationTypeValues = new ArrayList<>();
2489                new SimpleAnnotationValueVisitor9<Void, AnnotationValue>() {
2490                    @Override
2491                    public Void visitArray(List<? extends AnnotationValue> vals, AnnotationValue p) {
2492                        annotationTypeValues.addAll(vals);
2493                        return null;
2494                    }
2495                    @Override
2496                    protected Void defaultAction(Object o, AnnotationValue p) {
2497                        annotationTypeValues.add(p);
2498                        return null;
2499                    }
2500                }.visit(annotationValue, annotationValue);
2501                annotation.addContent(annotationTypeValues.size() == 1 ? "" : "{");
2502                String sep = "";
2503                for (AnnotationValue av : annotationTypeValues) {
2504                    annotation.addContent(sep);
2505                    annotation.addContent(annotationValueToContent(av));
2506                    sep = ",";
2507                }
2508                annotation.addContent(annotationTypeValues.size() == 1 ? "" : "}");
2509                isContainerDocumented = false;
2510            }
2511            annotation.addContent(")");
2512        }
2513    }
2514
2515    /**
2516     * Check if the annotation contains an array of annotation as a value. This
2517     * check is to verify if a repeatable type annotation is present or not.
2518     *
2519     * @param pairs annotation type element and value pairs
2520     *
2521     * @return true if the annotation contains an array of annotation as a value.
2522     */
2523    private boolean isAnnotationArray(Map<? extends ExecutableElement, ? extends AnnotationValue> pairs) {
2524        AnnotationValue annotationValue;
2525        for (ExecutableElement ee : pairs.keySet()) {
2526            annotationValue = pairs.get(ee);
2527            boolean rvalue = new SimpleAnnotationValueVisitor9<Boolean, Void>() {
2528                @Override
2529                public Boolean visitArray(List<? extends AnnotationValue> vals, Void p) {
2530                    if (vals.size() > 1) {
2531                        if (vals.get(0) instanceof AnnotationMirror) {
2532                            isContainerDocumented = true;
2533                            return new SimpleAnnotationValueVisitor9<Boolean, Void>() {
2534                                @Override
2535                                public Boolean visitAnnotation(AnnotationMirror a, Void p) {
2536                                    isContainerDocumented = true;
2537                                    Element asElement = a.getAnnotationType().asElement();
2538                                    if (utils.isDocumentedAnnotation((TypeElement)asElement)) {
2539                                        isAnnotationDocumented = true;
2540                                    }
2541                                    return true;
2542                                }
2543                                @Override
2544                                protected Boolean defaultAction(Object o, Void p) {
2545                                    return false;
2546                                }
2547                            }.visit(vals.get(0));
2548                        }
2549                    }
2550                    return false;
2551                }
2552
2553                @Override
2554                protected Boolean defaultAction(Object o, Void p) {
2555                    return false;
2556                }
2557            }.visit(annotationValue);
2558            if (rvalue) {
2559                return true;
2560            }
2561        }
2562        return false;
2563    }
2564
2565    private Content annotationValueToContent(AnnotationValue annotationValue) {
2566        return new SimpleAnnotationValueVisitor9<Content, Void>() {
2567
2568            @Override
2569            public Content visitType(TypeMirror t, Void p) {
2570                return new SimpleTypeVisitor9<Content, Void>() {
2571                    @Override
2572                    public Content visitDeclared(DeclaredType t, Void p) {
2573                        LinkInfoImpl linkInfo = new LinkInfoImpl(configuration,
2574                                LinkInfoImpl.Kind.ANNOTATION, t);
2575                        String name = utils.isIncluded(t.asElement())
2576                                ? t.asElement().getSimpleName().toString()
2577                                : utils.getFullyQualifiedName(t.asElement());
2578                        linkInfo.label = new StringContent(name + utils.getDimension(t) + ".class");
2579                        return getLink(linkInfo);
2580                    }
2581                    @Override
2582                    protected Content defaultAction(TypeMirror e, Void p) {
2583                        return new StringContent(t + utils.getDimension(t) + ".class");
2584                    }
2585                }.visit(t);
2586            }
2587            @Override
2588            public Content visitAnnotation(AnnotationMirror a, Void p) {
2589                List<Content> list = getAnnotations(0, a, false);
2590                ContentBuilder buf = new ContentBuilder();
2591                for (Content c : list) {
2592                    buf.addContent(c);
2593                }
2594                return buf;
2595            }
2596            @Override
2597            public Content visitEnumConstant(VariableElement c, Void p) {
2598                return getDocLink(LinkInfoImpl.Kind.ANNOTATION,
2599                        c, c.getSimpleName(), false);
2600            }
2601            @Override
2602            public Content visitArray(List<? extends AnnotationValue> vals, Void p) {
2603                ContentBuilder buf = new ContentBuilder();
2604                String sep = "";
2605                for (AnnotationValue av : vals) {
2606                    buf.addContent(sep);
2607                    buf.addContent(visit(av));
2608                    sep = " ";
2609                }
2610                return buf;
2611            }
2612            @Override
2613            protected Content defaultAction(Object o, Void p) {
2614                return new StringContent(annotationValue.toString());
2615            }
2616        }.visit(annotationValue);
2617    }
2618
2619    /**
2620     * Return the configuration for this doclet.
2621     *
2622     * @return the configuration for this doclet.
2623     */
2624    @Override
2625    public Configuration configuration() {
2626        return configuration;
2627    }
2628}
2629