1 /*
   2  * Copyright (c) 1997, 2019, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.javadoc.internal.doclets.formats.html;
  27 
  28 import java.util.ArrayList;
  29 import java.util.List;
  30 import java.util.Set;
  31 import java.util.TreeSet;
  32 import java.util.stream.Collectors;
  33 
  34 import javax.lang.model.element.Element;
  35 import javax.lang.model.element.ExecutableElement;
  36 import javax.lang.model.element.Modifier;
  37 import javax.lang.model.element.TypeElement;
  38 import javax.lang.model.element.TypeParameterElement;
  39 import javax.lang.model.type.TypeMirror;
  40 
  41 import com.sun.source.doctree.DocTree;
  42 
  43 import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder;
  44 import jdk.javadoc.internal.doclets.formats.html.markup.Entity;
  45 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle;
  46 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag;
  47 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree;
  48 import jdk.javadoc.internal.doclets.formats.html.markup.Links;
  49 import jdk.javadoc.internal.doclets.formats.html.markup.StringContent;
  50 import jdk.javadoc.internal.doclets.formats.html.markup.Table;
  51 import jdk.javadoc.internal.doclets.formats.html.markup.TableHeader;
  52 import jdk.javadoc.internal.doclets.toolkit.Content;
  53 import jdk.javadoc.internal.doclets.toolkit.MemberSummaryWriter;
  54 import jdk.javadoc.internal.doclets.toolkit.Resources;
  55 import jdk.javadoc.internal.doclets.toolkit.taglets.DeprecatedTaglet;
  56 import jdk.javadoc.internal.doclets.toolkit.util.DocletConstants;
  57 import jdk.javadoc.internal.doclets.toolkit.util.Utils;
  58 
  59 import static javax.lang.model.element.Modifier.ABSTRACT;
  60 import static javax.lang.model.element.Modifier.NATIVE;
  61 import static javax.lang.model.element.Modifier.PUBLIC;
  62 import static javax.lang.model.element.Modifier.STRICTFP;
  63 import static javax.lang.model.element.Modifier.SYNCHRONIZED;
  64 
  65 /**
  66  * The base class for member writers.
  67  *
  68  *  <p><b>This is NOT part of any supported API.
  69  *  If you write code that depends on this, you do so at your own risk.
  70  *  This code and its internal interfaces are subject to change or
  71  *  deletion without notice.</b>
  72  */
  73 public abstract class AbstractMemberWriter implements MemberSummaryWriter {
  74 
  75     protected final HtmlConfiguration configuration;
  76     protected final HtmlOptions options;
  77     protected final Utils utils;
  78     protected final SubWriterHolderWriter writer;
  79     protected final Contents contents;
  80     protected final Resources resources;
  81     protected final Links links;
  82 
  83     protected final TypeElement typeElement;
  84     public final boolean nodepr;
  85 
  86     protected boolean printedSummaryHeader = false;
  87 
  88     public AbstractMemberWriter(SubWriterHolderWriter writer, TypeElement typeElement) {
  89         this.configuration = writer.configuration;
  90         this.options = configuration.getOptions();
  91         this.writer = writer;
  92         this.nodepr = options.noDeprecated;
  93         this.typeElement = typeElement;
  94         this.utils = configuration.utils;
  95         this.contents = configuration.contents;
  96         this.resources = configuration.resources;
  97         this.links = writer.links;
  98     }
  99 
 100     public AbstractMemberWriter(SubWriterHolderWriter writer) {
 101         this(writer, null);
 102     }
 103 
 104     /*** abstracts ***/
 105 
 106     /**
 107      * Add the summary label for the member.
 108      *
 109      * @param memberTree the content tree to which the label will be added
 110      */
 111     public abstract void addSummaryLabel(Content memberTree);
 112 
 113     /**
 114      * Get the summary for the member summary table.
 115      *
 116      * @return a string for the table summary
 117      */
 118     private String getTableSummaryX() { return null; }
 119 
 120     /**
 121      * Get the summary table header for the member.
 122      *
 123      * @param member the member to be documented
 124      * @return the summary table header
 125      */
 126     public abstract TableHeader getSummaryTableHeader(Element member);
 127 
 128     private Table summaryTable;
 129 
 130     private Table getSummaryTable() {
 131         if (summaryTable == null) {
 132             summaryTable = createSummaryTable();
 133         }
 134         return summaryTable;
 135     }
 136 
 137     /**
 138      * Create the summary table for this element.
 139      * The table should be created and initialized if needed, and configured
 140      * so that it is ready to add content with {@link Table#addRow(Content[])}
 141      * and similar methods.
 142      *
 143      * @return the summary table
 144      */
 145     protected abstract Table createSummaryTable();
 146 
 147 
 148 
 149     /**
 150      * Add inherited summary label for the member.
 151      *
 152      * @param typeElement the TypeElement to which to link to
 153      * @param inheritedTree the content tree to which the inherited summary label will be added
 154      */
 155     public abstract void addInheritedSummaryLabel(TypeElement typeElement, Content inheritedTree);
 156 
 157     /**
 158      * Add the summary type for the member.
 159      *
 160      * @param member the member to be documented
 161      * @param tdSummaryType the content tree to which the type will be added
 162      */
 163     protected abstract void addSummaryType(Element member, Content tdSummaryType);
 164 
 165     /**
 166      * Add the summary link for the member.
 167      *
 168      * @param typeElement the TypeElement to be documented
 169      * @param member the member to be documented
 170      * @param tdSummary the content tree to which the link will be added
 171      */
 172     protected void addSummaryLink(TypeElement typeElement, Element member, Content tdSummary) {
 173         addSummaryLink(LinkInfoImpl.Kind.MEMBER, typeElement, member, tdSummary);
 174     }
 175 
 176     /**
 177      * Add the summary link for the member.
 178      *
 179      * @param context the id of the context where the link will be printed
 180      * @param typeElement the TypeElement to be documented
 181      * @param member the member to be documented
 182      * @param tdSummary the content tree to which the summary link will be added
 183      */
 184     protected abstract void addSummaryLink(LinkInfoImpl.Kind context,
 185             TypeElement typeElement, Element member, Content tdSummary);
 186 
 187     /**
 188      * Add the inherited summary link for the member.
 189      *
 190      * @param typeElement the TypeElement to be documented
 191      * @param member the member to be documented
 192      * @param linksTree the content tree to which the inherited summary link will be added
 193      */
 194     protected abstract void addInheritedSummaryLink(TypeElement typeElement,
 195             Element member, Content linksTree);
 196 
 197     /**
 198      * Get the deprecated link.
 199      *
 200      * @param member the member being linked to
 201      * @return a content tree representing the link
 202      */
 203     protected abstract Content getDeprecatedLink(Element member);
 204 
 205     protected CharSequence makeSpace(int len) {
 206         if (len <= 0) {
 207             return "";
 208         }
 209         StringBuilder sb = new StringBuilder(len);
 210         for (int i = 0; i < len; i++) {
 211             sb.append(' ');
 212         }
 213         return sb;
 214     }
 215 
 216     /**
 217      * Add the modifier and type for the member in the member summary.
 218      *
 219      * @param member the member to add the type for
 220      * @param type the type to add
 221      * @param tdSummaryType the content tree to which the modified and type will be added
 222      */
 223     protected void addModifierAndType(Element member, TypeMirror type,
 224             Content tdSummaryType) {
 225         HtmlTree code = new HtmlTree(HtmlTag.CODE);
 226         addModifier(member, code);
 227         if (type == null) {
 228             code.add(utils.isClass(member) ? "class" : "interface");
 229             code.add(Entity.NO_BREAK_SPACE);
 230         } else {
 231             List<? extends TypeParameterElement> list = utils.isExecutableElement(member)
 232                     ? ((ExecutableElement)member).getTypeParameters()
 233                     : null;
 234             if (list != null && !list.isEmpty()) {
 235                 Content typeParameters = ((AbstractExecutableMemberWriter) this)
 236                         .getTypeParameters((ExecutableElement)member);
 237                     code.add(typeParameters);
 238                 //Code to avoid ugly wrapping in member summary table.
 239                 if (typeParameters.charCount() > 10) {
 240                     code.add(new HtmlTree(HtmlTag.BR));
 241                 } else {
 242                     code.add(Entity.NO_BREAK_SPACE);
 243                 }
 244                 code.add(
 245                         writer.getLink(new LinkInfoImpl(configuration,
 246                         LinkInfoImpl.Kind.SUMMARY_RETURN_TYPE, type)));
 247             } else {
 248                 code.add(
 249                         writer.getLink(new LinkInfoImpl(configuration,
 250                         LinkInfoImpl.Kind.SUMMARY_RETURN_TYPE, type)));
 251             }
 252 
 253         }
 254         tdSummaryType.add(code);
 255     }
 256 
 257     /**
 258      * Add the modifier for the member.
 259      *
 260      * @param member the member to add the type for
 261      * @param code the content tree to which the modified will be added
 262      */
 263     private void addModifier(Element member, Content code) {
 264         if (utils.isProtected(member)) {
 265             code.add("protected ");
 266         } else if (utils.isPrivate(member)) {
 267             code.add("private ");
 268         } else if (!utils.isPublic(member)) { // Package private
 269             code.add(resources.getText("doclet.Package_private"));
 270             code.add(" ");
 271         }
 272         boolean isAnnotatedTypeElement = utils.isAnnotationType(member.getEnclosingElement());
 273         if (!isAnnotatedTypeElement && utils.isMethod(member)) {
 274             if (!utils.isInterface(member.getEnclosingElement()) && utils.isAbstract(member)) {
 275                 code.add("abstract ");
 276             }
 277             if (utils.isDefault(member)) {
 278                 code.add("default ");
 279             }
 280         }
 281         if (utils.isStatic(member)) {
 282             code.add("static ");
 283         }
 284     }
 285 
 286     /**
 287      * Add the deprecated information for the given member.
 288      *
 289      * @param member the member being documented.
 290      * @param contentTree the content tree to which the deprecated information will be added.
 291      */
 292     protected void addDeprecatedInfo(Element member, Content contentTree) {
 293         Content output = (new DeprecatedTaglet()).getTagletOutput(member,
 294             writer.getTagletWriterInstance(false));
 295         if (!output.isEmpty()) {
 296             Content deprecatedContent = output;
 297             Content div = HtmlTree.DIV(HtmlStyle.deprecationBlock, deprecatedContent);
 298             contentTree.add(div);
 299         }
 300     }
 301 
 302     /**
 303      * Add the comment for the given member.
 304      *
 305      * @param member the member being documented.
 306      * @param htmltree the content tree to which the comment will be added.
 307      */
 308     protected void addComment(Element member, Content htmltree) {
 309         if (!utils.getFullBody(member).isEmpty()) {
 310             writer.addInlineComment(member, htmltree);
 311         }
 312     }
 313 
 314     protected String name(Element member) {
 315         return utils.getSimpleName(member);
 316     }
 317 
 318     /**
 319     * Return true if the given <code>ProgramElement</code> is inherited
 320     * by the class that is being documented.
 321     *
 322     * @param ped The <code>ProgramElement</code> being checked.
 323     * return true if the <code>ProgramElement</code> is being inherited and
 324     * false otherwise.
 325      *@return true if inherited
 326     */
 327     protected boolean isInherited(Element ped){
 328         return (!utils.isPrivate(ped) &&
 329                 (!utils.isPackagePrivate(ped) ||
 330                     ped.getEnclosingElement().equals(ped.getEnclosingElement())));
 331     }
 332 
 333     /**
 334      * Add use information to the documentation tree.
 335      *
 336      * @param mems list of program elements for which the use information will be added
 337      * @param heading the section heading
 338      * @param contentTree the content tree to which the use information will be added
 339      */
 340     protected void addUseInfo(List<? extends Element> mems, Content heading, Content contentTree) {
 341         if (mems == null || mems.isEmpty()) {
 342             return;
 343         }
 344         List<? extends Element> members = mems;
 345         boolean printedUseTableHeader = false;
 346         if (members.size() > 0) {
 347             Table useTable = new Table(HtmlStyle.useSummary)
 348                     .setCaption(heading)
 349                     .setRowScopeColumn(1)
 350                     .setColumnStyles(HtmlStyle.colFirst, HtmlStyle.colSecond, HtmlStyle.colLast);
 351             for (Element element : members) {
 352                 TypeElement te = (typeElement == null)
 353                         ? utils.getEnclosingTypeElement(element)
 354                         : typeElement;
 355                 if (!printedUseTableHeader) {
 356                     useTable.setHeader(getSummaryTableHeader(element));
 357                     printedUseTableHeader = true;
 358                 }
 359                 Content summaryType = new ContentBuilder();
 360                 addSummaryType(element, summaryType);
 361                 Content typeContent = new ContentBuilder();
 362                 if (te != null
 363                         && !utils.isConstructor(element)
 364                         && !utils.isClass(element)
 365                         && !utils.isInterface(element)
 366                         && !utils.isAnnotationType(element)) {
 367                     HtmlTree name = new HtmlTree(HtmlTag.SPAN);
 368                     name.setStyle(HtmlStyle.typeNameLabel);
 369                     name.add(name(te) + ".");
 370                     typeContent.add(name);
 371                 }
 372                 addSummaryLink(utils.isClass(element) || utils.isInterface(element)
 373                         ? LinkInfoImpl.Kind.CLASS_USE
 374                         : LinkInfoImpl.Kind.MEMBER,
 375                         te, element, typeContent);
 376                 Content desc = new ContentBuilder();
 377                 writer.addSummaryLinkComment(this, element, desc);
 378                 useTable.addRow(summaryType, typeContent, desc);
 379             }
 380             contentTree.add(useTable.toContent());
 381         }
 382     }
 383 
 384     protected void serialWarning(Element e, String key, String a1, String a2) {
 385         if (configuration.getOptions().serialWarn) {
 386             configuration.messages.warning(e, key, a1, a2);
 387         }
 388     }
 389 
 390     /**
 391      * Add the member summary for the given class.
 392      *
 393      * @param tElement the class that is being documented
 394      * @param member the member being documented
 395      * @param firstSentenceTags the first sentence tags to be added to the summary
 396      */
 397     @Override
 398     public void addMemberSummary(TypeElement tElement, Element member,
 399             List<? extends DocTree> firstSentenceTags) {
 400         if (tElement != typeElement) {
 401             throw new IllegalStateException();
 402         }
 403         Table table = getSummaryTable();
 404         List<Content> rowContents = new ArrayList<>();
 405         Content summaryType = new ContentBuilder();
 406         addSummaryType(member, summaryType);
 407         if (!summaryType.isEmpty())
 408             rowContents.add(summaryType);
 409         Content summaryLink = new ContentBuilder();
 410         addSummaryLink(tElement, member, summaryLink);
 411         rowContents.add(summaryLink);
 412         Content desc = new ContentBuilder();
 413         writer.addSummaryLinkComment(this, member, firstSentenceTags, desc);
 414         rowContents.add(desc);
 415         table.addRow(member, rowContents);
 416     }
 417 
 418     /**
 419      * Add inherited member summary for the given class and member.
 420      *
 421      * @param tElement the class the inherited member belongs to
 422      * @param nestedClass the inherited member that is summarized
 423      * @param isFirst true if this is the first member in the list
 424      * @param isLast true if this is the last member in the list
 425      * @param linksTree the content tree to which the summary will be added
 426      */
 427     @Override
 428     public void addInheritedMemberSummary(TypeElement tElement,
 429             Element nestedClass, boolean isFirst, boolean isLast,
 430             Content linksTree) {
 431         writer.addInheritedMemberSummary(this, tElement, nestedClass, isFirst,
 432                 linksTree);
 433     }
 434 
 435     /**
 436      * Get the inherited summary header for the given class.
 437      *
 438      * @param tElement the class the inherited member belongs to
 439      * @return a content tree for the inherited summary header
 440      */
 441     @Override
 442     public Content getInheritedSummaryHeader(TypeElement tElement) {
 443         Content inheritedTree = writer.getMemberInheritedTree();
 444         writer.addInheritedSummaryHeader(this, tElement, inheritedTree);
 445         return inheritedTree;
 446     }
 447 
 448     /**
 449      * Get the inherited summary links tree.
 450      *
 451      * @return a content tree for the inherited summary links
 452      */
 453     @Override
 454     public Content getInheritedSummaryLinksTree() {
 455         return new HtmlTree(HtmlTag.CODE);
 456     }
 457 
 458     /**
 459      * Get the summary table tree for the given class.
 460      *
 461      * @param tElement the class for which the summary table is generated
 462      * @return a content tree for the summary table
 463      */
 464     @Override
 465     public Content getSummaryTableTree(TypeElement tElement) {
 466         if (tElement != typeElement) {
 467             throw new IllegalStateException();
 468         }
 469         Table table = getSummaryTable();
 470         if (table.needsScript()) {
 471             writer.getMainBodyScript().append(table.getScript());
 472         }
 473         return table.toContent();
 474     }
 475 
 476     /**
 477      * Get the member tree to be documented.
 478      *
 479      * @param memberTree the content tree of member to be documented
 480      * @return a content tree that will be added to the class documentation
 481      */
 482     @Override
 483     public Content getMemberTree(Content memberTree) {
 484         return writer.getMemberTree(memberTree);
 485     }
 486 
 487     /**
 488      * A content builder for member signatures.
 489      */
 490     class MemberSignature {
 491 
 492         private Element element;
 493         private Content typeParameters;
 494         private Content returnType;
 495         private Content parameters;
 496         private Content exceptions;
 497 
 498         // Threshold for length of type parameters before switching from inline to block representation.
 499         private static final int TYPE_PARAMS_MAX_INLINE_LENGTH = 50;
 500 
 501         // Threshold for combined length of modifiers, type params and return type before breaking
 502         // it up with a line break before the return type.
 503         private static final int RETURN_TYPE_MAX_LINE_LENGTH = 50;
 504 
 505         /**
 506          * Create a new member signature builder.
 507          *
 508          * @param element The element for which to create a signature.
 509          */
 510         MemberSignature(Element element) {
 511             this.element = element;
 512         }
 513 
 514         /**
 515          * Add the type parameters for an executable member.
 516          *
 517          * @param typeParameters the content tree containing the type parameters to add.
 518          * @return this MemberSignature instance
 519          */
 520         MemberSignature addTypeParameters(Content typeParameters) {
 521             this.typeParameters = typeParameters;
 522             return this;
 523         }
 524 
 525         /**
 526          * Add the return type for an executable member.
 527          *
 528          * @param returnType the content tree containing the return type to add.
 529          * @return this MemberSignature instance
 530          */
 531         MemberSignature addReturnType(Content returnType) {
 532             this.returnType = returnType;
 533             return this;
 534         }
 535 
 536         /**
 537          * Add the type information for a non-executable member.
 538          *
 539          * @param type the type of the member.
 540          * @return this MemberSignature instance
 541          */
 542         MemberSignature addType(TypeMirror type) {
 543             this.returnType = writer.getLink(new LinkInfoImpl(configuration, LinkInfoImpl.Kind.MEMBER, type));
 544             return this;
 545         }
 546 
 547         /**
 548          * Add the parameter information of an executable member.
 549          *
 550          * @param paramTree the content tree containing the parameter information.
 551          * @return this MemberSignature instance
 552          */
 553         MemberSignature addParameters(Content paramTree) {
 554             this.parameters = paramTree;
 555             return this;
 556         }
 557 
 558         /**
 559          * Add the exception information of an executable member.
 560          *
 561          * @param exceptionTree the content tree containing the exception information
 562          * @return this MemberSignature instance
 563          */
 564         MemberSignature addExceptions(Content exceptionTree) {
 565             this.exceptions = exceptionTree;
 566             return this;
 567         }
 568 
 569         /**
 570          * Return a HTML tree containing the member signature.
 571          *
 572          * @return a HTML tree containing the member signature
 573          */
 574         Content toContent() {
 575             Content content = new ContentBuilder();
 576             // Position of last line separator.
 577             int lastLineSeparator = 0;
 578 
 579             // Annotations
 580             Content annotationInfo = writer.getAnnotationInfo(element.getAnnotationMirrors(), true);
 581             if (!annotationInfo.isEmpty()) {
 582                 content.add(HtmlTree.SPAN(HtmlStyle.annotations, annotationInfo));
 583                 lastLineSeparator = content.charCount();
 584             }
 585 
 586             // Modifiers
 587             appendModifiers(content);
 588 
 589             // Type parameters
 590             if (typeParameters != null && !typeParameters.isEmpty()) {
 591                 lastLineSeparator = appendTypeParameters(content, lastLineSeparator);
 592             }
 593 
 594             // Return type
 595             if (returnType != null) {
 596                 content.add(HtmlTree.SPAN(HtmlStyle.returnType, returnType));
 597                 content.add(Entity.NO_BREAK_SPACE);
 598             }
 599 
 600             // Name
 601             HtmlTree nameSpan = new HtmlTree(HtmlTag.SPAN);
 602             nameSpan.setStyle(HtmlStyle.memberName);
 603             if (configuration.getOptions().linkSource) {
 604                 Content name = new StringContent(name(element));
 605                 writer.addSrcLink(element, name, nameSpan);
 606             } else {
 607                 nameSpan.add(name(element));
 608             }
 609             content.add(nameSpan);
 610 
 611 
 612             // Parameters and exceptions
 613             if (parameters != null) {
 614                 appendParametersAndExceptions(content, lastLineSeparator);
 615             }
 616 
 617             return HtmlTree.DIV(HtmlStyle.memberSignature, content);
 618         }
 619 
 620         /**
 621          * Add the modifier for the member. The modifiers are ordered as specified
 622          * by <em>The Java Language Specification</em>.
 623          *
 624          * @param htmltree the content tree to which the modifier information will be added.
 625          */
 626         private void appendModifiers(Content htmltree) {
 627             Set<Modifier> set = new TreeSet<>(element.getModifiers());
 628 
 629             // remove the ones we really don't need
 630             set.remove(NATIVE);
 631             set.remove(SYNCHRONIZED);
 632             set.remove(STRICTFP);
 633 
 634             // According to JLS, we should not be showing public modifier for
 635             // interface methods.
 636             if ((utils.isField(element) || utils.isMethod(element))
 637                     && ((writer instanceof ClassWriterImpl
 638                     && utils.isInterface(((ClassWriterImpl) writer).getTypeElement())  ||
 639                     writer instanceof AnnotationTypeWriterImpl) )) {
 640                 // Remove the implicit abstract and public modifiers
 641                 if (utils.isMethod(element) &&
 642                         (utils.isInterface(element.getEnclosingElement()) ||
 643                                 utils.isAnnotationType(element.getEnclosingElement()))) {
 644                     set.remove(ABSTRACT);
 645                     set.remove(PUBLIC);
 646                 }
 647                 if (!utils.isMethod(element)) {
 648                     set.remove(PUBLIC);
 649                 }
 650             }
 651             if (!set.isEmpty()) {
 652                 String mods = set.stream().map(Modifier::toString).collect(Collectors.joining(" "));
 653                 htmltree.add(HtmlTree.SPAN(HtmlStyle.modifiers, new StringContent(mods)));
 654                 htmltree.add(Entity.NO_BREAK_SPACE);
 655             }
 656         }
 657 
 658         /**
 659          * Append the type parameter information to the HTML tree.
 660          *
 661          * @param htmltree the HTML tree
 662          * @param lastLineSeparator index of last line separator in HTML tree
 663          * @return the new index of the last line separator
 664          */
 665         private int appendTypeParameters(Content htmltree, int lastLineSeparator) {
 666             // Apply different wrapping strategies for type parameters
 667             // depending of combined length of type parameters and return type.
 668             int typeParamLength = typeParameters.charCount();
 669 
 670             if (typeParamLength >= TYPE_PARAMS_MAX_INLINE_LENGTH) {
 671                 htmltree.add(HtmlTree.SPAN(HtmlStyle.typeParametersLong, typeParameters));
 672             } else {
 673                 htmltree.add(HtmlTree.SPAN(HtmlStyle.typeParameters, typeParameters));
 674             }
 675 
 676             int lineLength = htmltree.charCount() - lastLineSeparator;
 677             int newLastLineSeparator = lastLineSeparator;
 678 
 679             // sum below includes length of modifiers plus type params added above
 680             if (lineLength + returnType.charCount()> RETURN_TYPE_MAX_LINE_LENGTH) {
 681                 htmltree.add(DocletConstants.NL);
 682                 newLastLineSeparator = htmltree.charCount();
 683             } else {
 684                 htmltree.add(Entity.NO_BREAK_SPACE);
 685             }
 686 
 687             return newLastLineSeparator;
 688         }
 689 
 690         /**
 691          * Append the parameters and exceptions information to the HTML tree.
 692          *
 693          * @param htmltree the HTML tree
 694          * @param lastLineSeparator the index of the last line separator in HTML tree
 695          */
 696         private void appendParametersAndExceptions(Content htmltree, int lastLineSeparator) {
 697             // Record current position for indentation of exceptions
 698             int indentSize = htmltree.charCount() - lastLineSeparator;
 699 
 700             if (parameters.isEmpty()) {
 701                 htmltree.add("()");
 702             } else {
 703                 parameters.add(")");
 704                 htmltree.add(Entity.ZERO_WIDTH_SPACE);
 705                 htmltree.add("(");
 706                 htmltree.add(HtmlTree.SPAN(HtmlStyle.arguments, parameters));
 707             }
 708 
 709             // Exceptions
 710             if (exceptions != null && !exceptions.isEmpty()) {
 711                 CharSequence indent = makeSpace(indentSize + 1 - 7);
 712                 htmltree.add(DocletConstants.NL);
 713                 htmltree.add(indent);
 714                 htmltree.add("throws ");
 715                 htmltree.add(HtmlTree.SPAN(HtmlStyle.exceptions, exceptions));
 716             }
 717         }
 718     }
 719 }