/* * Copyright (c) 1998, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.tools.doclets.formats.html; import java.io.*; import java.text.SimpleDateFormat; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.sun.javadoc.*; import com.sun.tools.doclets.formats.html.markup.*; import com.sun.tools.doclets.internal.toolkit.*; import com.sun.tools.doclets.internal.toolkit.taglets.*; import com.sun.tools.doclets.internal.toolkit.util.*; import com.sun.tools.javac.util.StringUtils; /** * Class for the Html Format Code Generation specific to JavaDoc. * This Class contains methods related to the Html Code Generation which * are used extensively while generating the entire documentation. * * <p><b>This is NOT part of any supported API. * If you write code that depends on this, you do so at your own risk. * This code and its internal interfaces are subject to change or * deletion without notice.</b> * * @since 1.2 * @author Atul M Dambalkar * @author Robert Field * @author Bhavesh Patel (Modified) */ @Deprecated public class HtmlDocletWriter extends HtmlDocWriter { /** * Relative path from the file getting generated to the destination * directory. For example, if the file getting generated is * "java/lang/Object.html", then the path to the root is "../..". * This string can be empty if the file getting generated is in * the destination directory. */ public final DocPath pathToRoot; /** * Platform-independent path from the current or the * destination directory to the file getting generated. * Used when creating the file. */ public final DocPath path; /** * Name of the file getting generated. If the file getting generated is * "java/lang/Object.html", then the filename is "Object.html". */ public final DocPath filename; /** * The global configuration information for this run. */ public final ConfigurationImpl configuration; protected final Utils utils; /** * To check whether annotation heading is printed or not. */ protected boolean printedAnnotationHeading = false; /** * To check whether annotation field heading is printed or not. */ protected boolean printedAnnotationFieldHeading = false; /** * To check whether the repeated annotations is documented or not. */ private boolean isAnnotationDocumented = false; /** * To check whether the container annotations is documented or not. */ private boolean isContainerDocumented = false; HtmlTree fixedNavDiv = new HtmlTree(HtmlTag.DIV); /** * Constructor to construct the HtmlStandardWriter object. * * @param path File to be generated. */ public HtmlDocletWriter(ConfigurationImpl configuration, DocPath path) throws IOException { super(configuration, path); this.configuration = configuration; this.utils = configuration.utils; this.path = path; this.pathToRoot = path.parent().invert(); this.filename = path.basename(); } /** * Replace {@docRoot} tag used in options that accept HTML text, such * as -header, -footer, -top and -bottom, and when converting a relative * HREF where commentTagsToString inserts a {@docRoot} where one was * missing. (Also see DocRootTaglet for {@docRoot} tags in doc * comments.) * <p> * Replace {@docRoot} tag in htmlstr with the relative path to the * destination directory from the directory where the file is being * written, looping to handle all such tags in htmlstr. * <p> * For example, for "-d docs" and -header containing {@docRoot}, when * the HTML page for source file p/C1.java is being generated, the * {@docRoot} tag would be inserted into the header as "../", * the relative path from docs/p/ to docs/ (the document root). * <p> * Note: This doc comment was written with '&#064;' representing '@' * to prevent the inline tag from being interpreted. */ public String replaceDocRootDir(String htmlstr) { // Return if no inline tags exist int index = htmlstr.indexOf("{@"); if (index < 0) { return htmlstr; } Matcher docrootMatcher = docrootPattern.matcher(htmlstr); if (!docrootMatcher.find()) { return htmlstr; } StringBuilder buf = new StringBuilder(); int prevEnd = 0; do { int match = docrootMatcher.start(); // append htmlstr up to start of next {@docroot} buf.append(htmlstr.substring(prevEnd, match)); prevEnd = docrootMatcher.end(); if (configuration.docrootparent.length() > 0 && htmlstr.startsWith("/..", prevEnd)) { // Insert the absolute link if {@docRoot} is followed by "/..". buf.append(configuration.docrootparent); prevEnd += 3; } else { // Insert relative path where {@docRoot} was located buf.append(pathToRoot.isEmpty() ? "." : pathToRoot.getPath()); } // Append slash if next character is not a slash if (prevEnd < htmlstr.length() && htmlstr.charAt(prevEnd) != '/') { buf.append('/'); } } while (docrootMatcher.find()); buf.append(htmlstr.substring(prevEnd)); return buf.toString(); } //where: // Note: {@docRoot} is not case sensitive when passed in w/command line option: private static final Pattern docrootPattern = Pattern.compile(Pattern.quote("{@docroot}"), Pattern.CASE_INSENSITIVE); /** * Get the script to show or hide the All classes link. * * @param id id of the element to show or hide * @return a content tree for the script */ public Content getAllClassesLinkScript(String id) { HtmlTree script = HtmlTree.SCRIPT(); String scriptCode = "<!--" + DocletConstants.NL + " allClassesLink = document.getElementById(\"" + id + "\");" + DocletConstants.NL + " if(window==top) {" + DocletConstants.NL + " allClassesLink.style.display = \"block\";" + DocletConstants.NL + " }" + DocletConstants.NL + " else {" + DocletConstants.NL + " allClassesLink.style.display = \"none\";" + DocletConstants.NL + " }" + DocletConstants.NL + " //-->" + DocletConstants.NL; Content scriptContent = new RawHtml(scriptCode); script.addContent(scriptContent); Content div = HtmlTree.DIV(script); Content div_noscript = HtmlTree.DIV(getResource("doclet.No_Script_Message")); Content noScript = HtmlTree.NOSCRIPT(div_noscript); div.addContent(noScript); return div; } /** * Add method information. * * @param method the method to be documented * @param dl the content tree to which the method information will be added */ private void addMethodInfo(MethodDoc method, Content dl) { ClassDoc[] intfacs = method.containingClass().interfaces(); MethodDoc overriddenMethod = method.overriddenMethod(); // Check whether there is any implementation or overridden info to be // printed. If no overridden or implementation info needs to be // printed, do not print this section. if ((intfacs.length > 0 && new ImplementedMethods(method, this.configuration).build().length > 0) || overriddenMethod != null) { MethodWriterImpl.addImplementsInfo(this, method, dl); if (overriddenMethod != null) { MethodWriterImpl.addOverridden(this, method.overriddenType(), overriddenMethod, dl); } } } /** * Adds the tags information. * * @param doc the doc for which the tags will be generated * @param htmltree the documentation tree to which the tags will be added */ protected void addTagsInfo(Doc doc, Content htmltree) { if (configuration.nocomment) { return; } Content dl = new HtmlTree(HtmlTag.DL); if (doc instanceof MethodDoc) { addMethodInfo((MethodDoc) doc, dl); } Content output = new ContentBuilder(); TagletWriter.genTagOuput(configuration.tagletManager, doc, configuration.tagletManager.getCustomTaglets(doc), getTagletWriterInstance(false), output); dl.addContent(output); htmltree.addContent(dl); } /** * Check whether there are any tags for Serialization Overview * section to be printed. * * @param field the FieldDoc object to check for tags. * @return true if there are tags to be printed else return false. */ protected boolean hasSerializationOverviewTags(FieldDoc field) { Content output = new ContentBuilder(); TagletWriter.genTagOuput(configuration.tagletManager, field, configuration.tagletManager.getCustomTaglets(field), getTagletWriterInstance(false), output); return !output.isEmpty(); } /** * Returns a TagletWriter that knows how to write HTML. * * @return a TagletWriter that knows how to write HTML. */ public TagletWriter getTagletWriterInstance(boolean isFirstSentence) { return new TagletWriterImpl(this, isFirstSentence); } /** * Get Package link, with target frame. * * @param pd The link will be to the "package-summary.html" page for this package * @param target name of the target frame * @param label tag for the link * @return a content for the target package link */ public Content getTargetPackageLink(PackageDoc pd, String target, Content label) { return getHyperLink(pathString(pd, DocPaths.PACKAGE_SUMMARY), label, "", target); } /** * Generates the HTML document tree and prints it out. * * @param metakeywords Array of String keywords for META tag. Each element * of the array is assigned to a separate META tag. * Pass in null for no array * @param includeScript true if printing windowtitle script * false for files that appear in the left-hand frames * @param body the body htmltree to be included in the document */ public void printHtmlDocument(String[] metakeywords, boolean includeScript, Content body) throws IOException { Content htmlDocType = configuration.isOutputHtml5() ? DocType.HTML5 : DocType.TRANSITIONAL; Content htmlComment = new Comment(configuration.getText("doclet.New_Page")); Content head = new HtmlTree(HtmlTag.HEAD); head.addContent(getGeneratedBy(!configuration.notimestamp)); head.addContent(getTitle()); Content meta = HtmlTree.META("Content-Type", CONTENT_TYPE, (configuration.charset.length() > 0) ? configuration.charset : HtmlConstants.HTML_DEFAULT_CHARSET); head.addContent(meta); if (!configuration.notimestamp) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); meta = HtmlTree.META(configuration.isOutputHtml5() ? "dc.created" : "date", dateFormat.format(new Date())); head.addContent(meta); } if (metakeywords != null) { for (String metakeyword : metakeywords) { meta = HtmlTree.META("keywords", metakeyword); head.addContent(meta); } } addStyleSheetProperties(head); addScriptProperties(head); Content htmlTree = HtmlTree.HTML(configuration.getLocale().getLanguage(), head, body); Content htmlDocument = new HtmlDocument(htmlDocType, htmlComment, htmlTree); write(htmlDocument); } /** * Get the window title. * * @param title the title string to construct the complete window title * @return the window title string */ public String getWindowTitle(String title) { if (configuration.windowtitle.length() > 0) { title += " (" + configuration.windowtitle + ")"; } return title; } /** * Get user specified header and the footer. * * @param header if true print the user provided header else print the * user provided footer. */ public Content getUserHeaderFooter(boolean header) { String content; if (header) { content = replaceDocRootDir(configuration.header); } else { if (configuration.footer.length() != 0) { content = replaceDocRootDir(configuration.footer); } else { content = replaceDocRootDir(configuration.header); } } Content rawContent = new RawHtml(content); return rawContent; } /** * Adds the user specified top. * * @param htmlTree the content tree to which user specified top will be added */ public void addTop(Content htmlTree) { Content top = new RawHtml(replaceDocRootDir(configuration.top)); fixedNavDiv.addContent(top); } /** * Adds the user specified bottom. * * @param htmlTree the content tree to which user specified bottom will be added */ public void addBottom(Content htmlTree) { Content bottom = new RawHtml(replaceDocRootDir(configuration.bottom)); Content small = HtmlTree.SMALL(bottom); Content p = HtmlTree.P(HtmlStyle.legalCopy, small); htmlTree.addContent(p); } /** * Adds the navigation bar for the Html page at the top and and the bottom. * * @param header If true print navigation bar at the top of the page else * @param htmlTree the HtmlTree to which the nav links will be added */ protected void addNavLinks(boolean header, Content htmlTree) { if (!configuration.nonavbar) { Content tree = (configuration.allowTag(HtmlTag.NAV)) ? HtmlTree.NAV() : htmlTree; String allClassesId = "allclasses_"; HtmlTree navDiv = new HtmlTree(HtmlTag.DIV); fixedNavDiv.addStyle(HtmlStyle.fixedNav); Content skipNavLinks = configuration.getResource("doclet.Skip_navigation_links"); if (header) { fixedNavDiv.addContent(HtmlConstants.START_OF_TOP_NAVBAR); navDiv.addStyle(HtmlStyle.topNav); allClassesId += "navbar_top"; Content a = getMarkerAnchor(SectionName.NAVBAR_TOP); //WCAG - Hyperlinks should contain text or an image with alt text - for AT tools navDiv.addContent(a); Content skipLinkContent = HtmlTree.DIV(HtmlStyle.skipNav, getHyperLink( getDocLink(SectionName.SKIP_NAVBAR_TOP), skipNavLinks, skipNavLinks.toString(), "")); navDiv.addContent(skipLinkContent); } else { tree.addContent(HtmlConstants.START_OF_BOTTOM_NAVBAR); navDiv.addStyle(HtmlStyle.bottomNav); allClassesId += "navbar_bottom"; Content a = getMarkerAnchor(SectionName.NAVBAR_BOTTOM); navDiv.addContent(a); Content skipLinkContent = HtmlTree.DIV(HtmlStyle.skipNav, getHyperLink( getDocLink(SectionName.SKIP_NAVBAR_BOTTOM), skipNavLinks, skipNavLinks.toString(), "")); navDiv.addContent(skipLinkContent); } if (header) { navDiv.addContent(getMarkerAnchor(SectionName.NAVBAR_TOP_FIRSTROW)); } else { navDiv.addContent(getMarkerAnchor(SectionName.NAVBAR_BOTTOM_FIRSTROW)); } HtmlTree navList = new HtmlTree(HtmlTag.UL); navList.addStyle(HtmlStyle.navList); navList.addAttr(HtmlAttr.TITLE, configuration.getText("doclet.Navigation")); if (configuration.createoverview) { navList.addContent(getNavLinkContents()); } if (configuration.packages.size() == 1) { navList.addContent(getNavLinkPackage(configuration.packages.first())); } else if (configuration.packages.size() > 1) { navList.addContent(getNavLinkPackage()); } navList.addContent(getNavLinkClass()); if(configuration.classuse) { navList.addContent(getNavLinkClassUse()); } if(configuration.createtree) { navList.addContent(getNavLinkTree()); } if(!(configuration.nodeprecated || configuration.nodeprecatedlist)) { navList.addContent(getNavLinkDeprecated()); } if(configuration.createindex) { navList.addContent(getNavLinkIndex()); } if (!configuration.nohelp) { navList.addContent(getNavLinkHelp()); } navDiv.addContent(navList); Content aboutDiv = HtmlTree.DIV(HtmlStyle.aboutLanguage, getUserHeaderFooter(header)); navDiv.addContent(aboutDiv); if (header) { fixedNavDiv.addContent(navDiv); } else { tree.addContent(navDiv); } Content ulNav = HtmlTree.UL(HtmlStyle.navList, getNavLinkPrevious()); ulNav.addContent(getNavLinkNext()); Content subDiv = HtmlTree.DIV(HtmlStyle.subNav, ulNav); Content ulFrames = HtmlTree.UL(HtmlStyle.navList, getNavShowLists()); ulFrames.addContent(getNavHideLists(filename)); subDiv.addContent(ulFrames); HtmlTree ulAllClasses = HtmlTree.UL(HtmlStyle.navList, getNavLinkClassIndex()); ulAllClasses.addAttr(HtmlAttr.ID, allClassesId.toString()); subDiv.addContent(ulAllClasses); if (header && configuration.createindex) { HtmlTree inputText = HtmlTree.INPUT("text", "search"); HtmlTree inputReset = HtmlTree.INPUT("reset", "reset"); Content searchTxt = configuration.getResource("doclet.search"); searchTxt.addContent(getSpace()); HtmlTree liInput = HtmlTree.LI(HtmlTree.SPAN(searchTxt)); liInput.addContent(inputText); liInput.addContent(inputReset); HtmlTree ulSearch = HtmlTree.UL(HtmlStyle.navListSearch, liInput); subDiv.addContent(ulSearch); } subDiv.addContent(getAllClassesLinkScript(allClassesId.toString())); addSummaryDetailLinks(subDiv); if (header) { subDiv.addContent(getMarkerAnchor(SectionName.SKIP_NAVBAR_TOP)); fixedNavDiv.addContent(subDiv); fixedNavDiv.addContent(HtmlConstants.END_OF_TOP_NAVBAR); tree.addContent(fixedNavDiv); HtmlTree paddingDiv = HtmlTree.DIV(HtmlStyle.navPadding, getSpace()); tree.addContent(paddingDiv); } else { subDiv.addContent(getMarkerAnchor(SectionName.SKIP_NAVBAR_BOTTOM)); tree.addContent(subDiv); tree.addContent(HtmlConstants.END_OF_BOTTOM_NAVBAR); } if (configuration.allowTag(HtmlTag.NAV)) { htmlTree.addContent(tree); } } } /** * Get the word "NEXT" to indicate that no link is available. Override * this method to customize next link. * * @return a content tree for the link */ protected Content getNavLinkNext() { return getNavLinkNext(null); } /** * Get the word "PREV" to indicate that no link is available. Override * this method to customize prev link. * * @return a content tree for the link */ protected Content getNavLinkPrevious() { return getNavLinkPrevious(null); } /** * Do nothing. This is the default method. */ protected void addSummaryDetailLinks(Content navDiv) { } /** * Get link to the "overview-summary.html" page. * * @return a content tree for the link */ protected Content getNavLinkContents() { Content linkContent = getHyperLink(pathToRoot.resolve(DocPaths.OVERVIEW_SUMMARY), overviewLabel, "", ""); Content li = HtmlTree.LI(linkContent); return li; } /** * Get link to the "package-summary.html" page for the package passed. * * @param pkg Package to which link will be generated * @return a content tree for the link */ protected Content getNavLinkPackage(PackageDoc pkg) { Content linkContent = getPackageLink(pkg, packageLabel); Content li = HtmlTree.LI(linkContent); return li; } /** * Get the word "Package" , to indicate that link is not available here. * * @return a content tree for the link */ protected Content getNavLinkPackage() { Content li = HtmlTree.LI(packageLabel); return li; } /** * Get the word "Use", to indicate that link is not available. * * @return a content tree for the link */ protected Content getNavLinkClassUse() { Content li = HtmlTree.LI(useLabel); return li; } /** * Get link for previous file. * * @param prev File name for the prev link * @return a content tree for the link */ public Content getNavLinkPrevious(DocPath prev) { Content li; if (prev != null) { li = HtmlTree.LI(getHyperLink(prev, prevLabel, "", "")); } else li = HtmlTree.LI(prevLabel); return li; } /** * Get link for next file. If next is null, just print the label * without linking it anywhere. * * @param next File name for the next link * @return a content tree for the link */ public Content getNavLinkNext(DocPath next) { Content li; if (next != null) { li = HtmlTree.LI(getHyperLink(next, nextLabel, "", "")); } else li = HtmlTree.LI(nextLabel); return li; } /** * Get "FRAMES" link, to switch to the frame version of the output. * * @param link File to be linked, "index.html" * @return a content tree for the link */ protected Content getNavShowLists(DocPath link) { DocLink dl = new DocLink(link, path.getPath(), null); Content framesContent = getHyperLink(dl, framesLabel, "", "_top"); Content li = HtmlTree.LI(framesContent); return li; } /** * Get "FRAMES" link, to switch to the frame version of the output. * * @return a content tree for the link */ protected Content getNavShowLists() { return getNavShowLists(pathToRoot.resolve(DocPaths.INDEX)); } /** * Get "NO FRAMES" link, to switch to the non-frame version of the output. * * @param link File to be linked * @return a content tree for the link */ protected Content getNavHideLists(DocPath link) { Content noFramesContent = getHyperLink(link, noframesLabel, "", "_top"); Content li = HtmlTree.LI(noFramesContent); return li; } /** * Get "Tree" link in the navigation bar. If there is only one package * specified on the command line, then the "Tree" link will be to the * only "package-tree.html" file otherwise it will be to the * "overview-tree.html" file. * * @return a content tree for the link */ protected Content getNavLinkTree() { Content treeLinkContent; PackageDoc[] packages = configuration.root.specifiedPackages(); if (packages.length == 1 && configuration.root.specifiedClasses().length == 0) { treeLinkContent = getHyperLink(pathString(packages[0], DocPaths.PACKAGE_TREE), treeLabel, "", ""); } else { treeLinkContent = getHyperLink(pathToRoot.resolve(DocPaths.OVERVIEW_TREE), treeLabel, "", ""); } Content li = HtmlTree.LI(treeLinkContent); return li; } /** * Get the overview tree link for the main tree. * * @param label the label for the link * @return a content tree for the link */ protected Content getNavLinkMainTree(String label) { Content mainTreeContent = getHyperLink(pathToRoot.resolve(DocPaths.OVERVIEW_TREE), new StringContent(label)); Content li = HtmlTree.LI(mainTreeContent); return li; } /** * Get the word "Class", to indicate that class link is not available. * * @return a content tree for the link */ protected Content getNavLinkClass() { Content li = HtmlTree.LI(classLabel); return li; } /** * Get "Deprecated" API link in the navigation bar. * * @return a content tree for the link */ protected Content getNavLinkDeprecated() { Content linkContent = getHyperLink(pathToRoot.resolve(DocPaths.DEPRECATED_LIST), deprecatedLabel, "", ""); Content li = HtmlTree.LI(linkContent); return li; } /** * Get link for generated index. If the user has used "-splitindex" * command line option, then link to file "index-files/index-1.html" is * generated otherwise link to file "index-all.html" is generated. * * @return a content tree for the link */ protected Content getNavLinkClassIndex() { Content allClassesContent = getHyperLink(pathToRoot.resolve( DocPaths.ALLCLASSES_NOFRAME), allclassesLabel, "", ""); Content li = HtmlTree.LI(allClassesContent); return li; } /** * Get link for generated class index. * * @return a content tree for the link */ protected Content getNavLinkIndex() { Content linkContent = getHyperLink(pathToRoot.resolve( (configuration.splitindex ? DocPaths.INDEX_FILES.resolve(DocPaths.indexN(1)) : DocPaths.INDEX_ALL)), indexLabel, "", ""); Content li = HtmlTree.LI(linkContent); return li; } /** * Get help file link. If user has provided a help file, then generate a * link to the user given file, which is already copied to current or * destination directory. * * @return a content tree for the link */ protected Content getNavLinkHelp() { String helpfile = configuration.helpfile; DocPath helpfilenm; if (helpfile.isEmpty()) { helpfilenm = DocPaths.HELP_DOC; } else { DocFile file = DocFile.createFileForInput(configuration, helpfile); helpfilenm = DocPath.create(file.getName()); } Content linkContent = getHyperLink(pathToRoot.resolve(helpfilenm), helpLabel, "", ""); Content li = HtmlTree.LI(linkContent); return li; } /** * Get summary table header. * * @param header the header for the table * @param scope the scope of the headers * @return a content tree for the header */ public Content getSummaryTableHeader(String[] header, String scope) { Content tr = new HtmlTree(HtmlTag.TR); int size = header.length; Content tableHeader; if (size == 1) { tableHeader = new StringContent(header[0]); tr.addContent(HtmlTree.TH(HtmlStyle.colOne, scope, tableHeader)); return tr; } for (int i = 0; i < size; i++) { tableHeader = new StringContent(header[i]); if(i == 0) tr.addContent(HtmlTree.TH(HtmlStyle.colFirst, scope, tableHeader)); else if(i == (size - 1)) tr.addContent(HtmlTree.TH(HtmlStyle.colLast, scope, tableHeader)); else tr.addContent(HtmlTree.TH(scope, tableHeader)); } return tr; } /** * Get table caption. * * @param rawText the caption for the table which could be raw Html * @return a content tree for the caption */ public Content getTableCaption(Content title) { Content captionSpan = HtmlTree.SPAN(title); Content space = getSpace(); Content tabSpan = HtmlTree.SPAN(HtmlStyle.tabEnd, space); Content caption = HtmlTree.CAPTION(captionSpan); caption.addContent(tabSpan); return caption; } /** * Get the marker anchor which will be added to the documentation tree. * * @param anchorName the anchor name attribute * @return a content tree for the marker anchor */ public Content getMarkerAnchor(String anchorName) { return getMarkerAnchor(getName(anchorName), null); } /** * Get the marker anchor which will be added to the documentation tree. * * @param sectionName the section name anchor attribute for page * @return a content tree for the marker anchor */ public Content getMarkerAnchor(SectionName sectionName) { return getMarkerAnchor(sectionName.getName(), null); } /** * Get the marker anchor which will be added to the documentation tree. * * @param sectionName the section name anchor attribute for page * @param anchorName the anchor name combined with section name attribute for the page * @return a content tree for the marker anchor */ public Content getMarkerAnchor(SectionName sectionName, String anchorName) { return getMarkerAnchor(sectionName.getName() + getName(anchorName), null); } /** * Get the marker anchor which will be added to the documentation tree. * * @param anchorName the anchor name or id attribute * @param anchorContent the content that should be added to the anchor * @return a content tree for the marker anchor */ public Content getMarkerAnchor(String anchorName, Content anchorContent) { if (anchorContent == null) anchorContent = new Comment(" "); Content markerAnchor = HtmlTree.A(configuration.htmlVersion, anchorName, anchorContent); return markerAnchor; } /** * Returns a packagename content. * * @param packageDoc the package to check * @return package name content */ public Content getPackageName(PackageDoc packageDoc) { return packageDoc == null || packageDoc.name().isEmpty() ? defaultPackageLabel : getPackageLabel(packageDoc.name()); } /** * Returns a package name label. * * @param packageName the package name * @return the package name content */ public Content getPackageLabel(String packageName) { return new StringContent(packageName); } /** * Add package deprecation information to the documentation tree * * @param deprPkgs list of deprecated packages * @param headingKey the caption for the deprecated package table * @param tableSummary the summary for the deprecated package table * @param tableHeader table headers for the deprecated package table * @param contentTree the content tree to which the deprecated package table will be added */ protected void addPackageDeprecatedAPI(List<Doc> deprPkgs, String headingKey, String tableSummary, String[] tableHeader, Content contentTree) { if (deprPkgs.size() > 0) { Content caption = getTableCaption(configuration.getResource(headingKey)); Content table = (configuration.isOutputHtml5()) ? HtmlTree.TABLE(HtmlStyle.deprecatedSummary, caption) : HtmlTree.TABLE(HtmlStyle.deprecatedSummary, tableSummary, caption); table.addContent(getSummaryTableHeader(tableHeader, "col")); Content tbody = new HtmlTree(HtmlTag.TBODY); for (int i = 0; i < deprPkgs.size(); i++) { PackageDoc pkg = (PackageDoc) deprPkgs.get(i); HtmlTree td = HtmlTree.TD(HtmlStyle.colOne, getPackageLink(pkg, getPackageName(pkg))); if (pkg.tags("deprecated").length > 0) { addInlineDeprecatedComment(pkg, pkg.tags("deprecated")[0], td); } HtmlTree tr = HtmlTree.TR(td); if (i % 2 == 0) { tr.addStyle(HtmlStyle.altColor); } else { tr.addStyle(HtmlStyle.rowColor); } tbody.addContent(tr); } table.addContent(tbody); Content li = HtmlTree.LI(HtmlStyle.blockList, table); Content ul = HtmlTree.UL(HtmlStyle.blockList, li); contentTree.addContent(ul); } } /** * Return the path to the class page for a classdoc. * * @param cd Class to which the path is requested. * @param name Name of the file(doesn't include path). */ protected DocPath pathString(ClassDoc cd, DocPath name) { return pathString(cd.containingPackage(), name); } /** * Return path to the given file name in the given package. So if the name * passed is "Object.html" and the name of the package is "java.lang", and * if the relative path is "../.." then returned string will be * "../../java/lang/Object.html" * * @param pd Package in which the file name is assumed to be. * @param name File name, to which path string is. */ protected DocPath pathString(PackageDoc pd, DocPath name) { return pathToRoot.resolve(DocPath.forPackage(pd).resolve(name)); } /** * Given a package, return the name to be used in HTML anchor tag. * @param packageDoc the package. * @return the name to be used in HTML anchor tag. */ public String getPackageAnchorName(PackageDoc packageDoc) { return packageDoc == null || packageDoc.name().length() == 0 ? SectionName.UNNAMED_PACKAGE_ANCHOR.getName() : packageDoc.name(); } /** * Return the link to the given package. * * @param pkg the package to link to. * @param label the label for the link. * @return a content tree for the package link. */ public Content getPackageLink(PackageDoc pkg, String label) { return getPackageLink(pkg, new StringContent(label)); } /** * Return the link to the given package. * * @param pkg the package to link to. * @param label the label for the link. * @return a content tree for the package link. */ public Content getPackageLink(PackageDoc pkg, Content label) { boolean included = pkg != null && pkg.isIncluded(); if (! included) { for (PackageDoc p : configuration.packages) { if (p.equals(pkg)) { included = true; break; } } } if (included || pkg == null) { return getHyperLink(pathString(pkg, DocPaths.PACKAGE_SUMMARY), label); } else { DocLink crossPkgLink = getCrossPackageLink(utils.getPackageName(pkg)); if (crossPkgLink != null) { return getHyperLink(crossPkgLink, label); } else { return label; } } } public Content italicsClassName(ClassDoc cd, boolean qual) { Content name = new StringContent((qual)? cd.qualifiedName(): cd.name()); return (cd.isInterface())? HtmlTree.SPAN(HtmlStyle.interfaceName, name): name; } /** * Add the link to the content tree. * * @param doc program element doc for which the link will be added * @param label label for the link * @param htmltree the content tree to which the link will be added */ public void addSrcLink(ProgramElementDoc doc, Content label, Content htmltree) { if (doc == null) { return; } ClassDoc cd = doc.containingClass(); if (cd == null) { //d must be a class doc since in has no containing class. cd = (ClassDoc) doc; } DocPath href = pathToRoot .resolve(DocPaths.SOURCE_OUTPUT) .resolve(DocPath.forClass(cd)); Content linkContent = getHyperLink(href.fragment(SourceToHTMLConverter.getAnchorName(doc)), label, "", ""); htmltree.addContent(linkContent); } /** * Return the link to the given class. * * @param linkInfo the information about the link. * * @return the link for the given class. */ public Content getLink(LinkInfoImpl linkInfo) { LinkFactoryImpl factory = new LinkFactoryImpl(this); return factory.getLink(linkInfo); } /** * Return the type parameters for the given class. * * @param linkInfo the information about the link. * @return the type for the given class. */ public Content getTypeParameterLinks(LinkInfoImpl linkInfo) { LinkFactoryImpl factory = new LinkFactoryImpl(this); return factory.getTypeParameterLinks(linkInfo, false); } /************************************************************* * Return a class cross link to external class documentation. * The name must be fully qualified to determine which package * the class is in. The -link option does not allow users to * link to external classes in the "default" package. * * @param qualifiedClassName the qualified name of the external class. * @param refMemName the name of the member being referenced. This should * be null or empty string if no member is being referenced. * @param label the label for the external link. * @param strong true if the link should be strong. * @param style the style of the link. * @param code true if the label should be code font. */ public Content getCrossClassLink(String qualifiedClassName, String refMemName, Content label, boolean strong, String style, boolean code) { String className = ""; String packageName = qualifiedClassName == null ? "" : qualifiedClassName; int periodIndex; while ((periodIndex = packageName.lastIndexOf('.')) != -1) { className = packageName.substring(periodIndex + 1, packageName.length()) + (className.length() > 0 ? "." + className : ""); Content defaultLabel = new StringContent(className); if (code) defaultLabel = HtmlTree.CODE(defaultLabel); packageName = packageName.substring(0, periodIndex); if (getCrossPackageLink(packageName) != null) { //The package exists in external documentation, so link to the external //class (assuming that it exists). This is definitely a limitation of //the -link option. There are ways to determine if an external package //exists, but no way to determine if the external class exists. We just //have to assume that it does. DocLink link = configuration.extern.getExternalLink(packageName, pathToRoot, className + ".html", refMemName); return getHyperLink(link, (label == null) || label.isEmpty() ? defaultLabel : label, strong, style, configuration.getText("doclet.Href_Class_Or_Interface_Title", packageName), ""); } } return null; } public boolean isClassLinkable(ClassDoc cd) { if (cd.isIncluded()) { return configuration.isGeneratedDoc(cd); } return configuration.extern.isExternal(cd); } public DocLink getCrossPackageLink(String pkgName) { return configuration.extern.getExternalLink(pkgName, pathToRoot, DocPaths.PACKAGE_SUMMARY.getPath()); } /** * Get the class link. * * @param context the id of the context where the link will be added * @param cd the class doc to link to * @return a content tree for the link */ public Content getQualifiedClassLink(LinkInfoImpl.Kind context, ClassDoc cd) { return getLink(new LinkInfoImpl(configuration, context, cd) .label(configuration.getClassName(cd))); } /** * Add the class link. * * @param context the id of the context where the link will be added * @param cd the class doc to link to * @param contentTree the content tree to which the link will be added */ public void addPreQualifiedClassLink(LinkInfoImpl.Kind context, ClassDoc cd, Content contentTree) { addPreQualifiedClassLink(context, cd, false, contentTree); } /** * Retrieve the class link with the package portion of the label in * plain text. If the qualifier is excluded, it will not be included in the * link label. * * @param cd the class to link to. * @param isStrong true if the link should be strong. * @return the link with the package portion of the label in plain text. */ public Content getPreQualifiedClassLink(LinkInfoImpl.Kind context, ClassDoc cd, boolean isStrong) { ContentBuilder classlink = new ContentBuilder(); PackageDoc pd = cd.containingPackage(); if (pd != null && ! configuration.shouldExcludeQualifier(pd.name())) { classlink.addContent(getPkgName(cd)); } classlink.addContent(getLink(new LinkInfoImpl(configuration, context, cd).label(cd.name()).strong(isStrong))); return classlink; } /** * Add the class link with the package portion of the label in * plain text. If the qualifier is excluded, it will not be included in the * link label. * * @param context the id of the context where the link will be added * @param cd the class to link to * @param isStrong true if the link should be strong * @param contentTree the content tree to which the link with be added */ public void addPreQualifiedClassLink(LinkInfoImpl.Kind context, ClassDoc cd, boolean isStrong, Content contentTree) { PackageDoc pd = cd.containingPackage(); if(pd != null && ! configuration.shouldExcludeQualifier(pd.name())) { contentTree.addContent(getPkgName(cd)); } contentTree.addContent(getLink(new LinkInfoImpl(configuration, context, cd).label(cd.name()).strong(isStrong))); } /** * Add the class link, with only class name as the strong link and prefixing * plain package name. * * @param context the id of the context where the link will be added * @param cd the class to link to * @param contentTree the content tree to which the link with be added */ public void addPreQualifiedStrongClassLink(LinkInfoImpl.Kind context, ClassDoc cd, Content contentTree) { addPreQualifiedClassLink(context, cd, true, contentTree); } /** * Get the link for the given member. * * @param context the id of the context where the link will be added * @param doc the member being linked to * @param label the label for the link * @return a content tree for the doc link */ public Content getDocLink(LinkInfoImpl.Kind context, MemberDoc doc, String label) { return getDocLink(context, doc.containingClass(), doc, new StringContent(label)); } /** * Return the link for the given member. * * @param context the id of the context where the link will be printed. * @param doc the member being linked to. * @param label the label for the link. * @param strong true if the link should be strong. * @return the link for the given member. */ public Content getDocLink(LinkInfoImpl.Kind context, MemberDoc doc, String label, boolean strong) { return getDocLink(context, doc.containingClass(), doc, label, strong); } /** * Return the link for the given member. * * @param context the id of the context where the link will be printed. * @param classDoc the classDoc that we should link to. This is not * necessarily equal to doc.containingClass(). We may be * inheriting comments. * @param doc the member being linked to. * @param label the label for the link. * @param strong true if the link should be strong. * @return the link for the given member. */ public Content getDocLink(LinkInfoImpl.Kind context, ClassDoc classDoc, MemberDoc doc, String label, boolean strong) { return getDocLink(context, classDoc, doc, label, strong, false); } public Content getDocLink(LinkInfoImpl.Kind context, ClassDoc classDoc, MemberDoc doc, Content label, boolean strong) { return getDocLink(context, classDoc, doc, label, strong, false); } /** * Return the link for the given member. * * @param context the id of the context where the link will be printed. * @param classDoc the classDoc that we should link to. This is not * necessarily equal to doc.containingClass(). We may be * inheriting comments. * @param doc the member being linked to. * @param label the label for the link. * @param strong true if the link should be strong. * @param isProperty true if the doc parameter is a JavaFX property. * @return the link for the given member. */ public Content getDocLink(LinkInfoImpl.Kind context, ClassDoc classDoc, MemberDoc doc, String label, boolean strong, boolean isProperty) { return getDocLink(context, classDoc, doc, new StringContent(check(label)), strong, isProperty); } String check(String s) { if (s.matches(".*[&<>].*"))throw new IllegalArgumentException(s); return s; } public Content getDocLink(LinkInfoImpl.Kind context, ClassDoc classDoc, MemberDoc doc, Content label, boolean strong, boolean isProperty) { if (! (doc.isIncluded() || utils.isLinkable(classDoc, configuration))) { return label; } else if (doc instanceof ExecutableMemberDoc) { ExecutableMemberDoc emd = (ExecutableMemberDoc)doc; return getLink(new LinkInfoImpl(configuration, context, classDoc) .label(label).where(getName(getAnchor(emd, isProperty))).strong(strong)); } else if (doc instanceof MemberDoc) { return getLink(new LinkInfoImpl(configuration, context, classDoc) .label(label).where(getName(doc.name())).strong(strong)); } else { return label; } } /** * Return the link for the given member. * * @param context the id of the context where the link will be added * @param classDoc the classDoc that we should link to. This is not * necessarily equal to doc.containingClass(). We may be * inheriting comments * @param doc the member being linked to * @param label the label for the link * @return the link for the given member */ public Content getDocLink(LinkInfoImpl.Kind context, ClassDoc classDoc, MemberDoc doc, Content label) { if (! (doc.isIncluded() || utils.isLinkable(classDoc, configuration))) { return label; } else if (doc instanceof ExecutableMemberDoc) { ExecutableMemberDoc emd = (ExecutableMemberDoc) doc; return getLink(new LinkInfoImpl(configuration, context, classDoc) .label(label).where(getName(getAnchor(emd)))); } else if (doc instanceof MemberDoc) { return getLink(new LinkInfoImpl(configuration, context, classDoc) .label(label).where(getName(doc.name()))); } else { return label; } } public String getAnchor(ExecutableMemberDoc emd) { return getAnchor(emd, false); } public String getAnchor(ExecutableMemberDoc emd, boolean isProperty) { if (isProperty) { return emd.name(); } StringBuilder signature = new StringBuilder(emd.signature()); StringBuilder signatureParsed = new StringBuilder(); int counter = 0; for (int i = 0; i < signature.length(); i++) { char c = signature.charAt(i); if (c == '<') { counter++; } else if (c == '>') { counter--; } else if (counter == 0) { signatureParsed.append(c); } } return emd.name() + signatureParsed.toString(); } public Content seeTagToContent(SeeTag see) { String tagName = see.name(); if (! (tagName.startsWith("@link") || tagName.equals("@see"))) { return new ContentBuilder(); } String seetext = replaceDocRootDir(utils.normalizeNewlines(see.text())); //Check if @see is an href or "string" if (seetext.startsWith("<") || seetext.startsWith("\"")) { return new RawHtml(seetext); } boolean plain = tagName.equalsIgnoreCase("@linkplain"); Content label = plainOrCode(plain, new RawHtml(see.label())); //The text from the @see tag. We will output this text when a label is not specified. Content text = plainOrCode(plain, new RawHtml(seetext)); ClassDoc refClass = see.referencedClass(); String refClassName = see.referencedClassName(); MemberDoc refMem = see.referencedMember(); String refMemName = see.referencedMemberName(); if (refClass == null) { //@see is not referencing an included class PackageDoc refPackage = see.referencedPackage(); if (refPackage != null && refPackage.isIncluded()) { //@see is referencing an included package if (label.isEmpty()) label = plainOrCode(plain, new StringContent(refPackage.name())); return getPackageLink(refPackage, label); } else { //@see is not referencing an included class or package. Check for cross links. Content classCrossLink; DocLink packageCrossLink = getCrossPackageLink(refClassName); if (packageCrossLink != null) { //Package cross link found return getHyperLink(packageCrossLink, (label.isEmpty() ? text : label)); } else if ((classCrossLink = getCrossClassLink(refClassName, refMemName, label, false, "", !plain)) != null) { //Class cross link found (possibly to a member in the class) return classCrossLink; } else { //No cross link found so print warning configuration.getDocletSpecificMsg().warning(see.position(), "doclet.see.class_or_package_not_found", tagName, seetext); return (label.isEmpty() ? text: label); } } } else if (refMemName == null) { // Must be a class reference since refClass is not null and refMemName is null. if (label.isEmpty()) { label = plainOrCode(plain, new StringContent(refClass.name())); } return getLink(new LinkInfoImpl(configuration, LinkInfoImpl.Kind.DEFAULT, refClass) .label(label)); } else if (refMem == null) { // Must be a member reference since refClass is not null and refMemName is not null. // However, refMem is null, so this referenced member does not exist. return (label.isEmpty() ? text: label); } else { // Must be a member reference since refClass is not null and refMemName is not null. // refMem is not null, so this @see tag must be referencing a valid member. ClassDoc containing = refMem.containingClass(); if (see.text().trim().startsWith("#") && ! (containing.isPublic() || utils.isLinkable(containing, configuration))) { // Since the link is relative and the holder is not even being // documented, this must be an inherited link. Redirect it. // The current class either overrides the referenced member or // inherits it automatically. if (this instanceof ClassWriterImpl) { containing = ((ClassWriterImpl) this).getClassDoc(); } else if (!containing.isPublic()){ configuration.getDocletSpecificMsg().warning( see.position(), "doclet.see.class_or_package_not_accessible", tagName, containing.qualifiedName()); } else { configuration.getDocletSpecificMsg().warning( see.position(), "doclet.see.class_or_package_not_found", tagName, seetext); } } if (configuration.currentcd != containing) { refMemName = (refMem instanceof ConstructorDoc) ? refMemName : containing.name() + "." + refMemName; } if (refMem instanceof ExecutableMemberDoc) { if (refMemName.indexOf('(') < 0) { refMemName += ((ExecutableMemberDoc)refMem).signature(); } } text = plainOrCode(plain, new StringContent(refMemName)); return getDocLink(LinkInfoImpl.Kind.SEE_TAG, containing, refMem, (label.isEmpty() ? text: label), false); } } private Content plainOrCode(boolean plain, Content body) { return (plain || body.isEmpty()) ? body : HtmlTree.CODE(body); } /** * Add the inline comment. * * @param doc the doc for which the inline comment will be added * @param tag the inline tag to be added * @param htmltree the content tree to which the comment will be added */ public void addInlineComment(Doc doc, Tag tag, Content htmltree) { addCommentTags(doc, tag, tag.inlineTags(), false, false, htmltree); } /** * Add the inline deprecated comment. * * @param doc the doc for which the inline deprecated comment will be added * @param tag the inline tag to be added * @param htmltree the content tree to which the comment will be added */ public void addInlineDeprecatedComment(Doc doc, Tag tag, Content htmltree) { addCommentTags(doc, tag.inlineTags(), true, false, htmltree); } /** * Adds the summary content. * * @param doc the doc for which the summary will be generated * @param htmltree the documentation tree to which the summary will be added */ public void addSummaryComment(Doc doc, Content htmltree) { addSummaryComment(doc, doc.firstSentenceTags(), htmltree); } /** * Adds the summary content. * * @param doc the doc for which the summary will be generated * @param firstSentenceTags the first sentence tags for the doc * @param htmltree the documentation tree to which the summary will be added */ public void addSummaryComment(Doc doc, Tag[] firstSentenceTags, Content htmltree) { addCommentTags(doc, firstSentenceTags, false, true, htmltree); } public void addSummaryDeprecatedComment(Doc doc, Tag tag, Content htmltree) { addCommentTags(doc, tag.firstSentenceTags(), true, true, htmltree); } /** * Adds the inline comment. * * @param doc the doc for which the inline comments will be generated * @param htmltree the documentation tree to which the inline comments will be added */ public void addInlineComment(Doc doc, Content htmltree) { addCommentTags(doc, doc.inlineTags(), false, false, htmltree); } /** * Adds the comment tags. * * @param doc the doc for which the comment tags will be generated * @param tags the first sentence tags for the doc * @param depr true if it is deprecated * @param first true if the first sentence tags should be added * @param htmltree the documentation tree to which the comment tags will be added */ private void addCommentTags(Doc doc, Tag[] tags, boolean depr, boolean first, Content htmltree) { addCommentTags(doc, null, tags, depr, first, htmltree); } /** * Adds the comment tags. * * @param doc the doc for which the comment tags will be generated * @param holderTag the block tag context for the inline tags * @param tags the first sentence tags for the doc * @param depr true if it is deprecated * @param first true if the first sentence tags should be added * @param htmltree the documentation tree to which the comment tags will be added */ private void addCommentTags(Doc doc, Tag holderTag, Tag[] tags, boolean depr, boolean first, Content htmltree) { if(configuration.nocomment){ return; } Content div; Content result = commentTagsToContent(null, doc, tags, first); if (depr) { Content italic = HtmlTree.SPAN(HtmlStyle.deprecationComment, result); div = HtmlTree.DIV(HtmlStyle.block, italic); htmltree.addContent(div); } else { div = HtmlTree.DIV(HtmlStyle.block, result); htmltree.addContent(div); } if (tags.length == 0) { htmltree.addContent(getSpace()); } } /** * Converts inline tags and text to text strings, expanding the * inline tags along the way. Called wherever text can contain * an inline tag, such as in comments or in free-form text arguments * to non-inline tags. * * @param holderTag specific tag where comment resides * @param doc specific doc where comment resides * @param tags array of text tags and inline tags (often alternating) * present in the text of interest for this doc * @param isFirstSentence true if text is first sentence */ public Content commentTagsToContent(Tag holderTag, Doc doc, Tag[] tags, boolean isFirstSentence) { Content result = new ContentBuilder(); boolean textTagChange = false; // Array of all possible inline tags for this javadoc run configuration.tagletManager.checkTags(doc, tags, true); for (int i = 0; i < tags.length; i++) { Tag tagelem = tags[i]; String tagName = tagelem.name(); if (tagelem instanceof SeeTag) { result.addContent(seeTagToContent((SeeTag) tagelem)); } else if (! tagName.equals("Text")) { boolean wasEmpty = result.isEmpty(); Content output; if (configuration.docrootparent.length() > 0 && tagelem.name().equals("@docRoot") && ((tags[i + 1]).text()).startsWith("/..")) { // If Xdocrootparent switch ON, set the flag to remove the /.. occurrence after // {@docRoot} tag in the very next Text tag. textTagChange = true; // Replace the occurrence of {@docRoot}/.. with the absolute link. output = new StringContent(configuration.docrootparent); } else { output = TagletWriter.getInlineTagOuput( configuration.tagletManager, holderTag, tagelem, getTagletWriterInstance(isFirstSentence)); } if (output != null) result.addContent(output); if (wasEmpty && isFirstSentence && tagelem.name().equals("@inheritDoc") && !result.isEmpty()) { break; } else { continue; } } else { String text = tagelem.text(); //If Xdocrootparent switch ON, remove the /.. occurrence after {@docRoot} tag. if (textTagChange) { text = text.replaceFirst("/..", ""); textTagChange = false; } //This is just a regular text tag. The text may contain html links (<a>) //or inline tag {@docRoot}, which will be handled as special cases. text = redirectRelativeLinks(tagelem.holder(), text); // Replace @docRoot only if not represented by an instance of DocRootTaglet, // that is, only if it was not present in a source file doc comment. // This happens when inserted by the doclet (a few lines // above in this method). [It might also happen when passed in on the command // line as a text argument to an option (like -header).] text = replaceDocRootDir(text); if (isFirstSentence) { text = removeNonInlineHtmlTags(text); } text = utils.replaceTabs(configuration, text); text = utils.normalizeNewlines(text); result.addContent(new RawHtml(text)); } } return result; } /** * Return true if relative links should not be redirected. * * @return Return true if a relative link should not be redirected. */ private boolean shouldNotRedirectRelativeLinks() { return this instanceof AnnotationTypeWriter || this instanceof ClassWriter || this instanceof PackageSummaryWriter; } /** * Suppose a piece of documentation has a relative link. When you copy * that documentation to another place such as the index or class-use page, * that relative link will no longer work. We should redirect those links * so that they will work again. * <p> * Here is the algorithm used to fix the link: * <p> * {@literal <relative link> => docRoot + <relative path to file> + <relative link> } * <p> * For example, suppose com.sun.javadoc.RootDoc has this link: * {@literal <a href="package-summary.html">The package Page</a> } * <p> * If this link appeared in the index, we would redirect * the link like this: * * {@literal <a href="./com/sun/javadoc/package-summary.html">The package Page</a>} * * @param doc the Doc object whose documentation is being written. * @param text the text being written. * * @return the text, with all the relative links redirected to work. */ private String redirectRelativeLinks(Doc doc, String text) { if (doc == null || shouldNotRedirectRelativeLinks()) { return text; } DocPath redirectPathFromRoot; if (doc instanceof ClassDoc) { redirectPathFromRoot = DocPath.forPackage(((ClassDoc) doc).containingPackage()); } else if (doc instanceof MemberDoc) { redirectPathFromRoot = DocPath.forPackage(((MemberDoc) doc).containingPackage()); } else if (doc instanceof PackageDoc) { redirectPathFromRoot = DocPath.forPackage((PackageDoc) doc); } else { return text; } //Redirect all relative links. int end, begin = StringUtils.indexOfIgnoreCase(text, "<a"); if(begin >= 0){ StringBuilder textBuff = new StringBuilder(text); while(begin >=0){ if (textBuff.length() > begin + 2 && ! Character.isWhitespace(textBuff.charAt(begin+2))) { begin = StringUtils.indexOfIgnoreCase(textBuff.toString(), "<a", begin + 1); continue; } begin = textBuff.indexOf("=", begin) + 1; end = textBuff.indexOf(">", begin +1); if(begin == 0){ //Link has no equal symbol. configuration.root.printWarning( doc.position(), configuration.getText("doclet.malformed_html_link_tag", text)); break; } if (end == -1) { //Break without warning. This <a> tag is not necessarily malformed. The text //might be missing '>' character because the href has an inline tag. break; } String quote = textBuff.substring(begin, end); quote = quote.contains("\"") ? "\"" : quote.contains("\'") ? "\'" : null; if (quote != null) { begin = textBuff.indexOf(quote, begin) + 1; end = textBuff.indexOf(quote, begin +1); if (begin == 0 || end == -1){ //Link is missing a quote. break; } } String relativeLink = textBuff.substring(begin, end); String relativeLinkLowerCase = StringUtils.toLowerCase(relativeLink); if (!(relativeLinkLowerCase.startsWith("mailto:") || relativeLinkLowerCase.startsWith("http:") || relativeLinkLowerCase.startsWith("https:") || relativeLinkLowerCase.startsWith("file:"))) { relativeLink = "{@"+(new DocRootTaglet()).getName() + "}/" + redirectPathFromRoot.resolve(relativeLink).getPath(); textBuff.replace(begin, end, relativeLink); } begin = StringUtils.indexOfIgnoreCase(textBuff.toString(), "<a", begin + 1); } return textBuff.toString(); } return text; } static final Set<String> blockTags = new HashSet<>(); static { for (HtmlTag t: HtmlTag.values()) { if (t.blockType == HtmlTag.BlockType.BLOCK) blockTags.add(t.value); } } public static String removeNonInlineHtmlTags(String text) { final int len = text.length(); int startPos = 0; // start of text to copy int lessThanPos = text.indexOf('<'); // position of latest '<' if (lessThanPos < 0) { return text; } StringBuilder result = new StringBuilder(); main: while (lessThanPos != -1) { int currPos = lessThanPos + 1; if (currPos == len) break; char ch = text.charAt(currPos); if (ch == '/') { if (++currPos == len) break; ch = text.charAt(currPos); } int tagPos = currPos; while (isHtmlTagLetterOrDigit(ch)) { if (++currPos == len) break main; ch = text.charAt(currPos); } if (ch == '>' && blockTags.contains(StringUtils.toLowerCase(text.substring(tagPos, currPos)))) { result.append(text, startPos, lessThanPos); startPos = currPos + 1; } lessThanPos = text.indexOf('<', currPos); } result.append(text.substring(startPos)); return result.toString(); } private static boolean isHtmlTagLetterOrDigit(char ch) { return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || ('1' <= ch && ch <= '6'); } /** * Add a link to the stylesheet file. * * @param head the content tree to which the files will be added */ public void addStyleSheetProperties(Content head) { String stylesheetfile = configuration.stylesheetfile; DocPath stylesheet; if (stylesheetfile.isEmpty()) { stylesheet = DocPaths.STYLESHEET; } else { DocFile file = DocFile.createFileForInput(configuration, stylesheetfile); stylesheet = DocPath.create(file.getName()); } HtmlTree link = HtmlTree.LINK("stylesheet", "text/css", pathToRoot.resolve(stylesheet).getPath(), "Style"); head.addContent(link); if (configuration.createindex) { HtmlTree jq_link = HtmlTree.LINK("stylesheet", "text/css", pathToRoot.resolve(DocPaths.JQUERY_FILES.resolve(DocPaths.JQUERY_STYLESHEET_FILE)).getPath(), "Style"); head.addContent(jq_link); } } /** * Add a link to the JavaScript file. * * @param head the content tree to which the files will be added */ public void addScriptProperties(Content head) { HtmlTree javascript = HtmlTree.SCRIPT(pathToRoot.resolve(DocPaths.JAVASCRIPT).getPath()); head.addContent(javascript); if (configuration.createindex) { if (pathToRoot != null && script != null) { String path = pathToRoot.isEmpty() ? "." : pathToRoot.getPath(); script.addContent(new RawHtml("var pathtoroot = \"" + path + "/\";loadScripts(document, \'script\');")); } addJQueryFile(head, DocPaths.JSZIP_MIN); addJQueryFile(head, DocPaths.JSZIPUTILS_MIN); head.addContent(new RawHtml("<!--[if IE]>")); addJQueryFile(head, DocPaths.JSZIPUTILS_IE_MIN); head.addContent(new RawHtml("<![endif]-->")); addJQueryFile(head, DocPaths.JQUERY_JS_1_10); addJQueryFile(head, DocPaths.JQUERY_JS); } } /** * Add a link to the JQuery javascript file. * * @param head the content tree to which the files will be added * @param filePath the DocPath of the file that needs to be added */ private void addJQueryFile(Content head, DocPath filePath) { HtmlTree jqyeryScriptFile = HtmlTree.SCRIPT( pathToRoot.resolve(DocPaths.JQUERY_FILES.resolve(filePath)).getPath()); head.addContent(jqyeryScriptFile); } /** * According to * <cite>The Java™ Language Specification</cite>, * all the outer classes and static nested classes are core classes. */ public boolean isCoreClass(ClassDoc cd) { return cd.containingClass() == null || cd.isStatic(); } /** * Adds the annotatation types for the given packageDoc. * * @param packageDoc the package to write annotations for. * @param htmltree the documentation tree to which the annotation info will be * added */ public void addAnnotationInfo(PackageDoc packageDoc, Content htmltree) { addAnnotationInfo(packageDoc, packageDoc.annotations(), htmltree); } /** * Add the annotation types of the executable receiver. * * @param method the executable to write the receiver annotations for. * @param descList list of annotation description. * @param htmltree the documentation tree to which the annotation info will be * added */ public void addReceiverAnnotationInfo(ExecutableMemberDoc method, AnnotationDesc[] descList, Content htmltree) { addAnnotationInfo(0, method, descList, false, htmltree); } /** * Adds the annotatation types for the given doc. * * @param doc the package to write annotations for * @param htmltree the content tree to which the annotation types will be added */ public void addAnnotationInfo(ProgramElementDoc doc, Content htmltree) { addAnnotationInfo(doc, doc.annotations(), htmltree); } /** * Add the annotatation types for the given doc and parameter. * * @param indent the number of spaces to indent the parameters. * @param doc the doc to write annotations for. * @param param the parameter to write annotations for. * @param tree the content tree to which the annotation types will be added */ public boolean addAnnotationInfo(int indent, Doc doc, Parameter param, Content tree) { return addAnnotationInfo(indent, doc, param.annotations(), false, tree); } /** * Adds the annotatation types for the given doc. * * @param doc the doc to write annotations for. * @param descList the array of {@link AnnotationDesc}. * @param htmltree the documentation tree to which the annotation info will be * added */ private void addAnnotationInfo(Doc doc, AnnotationDesc[] descList, Content htmltree) { addAnnotationInfo(0, doc, descList, true, htmltree); } /** * Adds the annotation types for the given doc. * * @param indent the number of extra spaces to indent the annotations. * @param doc the doc to write annotations for. * @param descList the array of {@link AnnotationDesc}. * @param htmltree the documentation tree to which the annotation info will be * added */ private boolean addAnnotationInfo(int indent, Doc doc, AnnotationDesc[] descList, boolean lineBreak, Content htmltree) { List<Content> annotations = getAnnotations(indent, descList, lineBreak); String sep =""; if (annotations.isEmpty()) { return false; } for (Content annotation: annotations) { htmltree.addContent(sep); htmltree.addContent(annotation); if (!lineBreak) { sep = " "; } } return true; } /** * Return the string representations of the annotation types for * the given doc. * * @param indent the number of extra spaces to indent the annotations. * @param descList the array of {@link AnnotationDesc}. * @param linkBreak if true, add new line between each member value. * @return an array of strings representing the annotations being * documented. */ private List<Content> getAnnotations(int indent, AnnotationDesc[] descList, boolean linkBreak) { return getAnnotations(indent, descList, linkBreak, true); } /** * Return the string representations of the annotation types for * the given doc. * * A {@code null} {@code elementType} indicates that all the * annotations should be returned without any filtering. * * @param indent the number of extra spaces to indent the annotations. * @param descList the array of {@link AnnotationDesc}. * @param linkBreak if true, add new line between each member value. * @param elementType the type of targeted element (used for filtering * type annotations from declaration annotations) * @return an array of strings representing the annotations being * documented. */ public List<Content> getAnnotations(int indent, AnnotationDesc[] descList, boolean linkBreak, boolean isJava5DeclarationLocation) { List<Content> results = new ArrayList<>(); ContentBuilder annotation; for (AnnotationDesc aDesc : descList) { AnnotationTypeDoc annotationDoc = aDesc.annotationType(); // If an annotation is not documented, do not add it to the list. If // the annotation is of a repeatable type, and if it is not documented // and also if its container annotation is not documented, do not add it // to the list. If an annotation of a repeatable type is not documented // but its container is documented, it will be added to the list. if (!utils.isDocumentedAnnotation(annotationDoc) && (!isAnnotationDocumented && !isContainerDocumented)) { continue; } /* TODO: check logic here to correctly handle declaration * and type annotations. if (util.isDeclarationAnnotation(annotationDoc, isJava5DeclarationLocation)) { continue; }*/ annotation = new ContentBuilder(); isAnnotationDocumented = false; LinkInfoImpl linkInfo = new LinkInfoImpl(configuration, LinkInfoImpl.Kind.ANNOTATION, annotationDoc); AnnotationDesc.ElementValuePair[] pairs = aDesc.elementValues(); // If the annotation is synthesized, do not print the container. if (aDesc.isSynthesized()) { for (AnnotationDesc.ElementValuePair pair : pairs) { AnnotationValue annotationValue = pair.value(); List<AnnotationValue> annotationTypeValues = new ArrayList<>(); if (annotationValue.value() instanceof AnnotationValue[]) { AnnotationValue[] annotationArray = (AnnotationValue[]) annotationValue.value(); annotationTypeValues.addAll(Arrays.asList(annotationArray)); } else { annotationTypeValues.add(annotationValue); } String sep = ""; for (AnnotationValue av : annotationTypeValues) { annotation.addContent(sep); annotation.addContent(annotationValueToContent(av)); sep = " "; } } } else if (isAnnotationArray(pairs)) { // If the container has 1 or more value defined and if the // repeatable type annotation is not documented, do not print // the container. if (pairs.length == 1 && isAnnotationDocumented) { AnnotationValue[] annotationArray = (AnnotationValue[]) (pairs[0].value()).value(); List<AnnotationValue> annotationTypeValues = new ArrayList<>(); annotationTypeValues.addAll(Arrays.asList(annotationArray)); String sep = ""; for (AnnotationValue av : annotationTypeValues) { annotation.addContent(sep); annotation.addContent(annotationValueToContent(av)); sep = " "; } } // If the container has 1 or more value defined and if the // repeatable type annotation is not documented, print the container. else { addAnnotations(annotationDoc, linkInfo, annotation, pairs, indent, false); } } else { addAnnotations(annotationDoc, linkInfo, annotation, pairs, indent, linkBreak); } annotation.addContent(linkBreak ? DocletConstants.NL : ""); results.add(annotation); } return results; } /** * Add annotation to the annotation string. * * @param annotationDoc the annotation being documented * @param linkInfo the information about the link * @param annotation the annotation string to which the annotation will be added * @param pairs annotation type element and value pairs * @param indent the number of extra spaces to indent the annotations. * @param linkBreak if true, add new line between each member value */ private void addAnnotations(AnnotationTypeDoc annotationDoc, LinkInfoImpl linkInfo, ContentBuilder annotation, AnnotationDesc.ElementValuePair[] pairs, int indent, boolean linkBreak) { linkInfo.label = new StringContent("@" + annotationDoc.name()); annotation.addContent(getLink(linkInfo)); if (pairs.length > 0) { annotation.addContent("("); for (int j = 0; j < pairs.length; j++) { if (j > 0) { annotation.addContent(","); if (linkBreak) { annotation.addContent(DocletConstants.NL); int spaces = annotationDoc.name().length() + 2; for (int k = 0; k < (spaces + indent); k++) { annotation.addContent(" "); } } } annotation.addContent(getDocLink(LinkInfoImpl.Kind.ANNOTATION, pairs[j].element(), pairs[j].element().name(), false)); annotation.addContent("="); AnnotationValue annotationValue = pairs[j].value(); List<AnnotationValue> annotationTypeValues = new ArrayList<>(); if (annotationValue.value() instanceof AnnotationValue[]) { AnnotationValue[] annotationArray = (AnnotationValue[]) annotationValue.value(); annotationTypeValues.addAll(Arrays.asList(annotationArray)); } else { annotationTypeValues.add(annotationValue); } annotation.addContent(annotationTypeValues.size() == 1 ? "" : "{"); String sep = ""; for (AnnotationValue av : annotationTypeValues) { annotation.addContent(sep); annotation.addContent(annotationValueToContent(av)); sep = ","; } annotation.addContent(annotationTypeValues.size() == 1 ? "" : "}"); isContainerDocumented = false; } annotation.addContent(")"); } } /** * Check if the annotation contains an array of annotation as a value. This * check is to verify if a repeatable type annotation is present or not. * * @param pairs annotation type element and value pairs * * @return true if the annotation contains an array of annotation as a value. */ private boolean isAnnotationArray(AnnotationDesc.ElementValuePair[] pairs) { AnnotationValue annotationValue; for (AnnotationDesc.ElementValuePair pair : pairs) { annotationValue = pair.value(); if (annotationValue.value() instanceof AnnotationValue[]) { AnnotationValue[] annotationArray = (AnnotationValue[]) annotationValue.value(); if (annotationArray.length > 1) { if (annotationArray[0].value() instanceof AnnotationDesc) { AnnotationTypeDoc annotationDoc = ((AnnotationDesc) annotationArray[0].value()).annotationType(); isContainerDocumented = true; if (utils.isDocumentedAnnotation(annotationDoc)) { isAnnotationDocumented = true; } return true; } } } } return false; } private Content annotationValueToContent(AnnotationValue annotationValue) { if (annotationValue.value() instanceof Type) { Type type = (Type) annotationValue.value(); if (type.asClassDoc() != null) { LinkInfoImpl linkInfo = new LinkInfoImpl(configuration, LinkInfoImpl.Kind.ANNOTATION, type); linkInfo.label = new StringContent((type.asClassDoc().isIncluded() ? type.typeName() : type.qualifiedTypeName()) + type.dimension() + ".class"); return getLink(linkInfo); } else { return new StringContent(type.typeName() + type.dimension() + ".class"); } } else if (annotationValue.value() instanceof AnnotationDesc) { List<Content> list = getAnnotations(0, new AnnotationDesc[]{(AnnotationDesc) annotationValue.value()}, false); ContentBuilder buf = new ContentBuilder(); for (Content c: list) { buf.addContent(c); } return buf; } else if (annotationValue.value() instanceof MemberDoc) { return getDocLink(LinkInfoImpl.Kind.ANNOTATION, (MemberDoc) annotationValue.value(), ((MemberDoc) annotationValue.value()).name(), false); } else { return new StringContent(annotationValue.toString()); } } /** * Return the configuation for this doclet. * * @return the configuration for this doclet. */ public Configuration configuration() { return configuration; } }