1 /*
   2  * Copyright (c) 1998, 2012, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.tools.doclets.formats.html;
  27 
  28 import java.io.*;
  29 import java.text.SimpleDateFormat;
  30 import java.util.*;
  31 
  32 import com.sun.javadoc.*;
  33 import com.sun.tools.doclets.formats.html.markup.*;
  34 import com.sun.tools.doclets.internal.toolkit.*;
  35 import com.sun.tools.doclets.internal.toolkit.taglets.*;
  36 import com.sun.tools.doclets.internal.toolkit.util.*;
  37 
  38 /**
  39  * Class for the Html Format Code Generation specific to JavaDoc.
  40  * This Class contains methods related to the Html Code Generation which
  41  * are used extensively while generating the entire documentation.
  42  *
  43  *  <p><b>This is NOT part of any supported API.
  44  *  If you write code that depends on this, you do so at your own risk.
  45  *  This code and its internal interfaces are subject to change or
  46  *  deletion without notice.</b>
  47  *
  48  * @since 1.2
  49  * @author Atul M Dambalkar
  50  * @author Robert Field
  51  * @author Bhavesh Patel (Modified)
  52  */
  53 public class HtmlDocletWriter extends HtmlDocWriter {
  54 
  55     /**
  56      * Relative path from the file getting generated to the destination
  57      * directory. For example, if the file getting generated is
  58      * "java/lang/Object.html", then the path to the root is "../..".
  59      * This string can be empty if the file getting generated is in
  60      * the destination directory.
  61      */
  62     public final DocPath pathToRoot;
  63 
  64     /**
  65      * Platform-independent path from the current or the
  66      * destination directory to the file getting generated.
  67      * Used when creating the file.
  68      */
  69     public final DocPath path;
  70 
  71     /**
  72      * Name of the file getting generated. If the file getting generated is
  73      * "java/lang/Object.html", then the filename is "Object.html".
  74      */
  75     public final DocPath filename;
  76 
  77     /**
  78      * The display length used for indentation while generating the class page.
  79      */
  80     public int displayLength = 0;
  81 
  82     /**
  83      * The global configuration information for this run.
  84      */
  85     public final ConfigurationImpl configuration;
  86 
  87     /**
  88      * To check whether annotation heading is printed or not.
  89      */
  90     protected boolean printedAnnotationHeading = false;
  91 
  92     /**
  93      * To check whether the repeated annotations is documented or not.
  94      */
  95     private boolean isAnnotationDocumented = false;
  96 
  97     /**
  98      * To check whether the container annotations is documented or not.
  99      */
 100     private boolean isContainerDocumented = false;
 101 
 102     /**
 103      * Constructor to construct the HtmlStandardWriter object.
 104      *
 105      * @param path File to be generated.
 106      */
 107     public HtmlDocletWriter(ConfigurationImpl configuration, DocPath path)
 108             throws IOException {
 109         super(configuration, path);
 110         this.configuration = configuration;
 111         this.path = path;
 112         this.pathToRoot = path.parent().invert();
 113         this.filename = path.basename();
 114     }
 115 
 116     /**
 117      * Replace {&#064;docRoot} tag used in options that accept HTML text, such
 118      * as -header, -footer, -top and -bottom, and when converting a relative
 119      * HREF where commentTagsToString inserts a {&#064;docRoot} where one was
 120      * missing.  (Also see DocRootTaglet for {&#064;docRoot} tags in doc
 121      * comments.)
 122      * <p>
 123      * Replace {&#064;docRoot} tag in htmlstr with the relative path to the
 124      * destination directory from the directory where the file is being
 125      * written, looping to handle all such tags in htmlstr.
 126      * <p>
 127      * For example, for "-d docs" and -header containing {&#064;docRoot}, when
 128      * the HTML page for source file p/C1.java is being generated, the
 129      * {&#064;docRoot} tag would be inserted into the header as "../",
 130      * the relative path from docs/p/ to docs/ (the document root).
 131      * <p>
 132      * Note: This doc comment was written with '&amp;#064;' representing '@'
 133      * to prevent the inline tag from being interpreted.
 134      */
 135     public String replaceDocRootDir(String htmlstr) {
 136         // Return if no inline tags exist
 137         int index = htmlstr.indexOf("{@");
 138         if (index < 0) {
 139             return htmlstr;
 140         }
 141         String lowerHtml = htmlstr.toLowerCase();
 142         // Return index of first occurrence of {@docroot}
 143         // Note: {@docRoot} is not case sensitive when passed in w/command line option
 144         index = lowerHtml.indexOf("{@docroot}", index);
 145         if (index < 0) {
 146             return htmlstr;
 147         }
 148         StringBuilder buf = new StringBuilder();
 149         int previndex = 0;
 150         while (true) {
 151             if (configuration.docrootparent.length() > 0) {
 152                 final String docroot_parent = "{@docroot}/..";
 153                 // Search for lowercase version of {@docRoot}/..
 154                 index = lowerHtml.indexOf(docroot_parent, previndex);
 155                 // If next {@docRoot}/.. pattern not found, append rest of htmlstr and exit loop
 156                 if (index < 0) {
 157                     buf.append(htmlstr.substring(previndex));
 158                     break;
 159                 }
 160                 // If next {@docroot}/.. pattern found, append htmlstr up to start of tag
 161                 buf.append(htmlstr.substring(previndex, index));
 162                 previndex = index + docroot_parent.length();
 163                 // Insert docrootparent absolute path where {@docRoot}/.. was located
 164 
 165                 buf.append(configuration.docrootparent);
 166                 // Append slash if next character is not a slash
 167                 if (previndex < htmlstr.length() && htmlstr.charAt(previndex) != '/') {
 168                     buf.append('/');
 169                 }
 170             } else {
 171                 final String docroot = "{@docroot}";
 172                 // Search for lowercase version of {@docRoot}
 173                 index = lowerHtml.indexOf(docroot, previndex);
 174                 // If next {@docRoot} tag not found, append rest of htmlstr and exit loop
 175                 if (index < 0) {
 176                     buf.append(htmlstr.substring(previndex));
 177                     break;
 178                 }
 179                 // If next {@docroot} tag found, append htmlstr up to start of tag
 180                 buf.append(htmlstr.substring(previndex, index));
 181                 previndex = index + docroot.length();
 182                 // Insert relative path where {@docRoot} was located
 183                 buf.append(pathToRoot.isEmpty() ? "." : pathToRoot.getPath());
 184                 // Append slash if next character is not a slash
 185                 if (previndex < htmlstr.length() && htmlstr.charAt(previndex) != '/') {
 186                     buf.append('/');
 187                 }
 188             }
 189         }
 190         return buf.toString();
 191     }
 192 
 193     /**
 194      * Get the script to show or hide the All classes link.
 195      *
 196      * @param id id of the element to show or hide
 197      * @return a content tree for the script
 198      */
 199     public Content getAllClassesLinkScript(String id) {
 200         HtmlTree script = new HtmlTree(HtmlTag.SCRIPT);
 201         script.addAttr(HtmlAttr.TYPE, "text/javascript");
 202         String scriptCode = "<!--" + DocletConstants.NL +
 203                 "  allClassesLink = document.getElementById(\"" + id + "\");" + DocletConstants.NL +
 204                 "  if(window==top) {" + DocletConstants.NL +
 205                 "    allClassesLink.style.display = \"block\";" + DocletConstants.NL +
 206                 "  }" + DocletConstants.NL +
 207                 "  else {" + DocletConstants.NL +
 208                 "    allClassesLink.style.display = \"none\";" + DocletConstants.NL +
 209                 "  }" + DocletConstants.NL +
 210                 "  //-->" + DocletConstants.NL;
 211         Content scriptContent = new RawHtml(scriptCode);
 212         script.addContent(scriptContent);
 213         Content div = HtmlTree.DIV(script);
 214         return div;
 215     }
 216 
 217     /**
 218      * Add method information.
 219      *
 220      * @param method the method to be documented
 221      * @param dl the content tree to which the method information will be added
 222      */
 223     private void addMethodInfo(MethodDoc method, Content dl) {
 224         ClassDoc[] intfacs = method.containingClass().interfaces();
 225         MethodDoc overriddenMethod = method.overriddenMethod();
 226         // Check whether there is any implementation or overridden info to be
 227         // printed. If no overridden or implementation info needs to be
 228         // printed, do not print this section.
 229         if ((intfacs.length > 0 &&
 230                 new ImplementedMethods(method, this.configuration).build().length > 0) ||
 231                 overriddenMethod != null) {
 232             MethodWriterImpl.addImplementsInfo(this, method, dl);
 233             if (overriddenMethod != null) {
 234                 MethodWriterImpl.addOverridden(this,
 235                         method.overriddenType(), overriddenMethod, dl);
 236             }
 237         }
 238     }
 239 
 240     /**
 241      * Adds the tags information.
 242      *
 243      * @param doc the doc for which the tags will be generated
 244      * @param htmltree the documentation tree to which the tags will be added
 245      */
 246     protected void addTagsInfo(Doc doc, Content htmltree) {
 247         if (configuration.nocomment) {
 248             return;
 249         }
 250         Content dl = new HtmlTree(HtmlTag.DL);
 251         if (doc instanceof MethodDoc) {
 252             addMethodInfo((MethodDoc) doc, dl);
 253         }
 254         TagletOutputImpl output = new TagletOutputImpl("");
 255         TagletWriter.genTagOuput(configuration.tagletManager, doc,
 256             configuration.tagletManager.getCustomTags(doc),
 257                 getTagletWriterInstance(false), output);
 258         String outputString = output.toString().trim();
 259         if (!outputString.isEmpty()) {
 260             Content resultString = new RawHtml(outputString);
 261             dl.addContent(resultString);
 262         }
 263         htmltree.addContent(dl);
 264     }
 265 
 266     /**
 267      * Check whether there are any tags for Serialization Overview
 268      * section to be printed.
 269      *
 270      * @param field the FieldDoc object to check for tags.
 271      * @return true if there are tags to be printed else return false.
 272      */
 273     protected boolean hasSerializationOverviewTags(FieldDoc field) {
 274         TagletOutputImpl output = new TagletOutputImpl("");
 275         TagletWriter.genTagOuput(configuration.tagletManager, field,
 276             configuration.tagletManager.getCustomTags(field),
 277                 getTagletWriterInstance(false), output);
 278         return (!output.toString().trim().isEmpty());
 279     }
 280 
 281     /**
 282      * Returns a TagletWriter that knows how to write HTML.
 283      *
 284      * @return a TagletWriter that knows how to write HTML.
 285      */
 286     public TagletWriter getTagletWriterInstance(boolean isFirstSentence) {
 287         return new TagletWriterImpl(this, isFirstSentence);
 288     }
 289 
 290     /**
 291      * Get Package link, with target frame.
 292      *
 293      * @param pd The link will be to the "package-summary.html" page for this package
 294      * @param target name of the target frame
 295      * @param label tag for the link
 296      * @return a content for the target package link
 297      */
 298     public Content getTargetPackageLink(PackageDoc pd, String target,
 299             Content label) {
 300         return getHyperLink(pathString(pd, DocPaths.PACKAGE_SUMMARY), label, "", target);
 301     }
 302 
 303     /**
 304      * Generates the HTML document tree and prints it out.
 305      *
 306      * @param metakeywords Array of String keywords for META tag. Each element
 307      *                     of the array is assigned to a separate META tag.
 308      *                     Pass in null for no array
 309      * @param includeScript true if printing windowtitle script
 310      *                      false for files that appear in the left-hand frames
 311      * @param body the body htmltree to be included in the document
 312      */
 313     public void printHtmlDocument(String[] metakeywords, boolean includeScript,
 314             Content body) throws IOException {
 315         Content htmlDocType = DocType.TRANSITIONAL;
 316         Content htmlComment = new Comment(configuration.getText("doclet.New_Page"));
 317         Content head = new HtmlTree(HtmlTag.HEAD);
 318         if (!configuration.notimestamp) {
 319             Content headComment = new Comment(getGeneratedByString());
 320             head.addContent(headComment);
 321         }
 322         if (configuration.charset.length() > 0) {
 323             Content meta = HtmlTree.META("Content-Type", "text/html",
 324                     configuration.charset);
 325             head.addContent(meta);
 326         }
 327         head.addContent(getTitle());
 328         if (!configuration.notimestamp) {
 329             SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
 330             Content meta = HtmlTree.META("date", dateFormat.format(new Date()));
 331             head.addContent(meta);
 332         }
 333         if (metakeywords != null) {
 334             for (int i=0; i < metakeywords.length; i++) {
 335                 Content meta = HtmlTree.META("keywords", metakeywords[i]);
 336                 head.addContent(meta);
 337             }
 338         }
 339         head.addContent(getStyleSheetProperties());
 340         head.addContent(getScriptProperties());
 341         Content htmlTree = HtmlTree.HTML(configuration.getLocale().getLanguage(),
 342                 head, body);
 343         Content htmlDocument = new HtmlDocument(htmlDocType,
 344                 htmlComment, htmlTree);
 345         write(htmlDocument);
 346     }
 347 
 348     /**
 349      * Get the window title.
 350      *
 351      * @param title the title string to construct the complete window title
 352      * @return the window title string
 353      */
 354     public String getWindowTitle(String title) {
 355         if (configuration.windowtitle.length() > 0) {
 356             title += " (" + configuration.windowtitle  + ")";
 357         }
 358         return title;
 359     }
 360 
 361     /**
 362      * Get user specified header and the footer.
 363      *
 364      * @param header if true print the user provided header else print the
 365      * user provided footer.
 366      */
 367     public Content getUserHeaderFooter(boolean header) {
 368         String content;
 369         if (header) {
 370             content = replaceDocRootDir(configuration.header);
 371         } else {
 372             if (configuration.footer.length() != 0) {
 373                 content = replaceDocRootDir(configuration.footer);
 374             } else {
 375                 content = replaceDocRootDir(configuration.header);
 376             }
 377         }
 378         Content rawContent = new RawHtml(content);
 379         Content em = HtmlTree.EM(rawContent);
 380         return em;
 381     }
 382 
 383     /**
 384      * Adds the user specified top.
 385      *
 386      * @param body the content tree to which user specified top will be added
 387      */
 388     public void addTop(Content body) {
 389         Content top = new RawHtml(replaceDocRootDir(configuration.top));
 390         body.addContent(top);
 391     }
 392 
 393     /**
 394      * Adds the user specified bottom.
 395      *
 396      * @param body the content tree to which user specified bottom will be added
 397      */
 398     public void addBottom(Content body) {
 399         Content bottom = new RawHtml(replaceDocRootDir(configuration.bottom));
 400         Content small = HtmlTree.SMALL(bottom);
 401         Content p = HtmlTree.P(HtmlStyle.legalCopy, small);
 402         body.addContent(p);
 403     }
 404 
 405     /**
 406      * Adds the navigation bar for the Html page at the top and and the bottom.
 407      *
 408      * @param header If true print navigation bar at the top of the page else
 409      * @param body the HtmlTree to which the nav links will be added
 410      */
 411     protected void addNavLinks(boolean header, Content body) {
 412         if (!configuration.nonavbar) {
 413             String allClassesId = "allclasses_";
 414             HtmlTree navDiv = new HtmlTree(HtmlTag.DIV);
 415             if (header) {
 416                 body.addContent(HtmlConstants.START_OF_TOP_NAVBAR);
 417                 navDiv.addStyle(HtmlStyle.topNav);
 418                 allClassesId += "navbar_top";
 419                 Content a = getMarkerAnchor("navbar_top");
 420                 navDiv.addContent(a);
 421                 Content skipLinkContent = getHyperLink(DocLink.fragment("skip-navbar_top"),
 422                         HtmlTree.EMPTY,
 423                         configuration.getText("doclet.Skip_navigation_links"),
 424                         "");
 425                 navDiv.addContent(skipLinkContent);
 426             } else {
 427                 body.addContent(HtmlConstants.START_OF_BOTTOM_NAVBAR);
 428                 navDiv.addStyle(HtmlStyle.bottomNav);
 429                 allClassesId += "navbar_bottom";
 430                 Content a = getMarkerAnchor("navbar_bottom");
 431                 navDiv.addContent(a);
 432                 Content skipLinkContent = getHyperLink(DocLink.fragment("skip-navbar_bottom"),
 433                         HtmlTree.EMPTY,
 434                         configuration.getText("doclet.Skip_navigation_links"),
 435                         "");
 436                 navDiv.addContent(skipLinkContent);
 437             }
 438             if (header) {
 439                 navDiv.addContent(getMarkerAnchor("navbar_top_firstrow"));
 440             } else {
 441                 navDiv.addContent(getMarkerAnchor("navbar_bottom_firstrow"));
 442             }
 443             HtmlTree navList = new HtmlTree(HtmlTag.UL);
 444             navList.addStyle(HtmlStyle.navList);
 445             navList.addAttr(HtmlAttr.TITLE, "Navigation");
 446             if (configuration.createoverview) {
 447                 navList.addContent(getNavLinkContents());
 448             }
 449             if (configuration.packages.length == 1) {
 450                 navList.addContent(getNavLinkPackage(configuration.packages[0]));
 451             } else if (configuration.packages.length > 1) {
 452                 navList.addContent(getNavLinkPackage());
 453             }
 454             navList.addContent(getNavLinkClass());
 455             if(configuration.classuse) {
 456                 navList.addContent(getNavLinkClassUse());
 457             }
 458             if(configuration.createtree) {
 459                 navList.addContent(getNavLinkTree());
 460             }
 461             if(!(configuration.nodeprecated ||
 462                      configuration.nodeprecatedlist)) {
 463                 navList.addContent(getNavLinkDeprecated());
 464             }
 465             if(configuration.createindex) {
 466                 navList.addContent(getNavLinkIndex());
 467             }
 468             if (!configuration.nohelp) {
 469                 navList.addContent(getNavLinkHelp());
 470             }
 471             navDiv.addContent(navList);
 472             Content aboutDiv = HtmlTree.DIV(HtmlStyle.aboutLanguage, getUserHeaderFooter(header));
 473             navDiv.addContent(aboutDiv);
 474             body.addContent(navDiv);
 475             Content ulNav = HtmlTree.UL(HtmlStyle.navList, getNavLinkPrevious());
 476             ulNav.addContent(getNavLinkNext());
 477             Content subDiv = HtmlTree.DIV(HtmlStyle.subNav, ulNav);
 478             Content ulFrames = HtmlTree.UL(HtmlStyle.navList, getNavShowLists());
 479             ulFrames.addContent(getNavHideLists(filename));
 480             subDiv.addContent(ulFrames);
 481             HtmlTree ulAllClasses = HtmlTree.UL(HtmlStyle.navList, getNavLinkClassIndex());
 482             ulAllClasses.addAttr(HtmlAttr.ID, allClassesId.toString());
 483             subDiv.addContent(ulAllClasses);
 484             subDiv.addContent(getAllClassesLinkScript(allClassesId.toString()));
 485             addSummaryDetailLinks(subDiv);
 486             if (header) {
 487                 subDiv.addContent(getMarkerAnchor("skip-navbar_top"));
 488                 body.addContent(subDiv);
 489                 body.addContent(HtmlConstants.END_OF_TOP_NAVBAR);
 490             } else {
 491                 subDiv.addContent(getMarkerAnchor("skip-navbar_bottom"));
 492                 body.addContent(subDiv);
 493                 body.addContent(HtmlConstants.END_OF_BOTTOM_NAVBAR);
 494             }
 495         }
 496     }
 497 
 498     /**
 499      * Get the word "NEXT" to indicate that no link is available.  Override
 500      * this method to customize next link.
 501      *
 502      * @return a content tree for the link
 503      */
 504     protected Content getNavLinkNext() {
 505         return getNavLinkNext(null);
 506     }
 507 
 508     /**
 509      * Get the word "PREV" to indicate that no link is available.  Override
 510      * this method to customize prev link.
 511      *
 512      * @return a content tree for the link
 513      */
 514     protected Content getNavLinkPrevious() {
 515         return getNavLinkPrevious(null);
 516     }
 517 
 518     /**
 519      * Do nothing. This is the default method.
 520      */
 521     protected void addSummaryDetailLinks(Content navDiv) {
 522     }
 523 
 524     /**
 525      * Get link to the "overview-summary.html" page.
 526      *
 527      * @return a content tree for the link
 528      */
 529     protected Content getNavLinkContents() {
 530         Content linkContent = getHyperLink(pathToRoot.resolve(DocPaths.OVERVIEW_SUMMARY),
 531                 overviewLabel, "", "");
 532         Content li = HtmlTree.LI(linkContent);
 533         return li;
 534     }
 535 
 536     /**
 537      * Get link to the "package-summary.html" page for the package passed.
 538      *
 539      * @param pkg Package to which link will be generated
 540      * @return a content tree for the link
 541      */
 542     protected Content getNavLinkPackage(PackageDoc pkg) {
 543         Content linkContent = getPackageLink(pkg,
 544                 packageLabel);
 545         Content li = HtmlTree.LI(linkContent);
 546         return li;
 547     }
 548 
 549     /**
 550      * Get the word "Package" , to indicate that link is not available here.
 551      *
 552      * @return a content tree for the link
 553      */
 554     protected Content getNavLinkPackage() {
 555         Content li = HtmlTree.LI(packageLabel);
 556         return li;
 557     }
 558 
 559     /**
 560      * Get the word "Use", to indicate that link is not available.
 561      *
 562      * @return a content tree for the link
 563      */
 564     protected Content getNavLinkClassUse() {
 565         Content li = HtmlTree.LI(useLabel);
 566         return li;
 567     }
 568 
 569     /**
 570      * Get link for previous file.
 571      *
 572      * @param prev File name for the prev link
 573      * @return a content tree for the link
 574      */
 575     public Content getNavLinkPrevious(DocPath prev) {
 576         Content li;
 577         if (prev != null) {
 578             li = HtmlTree.LI(getHyperLink(prev, prevLabel, "", ""));
 579         }
 580         else
 581             li = HtmlTree.LI(prevLabel);
 582         return li;
 583     }
 584 
 585     /**
 586      * Get link for next file.  If next is null, just print the label
 587      * without linking it anywhere.
 588      *
 589      * @param next File name for the next link
 590      * @return a content tree for the link
 591      */
 592     public Content getNavLinkNext(DocPath next) {
 593         Content li;
 594         if (next != null) {
 595             li = HtmlTree.LI(getHyperLink(next, nextLabel, "", ""));
 596         }
 597         else
 598             li = HtmlTree.LI(nextLabel);
 599         return li;
 600     }
 601 
 602     /**
 603      * Get "FRAMES" link, to switch to the frame version of the output.
 604      *
 605      * @param link File to be linked, "index.html"
 606      * @return a content tree for the link
 607      */
 608     protected Content getNavShowLists(DocPath link) {
 609         DocLink dl = new DocLink(link, path.getPath(), null);
 610         Content framesContent = getHyperLink(dl, framesLabel, "", "_top");
 611         Content li = HtmlTree.LI(framesContent);
 612         return li;
 613     }
 614 
 615     /**
 616      * Get "FRAMES" link, to switch to the frame version of the output.
 617      *
 618      * @return a content tree for the link
 619      */
 620     protected Content getNavShowLists() {
 621         return getNavShowLists(pathToRoot.resolve(DocPaths.INDEX));
 622     }
 623 
 624     /**
 625      * Get "NO FRAMES" link, to switch to the non-frame version of the output.
 626      *
 627      * @param link File to be linked
 628      * @return a content tree for the link
 629      */
 630     protected Content getNavHideLists(DocPath link) {
 631         Content noFramesContent = getHyperLink(link, noframesLabel, "", "_top");
 632         Content li = HtmlTree.LI(noFramesContent);
 633         return li;
 634     }
 635 
 636     /**
 637      * Get "Tree" link in the navigation bar. If there is only one package
 638      * specified on the command line, then the "Tree" link will be to the
 639      * only "package-tree.html" file otherwise it will be to the
 640      * "overview-tree.html" file.
 641      *
 642      * @return a content tree for the link
 643      */
 644     protected Content getNavLinkTree() {
 645         Content treeLinkContent;
 646         PackageDoc[] packages = configuration.root.specifiedPackages();
 647         if (packages.length == 1 && configuration.root.specifiedClasses().length == 0) {
 648             treeLinkContent = getHyperLink(pathString(packages[0],
 649                     DocPaths.PACKAGE_TREE), treeLabel,
 650                     "", "");
 651         } else {
 652             treeLinkContent = getHyperLink(pathToRoot.resolve(DocPaths.OVERVIEW_TREE),
 653                     treeLabel, "", "");
 654         }
 655         Content li = HtmlTree.LI(treeLinkContent);
 656         return li;
 657     }
 658 
 659     /**
 660      * Get the overview tree link for the main tree.
 661      *
 662      * @param label the label for the link
 663      * @return a content tree for the link
 664      */
 665     protected Content getNavLinkMainTree(String label) {
 666         Content mainTreeContent = getHyperLink(pathToRoot.resolve(DocPaths.OVERVIEW_TREE),
 667                 new StringContent(label));
 668         Content li = HtmlTree.LI(mainTreeContent);
 669         return li;
 670     }
 671 
 672     /**
 673      * Get the word "Class", to indicate that class link is not available.
 674      *
 675      * @return a content tree for the link
 676      */
 677     protected Content getNavLinkClass() {
 678         Content li = HtmlTree.LI(classLabel);
 679         return li;
 680     }
 681 
 682     /**
 683      * Get "Deprecated" API link in the navigation bar.
 684      *
 685      * @return a content tree for the link
 686      */
 687     protected Content getNavLinkDeprecated() {
 688         Content linkContent = getHyperLink(pathToRoot.resolve(DocPaths.DEPRECATED_LIST),
 689                 deprecatedLabel, "", "");
 690         Content li = HtmlTree.LI(linkContent);
 691         return li;
 692     }
 693 
 694     /**
 695      * Get link for generated index. If the user has used "-splitindex"
 696      * command line option, then link to file "index-files/index-1.html" is
 697      * generated otherwise link to file "index-all.html" is generated.
 698      *
 699      * @return a content tree for the link
 700      */
 701     protected Content getNavLinkClassIndex() {
 702         Content allClassesContent = getHyperLink(pathToRoot.resolve(
 703                 DocPaths.ALLCLASSES_NOFRAME),
 704                 allclassesLabel, "", "");
 705         Content li = HtmlTree.LI(allClassesContent);
 706         return li;
 707     }
 708 
 709     /**
 710      * Get link for generated class index.
 711      *
 712      * @return a content tree for the link
 713      */
 714     protected Content getNavLinkIndex() {
 715         Content linkContent = getHyperLink(pathToRoot.resolve(
 716                 (configuration.splitindex
 717                     ? DocPaths.INDEX_FILES.resolve(DocPaths.indexN(1))
 718                     : DocPaths.INDEX_ALL)),
 719             indexLabel, "", "");
 720         Content li = HtmlTree.LI(linkContent);
 721         return li;
 722     }
 723 
 724     /**
 725      * Get help file link. If user has provided a help file, then generate a
 726      * link to the user given file, which is already copied to current or
 727      * destination directory.
 728      *
 729      * @return a content tree for the link
 730      */
 731     protected Content getNavLinkHelp() {
 732         String helpfile = configuration.helpfile;
 733         DocPath helpfilenm;
 734         if (helpfile.isEmpty()) {
 735             helpfilenm = DocPaths.HELP_DOC;
 736         } else {
 737             DocFile file = DocFile.createFileForInput(configuration, helpfile);
 738             helpfilenm = DocPath.create(file.getName());
 739         }
 740         Content linkContent = getHyperLink(pathToRoot.resolve(helpfilenm),
 741                 helpLabel, "", "");
 742         Content li = HtmlTree.LI(linkContent);
 743         return li;
 744     }
 745 
 746     /**
 747      * Get summary table header.
 748      *
 749      * @param header the header for the table
 750      * @param scope the scope of the headers
 751      * @return a content tree for the header
 752      */
 753     public Content getSummaryTableHeader(String[] header, String scope) {
 754         Content tr = new HtmlTree(HtmlTag.TR);
 755         int size = header.length;
 756         Content tableHeader;
 757         if (size == 1) {
 758             tableHeader = new StringContent(header[0]);
 759             tr.addContent(HtmlTree.TH(HtmlStyle.colOne, scope, tableHeader));
 760             return tr;
 761         }
 762         for (int i = 0; i < size; i++) {
 763             tableHeader = new StringContent(header[i]);
 764             if(i == 0)
 765                 tr.addContent(HtmlTree.TH(HtmlStyle.colFirst, scope, tableHeader));
 766             else if(i == (size - 1))
 767                 tr.addContent(HtmlTree.TH(HtmlStyle.colLast, scope, tableHeader));
 768             else
 769                 tr.addContent(HtmlTree.TH(scope, tableHeader));
 770         }
 771         return tr;
 772     }
 773 
 774     /**
 775      * Get table caption.
 776      *
 777      * @param rawText the caption for the table which could be raw Html
 778      * @return a content tree for the caption
 779      */
 780     public Content getTableCaption(String rawText) {
 781         Content title = new RawHtml(rawText);
 782         Content captionSpan = HtmlTree.SPAN(title);
 783         Content space = getSpace();
 784         Content tabSpan = HtmlTree.SPAN(HtmlStyle.tabEnd, space);
 785         Content caption = HtmlTree.CAPTION(captionSpan);
 786         caption.addContent(tabSpan);
 787         return caption;
 788     }
 789 
 790     /**
 791      * Get the marker anchor which will be added to the documentation tree.
 792      *
 793      * @param anchorName the anchor name attribute
 794      * @return a content tree for the marker anchor
 795      */
 796     public Content getMarkerAnchor(String anchorName) {
 797         return getMarkerAnchor(anchorName, null);
 798     }
 799 
 800     /**
 801      * Get the marker anchor which will be added to the documentation tree.
 802      *
 803      * @param anchorName the anchor name attribute
 804      * @param anchorContent the content that should be added to the anchor
 805      * @return a content tree for the marker anchor
 806      */
 807     public Content getMarkerAnchor(String anchorName, Content anchorContent) {
 808         if (anchorContent == null)
 809             anchorContent = new Comment(" ");
 810         Content markerAnchor = HtmlTree.A_NAME(anchorName, anchorContent);
 811         return markerAnchor;
 812     }
 813 
 814     /**
 815      * Returns a packagename content.
 816      *
 817      * @param packageDoc the package to check
 818      * @return package name content
 819      */
 820     public Content getPackageName(PackageDoc packageDoc) {
 821         return packageDoc == null || packageDoc.name().length() == 0 ?
 822             defaultPackageLabel :
 823             getPackageLabel(packageDoc.name());
 824     }
 825 
 826     /**
 827      * Returns a package name label.
 828      *
 829      * @param packageName the package name
 830      * @return the package name content
 831      */
 832     public Content getPackageLabel(String packageName) {
 833         return new StringContent(packageName);
 834     }
 835 
 836     /**
 837      * Add package deprecation information to the documentation tree
 838      *
 839      * @param deprPkgs list of deprecated packages
 840      * @param headingKey the caption for the deprecated package table
 841      * @param tableSummary the summary for the deprecated package table
 842      * @param tableHeader table headers for the deprecated package table
 843      * @param contentTree the content tree to which the deprecated package table will be added
 844      */
 845     protected void addPackageDeprecatedAPI(List<Doc> deprPkgs, String headingKey,
 846             String tableSummary, String[] tableHeader, Content contentTree) {
 847         if (deprPkgs.size() > 0) {
 848             Content table = HtmlTree.TABLE(0, 3, 0, tableSummary,
 849                     getTableCaption(configuration.getText(headingKey)));
 850             table.addContent(getSummaryTableHeader(tableHeader, "col"));
 851             Content tbody = new HtmlTree(HtmlTag.TBODY);
 852             for (int i = 0; i < deprPkgs.size(); i++) {
 853                 PackageDoc pkg = (PackageDoc) deprPkgs.get(i);
 854                 HtmlTree td = HtmlTree.TD(HtmlStyle.colOne,
 855                         getPackageLink(pkg, getPackageName(pkg)));
 856                 if (pkg.tags("deprecated").length > 0) {
 857                     addInlineDeprecatedComment(pkg, pkg.tags("deprecated")[0], td);
 858                 }
 859                 HtmlTree tr = HtmlTree.TR(td);
 860                 if (i % 2 == 0) {
 861                     tr.addStyle(HtmlStyle.altColor);
 862                 } else {
 863                     tr.addStyle(HtmlStyle.rowColor);
 864                 }
 865                 tbody.addContent(tr);
 866             }
 867             table.addContent(tbody);
 868             Content li = HtmlTree.LI(HtmlStyle.blockList, table);
 869             Content ul = HtmlTree.UL(HtmlStyle.blockList, li);
 870             contentTree.addContent(ul);
 871         }
 872     }
 873 
 874     /**
 875      * Return the path to the class page for a classdoc.
 876      *
 877      * @param cd   Class to which the path is requested.
 878      * @param name Name of the file(doesn't include path).
 879      */
 880     protected DocPath pathString(ClassDoc cd, DocPath name) {
 881         return pathString(cd.containingPackage(), name);
 882     }
 883 
 884     /**
 885      * Return path to the given file name in the given package. So if the name
 886      * passed is "Object.html" and the name of the package is "java.lang", and
 887      * if the relative path is "../.." then returned string will be
 888      * "../../java/lang/Object.html"
 889      *
 890      * @param pd Package in which the file name is assumed to be.
 891      * @param name File name, to which path string is.
 892      */
 893     protected DocPath pathString(PackageDoc pd, DocPath name) {
 894         return pathToRoot.resolve(DocPath.forPackage(pd).resolve(name));
 895     }
 896 
 897     /**
 898      * Return the link to the given package.
 899      *
 900      * @param pkg the package to link to.
 901      * @param label the label for the link.
 902      * @param isStrong true if the label should be strong.
 903      * @return the link to the given package.
 904      */
 905     public String getPackageLinkString(PackageDoc pkg, String label,
 906                                  boolean isStrong) {
 907         return getPackageLinkString(pkg, label, isStrong, "");
 908     }
 909 
 910     /**
 911      * Return the link to the given package.
 912      *
 913      * @param pkg the package to link to.
 914      * @param label the label for the link.
 915      * @param isStrong true if the label should be strong.
 916      * @param style  the font of the package link label.
 917      * @return the link to the given package.
 918      */
 919     public String getPackageLinkString(PackageDoc pkg, String label, boolean isStrong,
 920             String style) {
 921         boolean included = pkg != null && pkg.isIncluded();
 922         if (! included) {
 923             PackageDoc[] packages = configuration.packages;
 924             for (int i = 0; i < packages.length; i++) {
 925                 if (packages[i].equals(pkg)) {
 926                     included = true;
 927                     break;
 928                 }
 929             }
 930         }
 931         if (included || pkg == null) {
 932             return getHyperLinkString(pathString(pkg, DocPaths.PACKAGE_SUMMARY),
 933                                 label, isStrong, style);
 934         } else {
 935             DocLink crossPkgLink = getCrossPackageLink(Util.getPackageName(pkg));
 936             if (crossPkgLink != null) {
 937                 return getHyperLinkString(crossPkgLink, label, isStrong, style);
 938             } else {
 939                 return label;
 940             }
 941         }
 942     }
 943 
 944     /**
 945      * Return the link to the given package.
 946      *
 947      * @param pkg the package to link to.
 948      * @param label the label for the link.
 949      * @return a content tree for the package link.
 950      */
 951     public Content getPackageLink(PackageDoc pkg, Content label) {
 952         boolean included = pkg != null && pkg.isIncluded();
 953         if (! included) {
 954             PackageDoc[] packages = configuration.packages;
 955             for (int i = 0; i < packages.length; i++) {
 956                 if (packages[i].equals(pkg)) {
 957                     included = true;
 958                     break;
 959                 }
 960             }
 961         }
 962         if (included || pkg == null) {
 963             return getHyperLink(pathString(pkg, DocPaths.PACKAGE_SUMMARY),
 964                     label);
 965         } else {
 966             DocLink crossPkgLink = getCrossPackageLink(Util.getPackageName(pkg));
 967             if (crossPkgLink != null) {
 968                 return getHyperLink(crossPkgLink, label);
 969             } else {
 970                 return label;
 971             }
 972         }
 973     }
 974 
 975     public String italicsClassName(ClassDoc cd, boolean qual) {
 976         String name = (qual)? cd.qualifiedName(): cd.name();
 977         return (cd.isInterface())?  italicsText(name): name;
 978     }
 979 
 980     /**
 981      * Add the link to the content tree.
 982      *
 983      * @param doc program element doc for which the link will be added
 984      * @param label label for the link
 985      * @param htmltree the content tree to which the link will be added
 986      */
 987     public void addSrcLink(ProgramElementDoc doc, Content label, Content htmltree) {
 988         if (doc == null) {
 989             return;
 990         }
 991         ClassDoc cd = doc.containingClass();
 992         if (cd == null) {
 993             //d must be a class doc since in has no containing class.
 994             cd = (ClassDoc) doc;
 995         }
 996         DocPath href = pathToRoot
 997                 .resolve(DocPaths.SOURCE_OUTPUT)
 998                 .resolve(DocPath.forClass(cd));
 999         Content linkContent = getHyperLink(href.fragment(SourceToHTMLConverter.getAnchorName(doc)), label, "", "");
1000         htmltree.addContent(linkContent);
1001     }
1002 
1003     /**
1004      * Return the link to the given class.
1005      *
1006      * @param linkInfo the information about the link.
1007      *
1008      * @return the link for the given class.
1009      */
1010     public String getLink(LinkInfoImpl linkInfo) {
1011         LinkFactoryImpl factory = new LinkFactoryImpl(this);
1012         String link = factory.getLinkOutput(linkInfo).toString();
1013         displayLength += linkInfo.displayLength;
1014         return link;
1015     }
1016 
1017     /**
1018      * Return the type parameters for the given class.
1019      *
1020      * @param linkInfo the information about the link.
1021      * @return the type for the given class.
1022      */
1023     public String getTypeParameterLinks(LinkInfoImpl linkInfo) {
1024         LinkFactoryImpl factory = new LinkFactoryImpl(this);
1025         return factory.getTypeParameterLinks(linkInfo, false).toString();
1026     }
1027 
1028     /*************************************************************
1029      * Return a class cross link to external class documentation.
1030      * The name must be fully qualified to determine which package
1031      * the class is in.  The -link option does not allow users to
1032      * link to external classes in the "default" package.
1033      *
1034      * @param qualifiedClassName the qualified name of the external class.
1035      * @param refMemName the name of the member being referenced.  This should
1036      * be null or empty string if no member is being referenced.
1037      * @param label the label for the external link.
1038      * @param strong true if the link should be strong.
1039      * @param style the style of the link.
1040      * @param code true if the label should be code font.
1041      */
1042     public String getCrossClassLink(String qualifiedClassName, String refMemName,
1043                                     String label, boolean strong, String style,
1044                                     boolean code) {
1045         String className = "";
1046         String packageName = qualifiedClassName == null ? "" : qualifiedClassName;
1047         int periodIndex;
1048         while ((periodIndex = packageName.lastIndexOf('.')) != -1) {
1049             className = packageName.substring(periodIndex + 1, packageName.length()) +
1050                 (className.length() > 0 ? "." + className : "");
1051             String defaultLabel = code ? codeText(className) : className;
1052             packageName = packageName.substring(0, periodIndex);
1053             if (getCrossPackageLink(packageName) != null) {
1054                 //The package exists in external documentation, so link to the external
1055                 //class (assuming that it exists).  This is definitely a limitation of
1056                 //the -link option.  There are ways to determine if an external package
1057                 //exists, but no way to determine if the external class exists.  We just
1058                 //have to assume that it does.
1059                 DocLink link = configuration.extern.getExternalLink(packageName, pathToRoot,
1060                                 className + ".html", refMemName);
1061                 return getHyperLinkString(link,
1062                     (label == null) || label.length() == 0 ? defaultLabel : label,
1063 
1064 
1065                     strong, style,
1066                     configuration.getText("doclet.Href_Class_Or_Interface_Title", packageName),
1067                     "");
1068             }
1069         }
1070         return null;
1071     }
1072 
1073     public boolean isClassLinkable(ClassDoc cd) {
1074         if (cd.isIncluded()) {
1075             return configuration.isGeneratedDoc(cd);
1076         }
1077         return configuration.extern.isExternal(cd);
1078     }
1079 
1080     public DocLink getCrossPackageLink(String pkgName) {
1081         return configuration.extern.getExternalLink(pkgName, pathToRoot,
1082             DocPaths.PACKAGE_SUMMARY.getPath());
1083     }
1084 
1085     /**
1086      * Get the class link.
1087      *
1088      * @param context the id of the context where the link will be added
1089      * @param cd the class doc to link to
1090      * @return a content tree for the link
1091      */
1092     public Content getQualifiedClassLink(int context, ClassDoc cd) {
1093         return new RawHtml(getLink(new LinkInfoImpl(configuration, context, cd,
1094                 configuration.getClassName(cd), "")));
1095     }
1096 
1097     /**
1098      * Add the class link.
1099      *
1100      * @param context the id of the context where the link will be added
1101      * @param cd the class doc to link to
1102      * @param contentTree the content tree to which the link will be added
1103      */
1104     public void addPreQualifiedClassLink(int context, ClassDoc cd, Content contentTree) {
1105         addPreQualifiedClassLink(context, cd, false, contentTree);
1106     }
1107 
1108     /**
1109      * Retrieve the class link with the package portion of the label in
1110      * plain text.  If the qualifier is excluded, it will not be included in the
1111      * link label.
1112      *
1113      * @param cd the class to link to.
1114      * @param isStrong true if the link should be strong.
1115      * @return the link with the package portion of the label in plain text.
1116      */
1117     public String getPreQualifiedClassLink(int context,
1118             ClassDoc cd, boolean isStrong) {
1119         String classlink = "";
1120         PackageDoc pd = cd.containingPackage();
1121         if(pd != null && ! configuration.shouldExcludeQualifier(pd.name())) {
1122             classlink = getPkgName(cd);
1123         }
1124         classlink += getLink(new LinkInfoImpl(configuration,
1125                 context, cd, cd.name(), isStrong));
1126         return classlink;
1127     }
1128 
1129     /**
1130      * Add the class link with the package portion of the label in
1131      * plain text. If the qualifier is excluded, it will not be included in the
1132      * link label.
1133      *
1134      * @param context the id of the context where the link will be added
1135      * @param cd the class to link to
1136      * @param isStrong true if the link should be strong
1137      * @param contentTree the content tree to which the link with be added
1138      */
1139     public void addPreQualifiedClassLink(int context,
1140             ClassDoc cd, boolean isStrong, Content contentTree) {
1141         PackageDoc pd = cd.containingPackage();
1142         if(pd != null && ! configuration.shouldExcludeQualifier(pd.name())) {
1143             contentTree.addContent(getPkgName(cd));
1144         }
1145         contentTree.addContent(new RawHtml(getLink(new LinkInfoImpl(configuration,
1146                 context, cd, cd.name(), isStrong))));
1147     }
1148 
1149     /**
1150      * Add the class link, with only class name as the strong link and prefixing
1151      * plain package name.
1152      *
1153      * @param context the id of the context where the link will be added
1154      * @param cd the class to link to
1155      * @param contentTree the content tree to which the link with be added
1156      */
1157     public void addPreQualifiedStrongClassLink(int context, ClassDoc cd, Content contentTree) {
1158         addPreQualifiedClassLink(context, cd, true, contentTree);
1159     }
1160 
1161     /**
1162      * Get the link for the given member.
1163      *
1164      * @param context the id of the context where the link will be added
1165      * @param doc the member being linked to
1166      * @param label the label for the link
1167      * @return a content tree for the doc link
1168      */
1169     public Content getDocLink(int context, MemberDoc doc, String label) {
1170         return getDocLink(context, doc.containingClass(), doc, label);
1171     }
1172 
1173     /**
1174      * Return the link for the given member.
1175      *
1176      * @param context the id of the context where the link will be printed.
1177      * @param doc the member being linked to.
1178      * @param label the label for the link.
1179      * @param strong true if the link should be strong.
1180      * @return the link for the given member.
1181      */
1182     public String getDocLink(int context, MemberDoc doc, String label,
1183                 boolean strong) {
1184         return getDocLink(context, doc.containingClass(), doc, label, strong);
1185     }
1186 
1187     /**
1188      * Return the link for the given member.
1189      *
1190      * @param context the id of the context where the link will be printed.
1191      * @param classDoc the classDoc that we should link to.  This is not
1192      *                 necessarily equal to doc.containingClass().  We may be
1193      *                 inheriting comments.
1194      * @param doc the member being linked to.
1195      * @param label the label for the link.
1196      * @param strong true if the link should be strong.
1197      * @return the link for the given member.
1198      */
1199     public String getDocLink(int context, ClassDoc classDoc, MemberDoc doc,
1200         String label, boolean strong) {
1201         if (! (doc.isIncluded() ||
1202             Util.isLinkable(classDoc, configuration))) {
1203             return label;
1204         } else if (doc instanceof ExecutableMemberDoc) {
1205             ExecutableMemberDoc emd = (ExecutableMemberDoc)doc;
1206             return getLink(new LinkInfoImpl(configuration, context, classDoc,
1207                 getAnchor(emd), label, strong));
1208         } else if (doc instanceof MemberDoc) {
1209             return getLink(new LinkInfoImpl(configuration, context, classDoc,
1210                 doc.name(), label, strong));
1211         } else {
1212             return label;
1213         }
1214     }
1215 
1216     /**
1217      * Return the link for the given member.
1218      *
1219      * @param context the id of the context where the link will be added
1220      * @param classDoc the classDoc that we should link to.  This is not
1221      *                 necessarily equal to doc.containingClass().  We may be
1222      *                 inheriting comments
1223      * @param doc the member being linked to
1224      * @param label the label for the link
1225      * @return the link for the given member
1226      */
1227     public Content getDocLink(int context, ClassDoc classDoc, MemberDoc doc,
1228         String label) {
1229         if (! (doc.isIncluded() ||
1230             Util.isLinkable(classDoc, configuration))) {
1231             return new StringContent(label);
1232         } else if (doc instanceof ExecutableMemberDoc) {
1233             ExecutableMemberDoc emd = (ExecutableMemberDoc)doc;
1234             return new RawHtml(getLink(new LinkInfoImpl(configuration, context, classDoc,
1235                 getAnchor(emd), label, false)));
1236         } else if (doc instanceof MemberDoc) {
1237             return new RawHtml(getLink(new LinkInfoImpl(configuration, context, classDoc,
1238                 doc.name(), label, false)));
1239         } else {
1240             return new StringContent(label);
1241         }
1242     }
1243 
1244     public String getAnchor(ExecutableMemberDoc emd) {
1245         StringBuilder signature = new StringBuilder(emd.signature());
1246         StringBuilder signatureParsed = new StringBuilder();
1247         int counter = 0;
1248         for (int i = 0; i < signature.length(); i++) {
1249             char c = signature.charAt(i);
1250             if (c == '<') {
1251                 counter++;
1252             } else if (c == '>') {
1253                 counter--;
1254             } else if (counter == 0) {
1255                 signatureParsed.append(c);
1256             }
1257         }
1258         return emd.name() + signatureParsed.toString();
1259     }
1260 
1261     public String seeTagToString(SeeTag see) {
1262         String tagName = see.name();
1263         if (! (tagName.startsWith("@link") || tagName.equals("@see"))) {
1264             return "";
1265         }
1266 
1267         String seetext = replaceDocRootDir(see.text());
1268 
1269         //Check if @see is an href or "string"
1270         if (seetext.startsWith("<") || seetext.startsWith("\"")) {
1271             return seetext;
1272         }
1273 
1274         boolean plain = tagName.equalsIgnoreCase("@linkplain");
1275         String label = plainOrCodeText(plain, see.label());
1276 
1277         //The text from the @see tag.  We will output this text when a label is not specified.
1278         String text = plainOrCodeText(plain, seetext);
1279 
1280         ClassDoc refClass = see.referencedClass();
1281         String refClassName = see.referencedClassName();
1282         MemberDoc refMem = see.referencedMember();
1283         String refMemName = see.referencedMemberName();
1284 
1285         if (refClass == null) {
1286             //@see is not referencing an included class
1287             PackageDoc refPackage = see.referencedPackage();
1288             if (refPackage != null && refPackage.isIncluded()) {
1289                 //@see is referencing an included package
1290                 if (label.isEmpty())
1291                     label = plainOrCodeText(plain, refPackage.name());
1292                 return getPackageLinkString(refPackage, label, false);
1293             } else {
1294                 //@see is not referencing an included class or package.  Check for cross links.
1295                 String classCrossLink;
1296                 DocLink packageCrossLink = getCrossPackageLink(refClassName);
1297                 if (packageCrossLink != null) {
1298                     //Package cross link found
1299                     return getHyperLinkString(packageCrossLink,
1300                         (label.isEmpty() ? text : label), false);
1301                 } else if ((classCrossLink = getCrossClassLink(refClassName,
1302                         refMemName, label, false, "", !plain)) != null) {
1303                     //Class cross link found (possibly to a member in the class)
1304                     return classCrossLink;
1305                 } else {
1306                     //No cross link found so print warning
1307                     configuration.getDocletSpecificMsg().warning(see.position(), "doclet.see.class_or_package_not_found",
1308                             tagName, seetext);
1309                     return (label.isEmpty() ? text: label);
1310                 }
1311             }
1312         } else if (refMemName == null) {
1313             // Must be a class reference since refClass is not null and refMemName is null.
1314             if (label.isEmpty()) {
1315                 label = plainOrCodeText(plain, refClass.name());
1316             }
1317             return getLink(new LinkInfoImpl(configuration, refClass, label));
1318         } else if (refMem == null) {
1319             // Must be a member reference since refClass is not null and refMemName is not null.
1320             // However, refMem is null, so this referenced member does not exist.
1321             return (label.isEmpty() ? text: label);
1322         } else {
1323             // Must be a member reference since refClass is not null and refMemName is not null.
1324             // refMem is not null, so this @see tag must be referencing a valid member.
1325             ClassDoc containing = refMem.containingClass();
1326             if (see.text().trim().startsWith("#") &&
1327                 ! (containing.isPublic() ||
1328                 Util.isLinkable(containing, configuration))) {
1329                 // Since the link is relative and the holder is not even being
1330                 // documented, this must be an inherited link.  Redirect it.
1331                 // The current class either overrides the referenced member or
1332                 // inherits it automatically.
1333                 if (this instanceof ClassWriterImpl) {
1334                     containing = ((ClassWriterImpl) this).getClassDoc();
1335                 } else if (!containing.isPublic()){
1336                     configuration.getDocletSpecificMsg().warning(
1337                         see.position(), "doclet.see.class_or_package_not_accessible",
1338                         tagName, containing.qualifiedName());
1339                 } else {
1340                     configuration.getDocletSpecificMsg().warning(
1341                         see.position(), "doclet.see.class_or_package_not_found",
1342                         tagName, seetext);
1343                 }
1344             }
1345             if (configuration.currentcd != containing) {
1346                 refMemName = containing.name() + "." + refMemName;
1347             }
1348             if (refMem instanceof ExecutableMemberDoc) {
1349                 if (refMemName.indexOf('(') < 0) {
1350                     refMemName += ((ExecutableMemberDoc)refMem).signature();
1351                 }
1352             }
1353 
1354             text = plainOrCodeText(plain, Util.escapeHtmlChars(refMemName));
1355 
1356             return getDocLink(LinkInfoImpl.CONTEXT_SEE_TAG, containing,
1357                 refMem, (label.isEmpty() ? text: label), false);
1358         }
1359     }
1360 
1361     private String plainOrCodeText(boolean plain, String text) {
1362         return (plain || text.isEmpty()) ? text : codeText(text);
1363     }
1364 
1365     /**
1366      * Add the inline comment.
1367      *
1368      * @param doc the doc for which the inline comment will be added
1369      * @param tag the inline tag to be added
1370      * @param htmltree the content tree to which the comment will be added
1371      */
1372     public void addInlineComment(Doc doc, Tag tag, Content htmltree) {
1373         addCommentTags(doc, tag.inlineTags(), false, false, htmltree);
1374     }
1375 
1376     /**
1377      * Add the inline deprecated comment.
1378      *
1379      * @param doc the doc for which the inline deprecated comment will be added
1380      * @param tag the inline tag to be added
1381      * @param htmltree the content tree to which the comment will be added
1382      */
1383     public void addInlineDeprecatedComment(Doc doc, Tag tag, Content htmltree) {
1384         addCommentTags(doc, tag.inlineTags(), true, false, htmltree);
1385     }
1386 
1387     /**
1388      * Adds the summary content.
1389      *
1390      * @param doc the doc for which the summary will be generated
1391      * @param htmltree the documentation tree to which the summary will be added
1392      */
1393     public void addSummaryComment(Doc doc, Content htmltree) {
1394         addSummaryComment(doc, doc.firstSentenceTags(), htmltree);
1395     }
1396 
1397     /**
1398      * Adds the summary content.
1399      *
1400      * @param doc the doc for which the summary will be generated
1401      * @param firstSentenceTags the first sentence tags for the doc
1402      * @param htmltree the documentation tree to which the summary will be added
1403      */
1404     public void addSummaryComment(Doc doc, Tag[] firstSentenceTags, Content htmltree) {
1405         addCommentTags(doc, firstSentenceTags, false, true, htmltree);
1406     }
1407 
1408     public void addSummaryDeprecatedComment(Doc doc, Tag tag, Content htmltree) {
1409         addCommentTags(doc, tag.firstSentenceTags(), true, true, htmltree);
1410     }
1411 
1412     /**
1413      * Adds the inline comment.
1414      *
1415      * @param doc the doc for which the inline comments will be generated
1416      * @param htmltree the documentation tree to which the inline comments will be added
1417      */
1418     public void addInlineComment(Doc doc, Content htmltree) {
1419         addCommentTags(doc, doc.inlineTags(), false, false, htmltree);
1420     }
1421 
1422     /**
1423      * Adds the comment tags.
1424      *
1425      * @param doc the doc for which the comment tags will be generated
1426      * @param tags the first sentence tags for the doc
1427      * @param depr true if it is deprecated
1428      * @param first true if the first sentence tags should be added
1429      * @param htmltree the documentation tree to which the comment tags will be added
1430      */
1431     private void addCommentTags(Doc doc, Tag[] tags, boolean depr,
1432             boolean first, Content htmltree) {
1433         if(configuration.nocomment){
1434             return;
1435         }
1436         Content div;
1437         Content result = new RawHtml(commentTagsToString(null, doc, tags, first));
1438         if (depr) {
1439             Content italic = HtmlTree.I(result);
1440             div = HtmlTree.DIV(HtmlStyle.block, italic);
1441             htmltree.addContent(div);
1442         }
1443         else {
1444             div = HtmlTree.DIV(HtmlStyle.block, result);
1445             htmltree.addContent(div);
1446         }
1447         if (tags.length == 0) {
1448             htmltree.addContent(getSpace());
1449         }
1450     }
1451 
1452     /**
1453      * Converts inline tags and text to text strings, expanding the
1454      * inline tags along the way.  Called wherever text can contain
1455      * an inline tag, such as in comments or in free-form text arguments
1456      * to non-inline tags.
1457      *
1458      * @param holderTag    specific tag where comment resides
1459      * @param doc    specific doc where comment resides
1460      * @param tags   array of text tags and inline tags (often alternating)
1461      *               present in the text of interest for this doc
1462      * @param isFirstSentence  true if text is first sentence
1463      */
1464     public String commentTagsToString(Tag holderTag, Doc doc, Tag[] tags,
1465             boolean isFirstSentence) {
1466         StringBuilder result = new StringBuilder();
1467         boolean textTagChange = false;
1468         // Array of all possible inline tags for this javadoc run
1469         configuration.tagletManager.checkTags(doc, tags, true);
1470         for (int i = 0; i < tags.length; i++) {
1471             Tag tagelem = tags[i];
1472             String tagName = tagelem.name();
1473             if (tagelem instanceof SeeTag) {
1474                 result.append(seeTagToString((SeeTag)tagelem));
1475             } else if (! tagName.equals("Text")) {
1476                 int originalLength = result.length();
1477                 TagletOutput output = TagletWriter.getInlineTagOuput(
1478                     configuration.tagletManager, holderTag,
1479                     tagelem, getTagletWriterInstance(isFirstSentence));
1480                 result.append(output == null ? "" : output.toString());
1481                 if (originalLength == 0 && isFirstSentence && tagelem.name().equals("@inheritDoc") && result.length() > 0) {
1482                     break;
1483                 } else if (configuration.docrootparent.length() > 0 &&
1484                         tagelem.name().equals("@docRoot") &&
1485                         ((tags[i + 1]).text()).startsWith("/..")) {
1486                     //If Xdocrootparent switch ON, set the flag to remove the /.. occurance after
1487                     //{@docRoot} tag in the very next Text tag.
1488                     textTagChange = true;
1489                     continue;
1490                 } else {
1491                     continue;
1492                 }
1493             } else {
1494                 String text = tagelem.text();
1495                 //If Xdocrootparent switch ON, remove the /.. occurance after {@docRoot} tag.
1496                 if (textTagChange) {
1497                     text = text.replaceFirst("/..", "");
1498                     textTagChange = false;
1499                 }
1500                 //This is just a regular text tag.  The text may contain html links (<a>)
1501                 //or inline tag {@docRoot}, which will be handled as special cases.
1502                 text = redirectRelativeLinks(tagelem.holder(), text);
1503 
1504                 // Replace @docRoot only if not represented by an instance of DocRootTaglet,
1505                 // that is, only if it was not present in a source file doc comment.
1506                 // This happens when inserted by the doclet (a few lines
1507                 // above in this method).  [It might also happen when passed in on the command
1508                 // line as a text argument to an option (like -header).]
1509                 text = replaceDocRootDir(text);
1510                 if (isFirstSentence) {
1511                     text = removeNonInlineHtmlTags(text);
1512                 }
1513                 StringTokenizer lines = new StringTokenizer(text, "\r\n", true);
1514                 StringBuilder textBuff = new StringBuilder();
1515                 while (lines.hasMoreTokens()) {
1516                     StringBuilder line = new StringBuilder(lines.nextToken());
1517                     Util.replaceTabs(configuration, line);
1518                     textBuff.append(line.toString());
1519                 }
1520                 result.append(textBuff);
1521             }
1522         }
1523         return result.toString();
1524     }
1525 
1526     /**
1527      * Return true if relative links should not be redirected.
1528      *
1529      * @return Return true if a relative link should not be redirected.
1530      */
1531     private boolean shouldNotRedirectRelativeLinks() {
1532         return  this instanceof AnnotationTypeWriter ||
1533                 this instanceof ClassWriter ||
1534                 this instanceof PackageSummaryWriter;
1535     }
1536 
1537     /**
1538      * Suppose a piece of documentation has a relative link.  When you copy
1539      * that documentation to another place such as the index or class-use page,
1540      * that relative link will no longer work.  We should redirect those links
1541      * so that they will work again.
1542      * <p>
1543      * Here is the algorithm used to fix the link:
1544      * <p>
1545      * {@literal <relative link> => docRoot + <relative path to file> + <relative link> }
1546      * <p>
1547      * For example, suppose com.sun.javadoc.RootDoc has this link:
1548      * {@literal <a href="package-summary.html">The package Page</a> }
1549      * <p>
1550      * If this link appeared in the index, we would redirect
1551      * the link like this:
1552      *
1553      * {@literal <a href="./com/sun/javadoc/package-summary.html">The package Page</a>}
1554      *
1555      * @param doc the Doc object whose documentation is being written.
1556      * @param text the text being written.
1557      *
1558      * @return the text, with all the relative links redirected to work.
1559      */
1560     private String redirectRelativeLinks(Doc doc, String text) {
1561         if (doc == null || shouldNotRedirectRelativeLinks()) {
1562             return text;
1563         }
1564 
1565         DocPath redirectPathFromRoot;
1566         if (doc instanceof ClassDoc) {
1567             redirectPathFromRoot = DocPath.forPackage(((ClassDoc) doc).containingPackage());
1568         } else if (doc instanceof MemberDoc) {
1569             redirectPathFromRoot = DocPath.forPackage(((MemberDoc) doc).containingPackage());
1570         } else if (doc instanceof PackageDoc) {
1571             redirectPathFromRoot = DocPath.forPackage((PackageDoc) doc);
1572         } else {
1573             return text;
1574         }
1575 
1576         //Redirect all relative links.
1577         int end, begin = text.toLowerCase().indexOf("<a");
1578         if(begin >= 0){
1579             StringBuilder textBuff = new StringBuilder(text);
1580 
1581             while(begin >=0){
1582                 if (textBuff.length() > begin + 2 && ! Character.isWhitespace(textBuff.charAt(begin+2))) {
1583                     begin = textBuff.toString().toLowerCase().indexOf("<a", begin + 1);
1584                     continue;
1585                 }
1586 
1587                 begin = textBuff.indexOf("=", begin) + 1;
1588                 end = textBuff.indexOf(">", begin +1);
1589                 if(begin == 0){
1590                     //Link has no equal symbol.
1591                     configuration.root.printWarning(
1592                         doc.position(),
1593                         configuration.getText("doclet.malformed_html_link_tag", text));
1594                     break;
1595                 }
1596                 if (end == -1) {
1597                     //Break without warning.  This <a> tag is not necessarily malformed.  The text
1598                     //might be missing '>' character because the href has an inline tag.
1599                     break;
1600                 }
1601                 if (textBuff.substring(begin, end).indexOf("\"") != -1){
1602                     begin = textBuff.indexOf("\"", begin) + 1;
1603                     end = textBuff.indexOf("\"", begin +1);
1604                     if (begin == 0 || end == -1){
1605                         //Link is missing a quote.
1606                         break;
1607                     }
1608                 }
1609                 String relativeLink = textBuff.substring(begin, end);
1610                 if (!(relativeLink.toLowerCase().startsWith("mailto:") ||
1611                         relativeLink.toLowerCase().startsWith("http:") ||
1612                         relativeLink.toLowerCase().startsWith("https:") ||
1613                         relativeLink.toLowerCase().startsWith("file:"))) {
1614                     relativeLink = "{@"+(new DocRootTaglet()).getName() + "}/"
1615                         + redirectPathFromRoot.resolve(relativeLink).getPath();
1616                     textBuff.replace(begin, end, relativeLink);
1617                 }
1618                 begin = textBuff.toString().toLowerCase().indexOf("<a", begin + 1);
1619             }
1620             return textBuff.toString();
1621         }
1622         return text;
1623     }
1624 
1625     public String removeNonInlineHtmlTags(String text) {
1626         if (text.indexOf('<') < 0) {
1627             return text;
1628         }
1629         String noninlinetags[] = { "<ul>", "</ul>", "<ol>", "</ol>",
1630                 "<dl>", "</dl>", "<table>", "</table>",
1631                 "<tr>", "</tr>", "<td>", "</td>",
1632                 "<th>", "</th>", "<p>", "</p>",
1633                 "<li>", "</li>", "<dd>", "</dd>",
1634                 "<dir>", "</dir>", "<dt>", "</dt>",
1635                 "<h1>", "</h1>", "<h2>", "</h2>",
1636                 "<h3>", "</h3>", "<h4>", "</h4>",
1637                 "<h5>", "</h5>", "<h6>", "</h6>",
1638                 "<pre>", "</pre>", "<menu>", "</menu>",
1639                 "<listing>", "</listing>", "<hr>",
1640                 "<blockquote>", "</blockquote>",
1641                 "<center>", "</center>",
1642                 "<UL>", "</UL>", "<OL>", "</OL>",
1643                 "<DL>", "</DL>", "<TABLE>", "</TABLE>",
1644                 "<TR>", "</TR>", "<TD>", "</TD>",
1645                 "<TH>", "</TH>", "<P>", "</P>",
1646                 "<LI>", "</LI>", "<DD>", "</DD>",
1647                 "<DIR>", "</DIR>", "<DT>", "</DT>",
1648                 "<H1>", "</H1>", "<H2>", "</H2>",
1649                 "<H3>", "</H3>", "<H4>", "</H4>",
1650                 "<H5>", "</H5>", "<H6>", "</H6>",
1651                 "<PRE>", "</PRE>", "<MENU>", "</MENU>",
1652                 "<LISTING>", "</LISTING>", "<HR>",
1653                 "<BLOCKQUOTE>", "</BLOCKQUOTE>",
1654                 "<CENTER>", "</CENTER>"
1655         };
1656         for (int i = 0; i < noninlinetags.length; i++) {
1657             text = replace(text, noninlinetags[i], "");
1658         }
1659         return text;
1660     }
1661 
1662     public String replace(String text, String tobe, String by) {
1663         while (true) {
1664             int startindex = text.indexOf(tobe);
1665             if (startindex < 0) {
1666                 return text;
1667             }
1668             int endindex = startindex + tobe.length();
1669             StringBuilder replaced = new StringBuilder();
1670             if (startindex > 0) {
1671                 replaced.append(text.substring(0, startindex));
1672             }
1673             replaced.append(by);
1674             if (text.length() > endindex) {
1675                 replaced.append(text.substring(endindex));
1676             }
1677             text = replaced.toString();
1678         }
1679     }
1680 
1681     /**
1682      * Returns a link to the stylesheet file.
1683      *
1684      * @return an HtmlTree for the lINK tag which provides the stylesheet location
1685      */
1686     public HtmlTree getStyleSheetProperties() {
1687         String stylesheetfile = configuration.stylesheetfile;
1688         DocPath stylesheet;
1689         if (stylesheetfile.isEmpty()) {
1690             stylesheet = DocPaths.STYLESHEET;
1691         } else {
1692             DocFile file = DocFile.createFileForInput(configuration, stylesheetfile);
1693             stylesheet = DocPath.create(file.getName());
1694         }
1695         HtmlTree link = HtmlTree.LINK("stylesheet", "text/css",
1696                 pathToRoot.resolve(stylesheet).getPath(),
1697                 "Style");
1698         return link;
1699     }
1700 
1701     /**
1702      * Returns a link to the JavaScript file.
1703      *
1704      * @return an HtmlTree for the Script tag which provides the JavaScript location
1705      */
1706     public HtmlTree getScriptProperties() {
1707         HtmlTree script = HtmlTree.SCRIPT("text/javascript",
1708                 pathToRoot.resolve(DocPaths.JAVASCRIPT).getPath());
1709         return script;
1710     }
1711 
1712     /**
1713      * According to
1714      * <cite>The Java&trade; Language Specification</cite>,
1715      * all the outer classes and static nested classes are core classes.
1716      */
1717     public boolean isCoreClass(ClassDoc cd) {
1718         return cd.containingClass() == null || cd.isStatic();
1719     }
1720 
1721     /**
1722      * Adds the annotatation types for the given packageDoc.
1723      *
1724      * @param packageDoc the package to write annotations for.
1725      * @param htmltree the documentation tree to which the annotation info will be
1726      *        added
1727      */
1728     public void addAnnotationInfo(PackageDoc packageDoc, Content htmltree) {
1729         addAnnotationInfo(packageDoc, packageDoc.annotations(), htmltree);
1730     }
1731 
1732     /**
1733      * Adds the annotatation types for the given doc.
1734      *
1735      * @param doc the package to write annotations for
1736      * @param htmltree the content tree to which the annotation types will be added
1737      */
1738     public void addAnnotationInfo(ProgramElementDoc doc, Content htmltree) {
1739         addAnnotationInfo(doc, doc.annotations(), htmltree);
1740     }
1741 
1742     /**
1743      * Add the annotatation types for the given doc and parameter.
1744      *
1745      * @param indent the number of spaces to indent the parameters.
1746      * @param doc the doc to write annotations for.
1747      * @param param the parameter to write annotations for.
1748      * @param tree the content tree to which the annotation types will be added
1749      */
1750     public boolean addAnnotationInfo(int indent, Doc doc, Parameter param,
1751             Content tree) {
1752         return addAnnotationInfo(indent, doc, param.annotations(), false, tree);
1753     }
1754 
1755     /**
1756      * Adds the annotatation types for the given doc.
1757      *
1758      * @param doc the doc to write annotations for.
1759      * @param descList the array of {@link AnnotationDesc}.
1760      * @param htmltree the documentation tree to which the annotation info will be
1761      *        added
1762      */
1763     private void addAnnotationInfo(Doc doc, AnnotationDesc[] descList,
1764             Content htmltree) {
1765         addAnnotationInfo(0, doc, descList, true, htmltree);
1766     }
1767 
1768     /**
1769      * Adds the annotatation types for the given doc.
1770      *
1771      * @param indent the number of extra spaces to indent the annotations.
1772      * @param doc the doc to write annotations for.
1773      * @param descList the array of {@link AnnotationDesc}.
1774      * @param htmltree the documentation tree to which the annotation info will be
1775      *        added
1776      */
1777     private boolean addAnnotationInfo(int indent, Doc doc,
1778             AnnotationDesc[] descList, boolean lineBreak, Content htmltree) {
1779         List<String> annotations = getAnnotations(indent, descList, lineBreak);
1780         if (annotations.size() == 0) {
1781             return false;
1782         }
1783         Content annotationContent;
1784         for (Iterator<String> iter = annotations.iterator(); iter.hasNext();) {
1785             annotationContent = new RawHtml(iter.next());
1786             htmltree.addContent(annotationContent);
1787         }
1788         return true;
1789     }
1790 
1791    /**
1792      * Return the string representations of the annotation types for
1793      * the given doc.
1794      *
1795      * @param indent the number of extra spaces to indent the annotations.
1796      * @param descList the array of {@link AnnotationDesc}.
1797      * @param linkBreak if true, add new line between each member value.
1798      * @return an array of strings representing the annotations being
1799      *         documented.
1800      */
1801     private List<String> getAnnotations(int indent, AnnotationDesc[] descList, boolean linkBreak) {
1802         List<String> results = new ArrayList<String>();
1803         StringBuilder annotation;
1804         for (int i = 0; i < descList.length; i++) {
1805             AnnotationTypeDoc annotationDoc = descList[i].annotationType();
1806             // If an annotation is not documented, do not add it to the list. If
1807             // the annotation is of a repeatable type, and if it is not documented
1808             // and also if its container annotation is not documented, do not add it
1809             // to the list. If an annotation of a repeatable type is not documented
1810             // but its container is documented, it will be added to the list.
1811             if (! Util.isDocumentedAnnotation(annotationDoc) &&
1812                     (!isAnnotationDocumented && !isContainerDocumented)) {
1813                 continue;
1814             }
1815             annotation = new StringBuilder();
1816             isAnnotationDocumented = false;
1817             LinkInfoImpl linkInfo = new LinkInfoImpl(configuration,
1818                 LinkInfoImpl.CONTEXT_ANNOTATION, annotationDoc);
1819             AnnotationDesc.ElementValuePair[] pairs = descList[i].elementValues();
1820             // If the annotation is synthesized, do not print the container.
1821             if (descList[i].isSynthesized()) {
1822                 for (int j = 0; j < pairs.length; j++) {
1823                     AnnotationValue annotationValue = pairs[j].value();
1824                     List<AnnotationValue> annotationTypeValues = new ArrayList<AnnotationValue>();
1825                     if (annotationValue.value() instanceof AnnotationValue[]) {
1826                         AnnotationValue[] annotationArray =
1827                                 (AnnotationValue[]) annotationValue.value();
1828                         annotationTypeValues.addAll(Arrays.asList(annotationArray));
1829                     } else {
1830                         annotationTypeValues.add(annotationValue);
1831                     }
1832                     String sep = "";
1833                     for (AnnotationValue av : annotationTypeValues) {
1834                         annotation.append(sep);
1835                         annotation.append(annotationValueToString(av));
1836                         sep = " ";
1837                     }
1838                 }
1839             }
1840             else if (isAnnotationArray(pairs)) {
1841                 // If the container has 1 or more value defined and if the
1842                 // repeatable type annotation is not documented, do not print
1843                 // the container.
1844                 if (pairs.length == 1 && isAnnotationDocumented) {
1845                     AnnotationValue[] annotationArray =
1846                             (AnnotationValue[]) (pairs[0].value()).value();
1847                     List<AnnotationValue> annotationTypeValues = new ArrayList<AnnotationValue>();
1848                     annotationTypeValues.addAll(Arrays.asList(annotationArray));
1849                     String sep = "";
1850                     for (AnnotationValue av : annotationTypeValues) {
1851                         annotation.append(sep);
1852                         annotation.append(annotationValueToString(av));
1853                         sep = " ";
1854                     }
1855                 }
1856                 // If the container has 1 or more value defined and if the
1857                 // repeatable type annotation is not documented, print the container.
1858                 else {
1859                     addAnnotations(annotationDoc, linkInfo, annotation, pairs,
1860                         indent, false);
1861                 }
1862             }
1863             else {
1864                 addAnnotations(annotationDoc, linkInfo, annotation, pairs,
1865                         indent, linkBreak);
1866             }
1867             annotation.append(linkBreak ? DocletConstants.NL : "");
1868             results.add(annotation.toString());
1869         }
1870         return results;
1871     }
1872 
1873     /**
1874      * Add annotation to the annotation string.
1875      *
1876      * @param annotationDoc the annotation being documented
1877      * @param linkInfo the information about the link
1878      * @param annotation the annotation string to which the annotation will be added
1879      * @param pairs annotation type element and value pairs
1880      * @param indent the number of extra spaces to indent the annotations.
1881      * @param linkBreak if true, add new line between each member value
1882      */
1883     private void addAnnotations(AnnotationTypeDoc annotationDoc, LinkInfoImpl linkInfo,
1884             StringBuilder annotation, AnnotationDesc.ElementValuePair[] pairs,
1885             int indent, boolean linkBreak) {
1886         linkInfo.label = "@" + annotationDoc.name();
1887         annotation.append(getLink(linkInfo));
1888         if (pairs.length > 0) {
1889             annotation.append('(');
1890             for (int j = 0; j < pairs.length; j++) {
1891                 if (j > 0) {
1892                     annotation.append(",");
1893                     if (linkBreak) {
1894                         annotation.append(DocletConstants.NL);
1895                         int spaces = annotationDoc.name().length() + 2;
1896                         for (int k = 0; k < (spaces + indent); k++) {
1897                             annotation.append(' ');
1898                         }
1899                     }
1900                 }
1901                 annotation.append(getDocLink(LinkInfoImpl.CONTEXT_ANNOTATION,
1902                         pairs[j].element(), pairs[j].element().name(), false));
1903                 annotation.append('=');
1904                 AnnotationValue annotationValue = pairs[j].value();
1905                 List<AnnotationValue> annotationTypeValues = new ArrayList<AnnotationValue>();
1906                 if (annotationValue.value() instanceof AnnotationValue[]) {
1907                     AnnotationValue[] annotationArray =
1908                             (AnnotationValue[]) annotationValue.value();
1909                     annotationTypeValues.addAll(Arrays.asList(annotationArray));
1910                 } else {
1911                     annotationTypeValues.add(annotationValue);
1912                 }
1913                 annotation.append(annotationTypeValues.size() == 1 ? "" : "{");
1914                 String sep = "";
1915                 for (AnnotationValue av : annotationTypeValues) {
1916                     annotation.append(sep);
1917                     annotation.append(annotationValueToString(av));
1918                     sep = ",";
1919                 }
1920                 annotation.append(annotationTypeValues.size() == 1 ? "" : "}");
1921                 isContainerDocumented = false;
1922             }
1923             annotation.append(")");
1924         }
1925     }
1926 
1927     /**
1928      * Check if the annotation contains an array of annotation as a value. This
1929      * check is to verify if a repeatable type annotation is present or not.
1930      *
1931      * @param pairs annotation type element and value pairs
1932      *
1933      * @return true if the annotation contains an array of annotation as a value.
1934      */
1935     private boolean isAnnotationArray(AnnotationDesc.ElementValuePair[] pairs) {
1936         AnnotationValue annotationValue;
1937         for (int j = 0; j < pairs.length; j++) {
1938             annotationValue = pairs[j].value();
1939             if (annotationValue.value() instanceof AnnotationValue[]) {
1940                 AnnotationValue[] annotationArray =
1941                         (AnnotationValue[]) annotationValue.value();
1942                 if (annotationArray.length > 1) {
1943                     if (annotationArray[0].value() instanceof AnnotationDesc) {
1944                         AnnotationTypeDoc annotationDoc =
1945                                 ((AnnotationDesc) annotationArray[0].value()).annotationType();
1946                         isContainerDocumented = true;
1947                         if (Util.isDocumentedAnnotation(annotationDoc)) {
1948                             isAnnotationDocumented = true;
1949                         }
1950                         return true;
1951                     }
1952                 }
1953             }
1954         }
1955         return false;
1956     }
1957 
1958     private String annotationValueToString(AnnotationValue annotationValue) {
1959         if (annotationValue.value() instanceof Type) {
1960             Type type = (Type) annotationValue.value();
1961             if (type.asClassDoc() != null) {
1962                 LinkInfoImpl linkInfo = new LinkInfoImpl(configuration,
1963                     LinkInfoImpl.CONTEXT_ANNOTATION, type);
1964                     linkInfo.label = (type.asClassDoc().isIncluded() ?
1965                         type.typeName() :
1966                         type.qualifiedTypeName()) + type.dimension() + ".class";
1967                 return getLink(linkInfo);
1968             } else {
1969                 return type.typeName() + type.dimension() + ".class";
1970             }
1971         } else if (annotationValue.value() instanceof AnnotationDesc) {
1972             List<String> list = getAnnotations(0,
1973                 new AnnotationDesc[]{(AnnotationDesc) annotationValue.value()},
1974                     false);
1975             StringBuilder buf = new StringBuilder();
1976             for (String s: list) {
1977                 buf.append(s);
1978             }
1979             return buf.toString();
1980         } else if (annotationValue.value() instanceof MemberDoc) {
1981             return getDocLink(LinkInfoImpl.CONTEXT_ANNOTATION,
1982                 (MemberDoc) annotationValue.value(),
1983                 ((MemberDoc) annotationValue.value()).name(), false);
1984          } else {
1985             return annotationValue.toString();
1986          }
1987     }
1988 
1989     /**
1990      * Return the configuation for this doclet.
1991      *
1992      * @return the configuration for this doclet.
1993      */
1994     public Configuration configuration() {
1995         return configuration;
1996     }
1997 }