1 /* 2 * Copyright (c) 1997, 2017, 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.*; 29 import java.util.stream.Collectors; 30 31 import javax.lang.model.element.Element; 32 import javax.lang.model.element.ExecutableElement; 33 import javax.lang.model.element.Modifier; 34 import javax.lang.model.element.TypeElement; 35 import javax.lang.model.element.TypeParameterElement; 36 import javax.lang.model.type.TypeMirror; 37 38 import com.sun.source.doctree.DocTree; 39 import jdk.javadoc.internal.doclets.formats.html.TableHeader; 40 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlAttr; 41 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlConstants; 42 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; 43 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag; 44 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; 45 import jdk.javadoc.internal.doclets.formats.html.markup.StringContent; 46 import jdk.javadoc.internal.doclets.toolkit.Content; 47 import jdk.javadoc.internal.doclets.toolkit.Resources; 48 import jdk.javadoc.internal.doclets.toolkit.taglets.DeprecatedTaglet; 49 import jdk.javadoc.internal.doclets.toolkit.util.MethodTypes; 50 import jdk.javadoc.internal.doclets.toolkit.util.Utils; 51 import jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberMap; 52 53 import static javax.lang.model.element.Modifier.*; 54 55 /** 56 * The base class for member writers. 57 * 58 * <p><b>This is NOT part of any supported API. 59 * If you write code that depends on this, you do so at your own risk. 60 * This code and its internal interfaces are subject to change or 61 * deletion without notice.</b> 62 * 63 * @author Robert Field 64 * @author Atul M Dambalkar 65 * @author Jamie Ho (Re-write) 66 * @author Bhavesh Patel (Modified) 67 */ 68 public abstract class AbstractMemberWriter { 69 70 protected final HtmlConfiguration configuration; 71 protected final Utils utils; 72 protected final SubWriterHolderWriter writer; 73 protected final Contents contents; 74 protected final Resources resources; 75 76 protected final TypeElement typeElement; 77 protected Map<String, Integer> typeMap = new LinkedHashMap<>(); 78 protected Set<MethodTypes> methodTypes = EnumSet.noneOf(MethodTypes.class); 79 private int methodTypesOr = 0; 80 public final boolean nodepr; 81 82 protected boolean printedSummaryHeader = false; 83 84 public AbstractMemberWriter(SubWriterHolderWriter writer, TypeElement typeElement) { 85 this.configuration = writer.configuration; 86 this.writer = writer; 87 this.nodepr = configuration.nodeprecated; 88 this.typeElement = typeElement; 89 this.utils = configuration.utils; 90 this.contents = configuration.contents; 91 this.resources = configuration.resources; 92 } 93 94 public AbstractMemberWriter(SubWriterHolderWriter writer) { 95 this(writer, null); 96 } 97 98 /*** abstracts ***/ 99 100 /** 101 * Add the summary label for the member. 102 * 103 * @param memberTree the content tree to which the label will be added 104 */ 105 public abstract void addSummaryLabel(Content memberTree); 106 107 /** 108 * Get the summary for the member summary table. 109 * 110 * @return a string for the table summary 111 */ 112 public abstract String getTableSummary(); 113 114 /** 115 * Get the caption for the member summary table. 116 * 117 * @return a string for the table caption 118 */ 119 public abstract Content getCaption(); 120 121 /** 122 * Get the summary table header for the member. 123 * 124 * @param member the member to be documented 125 * @return the summary table header 126 */ 127 public abstract TableHeader getSummaryTableHeader(Element member); 128 129 /** 130 * Add inherited summary label for the member. 131 * 132 * @param typeElement the TypeElement to which to link to 133 * @param inheritedTree the content tree to which the inherited summary label will be added 134 */ 135 public abstract void addInheritedSummaryLabel(TypeElement typeElement, Content inheritedTree); 136 137 /** 138 * Add the anchor for the summary section of the member. 139 * 140 * @param typeElement the TypeElement to be documented 141 * @param memberTree the content tree to which the summary anchor will be added 142 */ 143 public abstract void addSummaryAnchor(TypeElement typeElement, Content memberTree); 144 145 /** 146 * Add the anchor for the inherited summary section of the member. 147 * 148 * @param typeElement the TypeElement to be documented 149 * @param inheritedTree the content tree to which the inherited summary anchor will be added 150 */ 151 public abstract void addInheritedSummaryAnchor(TypeElement typeElement, Content inheritedTree); 152 153 /** 154 * Add the summary type for the member. 155 * 156 * @param member the member to be documented 157 * @param tdSummaryType the content tree to which the type will be added 158 */ 159 protected abstract void addSummaryType(Element member, Content tdSummaryType); 160 161 /** 162 * Add the summary link for the member. 163 * 164 * @param typeElement the TypeElement to be documented 165 * @param member the member to be documented 166 * @param tdSummary the content tree to which the link will be added 167 */ 168 protected void addSummaryLink(TypeElement typeElement, Element member, Content tdSummary) { 169 addSummaryLink(LinkInfoImpl.Kind.MEMBER, typeElement, member, tdSummary); 170 } 171 172 /** 173 * Add the summary link for the member. 174 * 175 * @param context the id of the context where the link will be printed 176 * @param typeElement the TypeElement to be documented 177 * @param member the member to be documented 178 * @param tdSummary the content tree to which the summary link will be added 179 */ 180 protected abstract void addSummaryLink(LinkInfoImpl.Kind context, 181 TypeElement typeElement, Element member, Content tdSummary); 182 183 /** 184 * Add the inherited summary link for the member. 185 * 186 * @param typeElement the TypeElement to be documented 187 * @param member the member to be documented 188 * @param linksTree the content tree to which the inherited summary link will be added 189 */ 190 protected abstract void addInheritedSummaryLink(TypeElement typeElement, 191 Element member, Content linksTree); 192 193 /** 194 * Get the deprecated link. 195 * 196 * @param member the member being linked to 197 * @return a content tree representing the link 198 */ 199 protected abstract Content getDeprecatedLink(Element member); 200 201 /** 202 * Get the navigation summary link. 203 * 204 * @param typeElement the TypeElement to be documented 205 * @param link true if its a link else the label to be printed 206 * @return a content tree for the navigation summary link. 207 */ 208 protected abstract Content getNavSummaryLink(TypeElement typeElement, boolean link); 209 210 /** 211 * Add the navigation detail link. 212 * 213 * @param link true if its a link else the label to be printed 214 * @param liNav the content tree to which the navigation detail link will be added 215 */ 216 protected abstract void addNavDetailLink(boolean link, Content liNav); 217 218 /** 219 * Add the member name to the content tree. 220 * 221 * @param name the member name to be added to the content tree. 222 * @param htmltree the content tree to which the name will be added. 223 */ 224 protected void addName(String name, Content htmltree) { 225 htmltree.addContent(name); 226 } 227 228 /** 229 * Add the modifier for the member. The modifiers are ordered as specified 230 * by <em>The Java Language Specification</em>. 231 * 232 * @param member the member for which teh modifier will be added. 233 * @param htmltree the content tree to which the modifier information will be added. 234 */ 235 protected void addModifiers(Element member, Content htmltree) { 236 Set<Modifier> set = new TreeSet<>(member.getModifiers()); 237 238 // remove the ones we really don't need 239 set.remove(NATIVE); 240 set.remove(SYNCHRONIZED); 241 set.remove(STRICTFP); 242 243 // According to JLS, we should not be showing public modifier for 244 // interface methods. 245 if ((utils.isField(member) || utils.isMethod(member)) 246 && ((writer instanceof ClassWriterImpl 247 && utils.isInterface(((ClassWriterImpl) writer).getTypeElement()) || 248 writer instanceof AnnotationTypeWriterImpl) )) { 249 // Remove the implicit abstract and public modifiers 250 if (utils.isMethod(member) && 251 (utils.isInterface(member.getEnclosingElement()) || 252 utils.isAnnotationType(member.getEnclosingElement()))) { 253 set.remove(ABSTRACT); 254 set.remove(PUBLIC); 255 } 256 if (!utils.isMethod(member)) { 257 set.remove(PUBLIC); 258 } 259 } 260 if (!set.isEmpty()) { 261 String mods = set.stream().map(Modifier::toString).collect(Collectors.joining(" ")); 262 htmltree.addContent(mods); 263 htmltree.addContent(Contents.SPACE); 264 } 265 } 266 267 protected CharSequence makeSpace(int len) { 268 if (len <= 0) { 269 return ""; 270 } 271 StringBuilder sb = new StringBuilder(len); 272 for (int i = 0; i < len; i++) { 273 sb.append(' '); 274 } 275 return sb; 276 } 277 278 /** 279 * Add the modifier and type for the member in the member summary. 280 * 281 * @param member the member to add the type for 282 * @param type the type to add 283 * @param tdSummaryType the content tree to which the modified and type will be added 284 */ 285 protected void addModifierAndType(Element member, TypeMirror type, 286 Content tdSummaryType) { 287 HtmlTree code = new HtmlTree(HtmlTag.CODE); 288 addModifier(member, code); 289 if (type == null) { 290 code.addContent(utils.isClass(member) ? "class" : "interface"); 291 code.addContent(Contents.SPACE); 292 } else { 293 List<? extends TypeParameterElement> list = utils.isExecutableElement(member) 294 ? ((ExecutableElement)member).getTypeParameters() 295 : null; 296 if (list != null && !list.isEmpty()) { 297 Content typeParameters = ((AbstractExecutableMemberWriter) this) 298 .getTypeParameters((ExecutableElement)member); 299 code.addContent(typeParameters); 300 //Code to avoid ugly wrapping in member summary table. 301 if (typeParameters.charCount() > 10) { 302 code.addContent(new HtmlTree(HtmlTag.BR)); 303 } else { 304 code.addContent(Contents.SPACE); 305 } 306 code.addContent( 307 writer.getLink(new LinkInfoImpl(configuration, 308 LinkInfoImpl.Kind.SUMMARY_RETURN_TYPE, type))); 309 } else { 310 code.addContent( 311 writer.getLink(new LinkInfoImpl(configuration, 312 LinkInfoImpl.Kind.SUMMARY_RETURN_TYPE, type))); 313 } 314 315 } 316 tdSummaryType.addContent(code); 317 } 318 319 /** 320 * Add the modifier for the member. 321 * 322 * @param member the member to add the type for 323 * @param code the content tree to which the modified will be added 324 */ 325 private void addModifier(Element member, Content code) { 326 if (utils.isProtected(member)) { 327 code.addContent("protected "); 328 } else if (utils.isPrivate(member)) { 329 code.addContent("private "); 330 } else if (!utils.isPublic(member)) { // Package private 331 code.addContent(configuration.getText("doclet.Package_private")); 332 code.addContent(" "); 333 } 334 boolean isAnnotatedTypeElement = utils.isAnnotationType(member.getEnclosingElement()); 335 if (!isAnnotatedTypeElement && utils.isMethod(member)) { 336 if (!utils.isInterface(member.getEnclosingElement()) && utils.isAbstract(member)) { 337 code.addContent("abstract "); 338 } 339 if (utils.isDefault(member)) { 340 code.addContent("default "); 341 } 342 } 343 if (utils.isStatic(member)) { 344 code.addContent("static "); 345 } 346 } 347 348 /** 349 * Add the deprecated information for the given member. 350 * 351 * @param member the member being documented. 352 * @param contentTree the content tree to which the deprecated information will be added. 353 */ 354 protected void addDeprecatedInfo(Element member, Content contentTree) { 355 Content output = (new DeprecatedTaglet()).getTagletOutput(member, 356 writer.getTagletWriterInstance(false)); 357 if (!output.isEmpty()) { 358 Content deprecatedContent = output; 359 Content div = HtmlTree.DIV(HtmlStyle.deprecationBlock, deprecatedContent); 360 contentTree.addContent(div); 361 } 362 } 363 364 /** 365 * Add the comment for the given member. 366 * 367 * @param member the member being documented. 368 * @param htmltree the content tree to which the comment will be added. 369 */ 370 protected void addComment(Element member, Content htmltree) { 371 if (!utils.getFullBody(member).isEmpty()) { 372 writer.addInlineComment(member, htmltree); 373 } 374 } 375 376 protected String name(Element member) { 377 return utils.getSimpleName(member); 378 } 379 380 /** 381 * Get the header for the section. 382 * 383 * @param member the member being documented. 384 * @return a header content for the section. 385 */ 386 protected Content getHead(Element member) { 387 Content memberContent = new StringContent(name(member)); 388 Content heading = HtmlTree.HEADING(HtmlConstants.MEMBER_HEADING, memberContent); 389 return heading; 390 } 391 392 /** 393 * Return true if the given <code>ProgramElement</code> is inherited 394 * by the class that is being documented. 395 * 396 * @param ped The <code>ProgramElement</code> being checked. 397 * return true if the <code>ProgramElement</code> is being inherited and 398 * false otherwise. 399 *@return true if inherited 400 */ 401 protected boolean isInherited(Element ped){ 402 return (!utils.isPrivate(ped) && 403 (!utils.isPackagePrivate(ped) || 404 ped.getEnclosingElement().equals(ped.getEnclosingElement()))); 405 } 406 407 /** 408 * Add use information to the documentation tree. 409 * 410 * @param mems list of program elements for which the use information will be added 411 * @param heading the section heading 412 * @param tableSummary the summary for the use table 413 * @param contentTree the content tree to which the use information will be added 414 */ 415 protected void addUseInfo(List<? extends Element> mems, 416 Content heading, String tableSummary, Content contentTree) { 417 if (mems == null || mems.isEmpty()) { 418 return; 419 } 420 List<? extends Element> members = mems; 421 boolean printedUseTableHeader = false; 422 if (members.size() > 0) { 423 Content caption = writer.getTableCaption(heading); 424 Content table = (configuration.isOutputHtml5()) 425 ? HtmlTree.TABLE(HtmlStyle.useSummary, caption) 426 : HtmlTree.TABLE(HtmlStyle.useSummary, tableSummary, caption); 427 Content tbody = new HtmlTree(HtmlTag.TBODY); 428 boolean altColor = true; 429 for (Element element : members) { 430 TypeElement te = utils.getEnclosingTypeElement(element); 431 if (!printedUseTableHeader) { 432 table.addContent(getSummaryTableHeader(element).toContent()); 433 printedUseTableHeader = true; 434 } 435 HtmlTree tr = new HtmlTree(HtmlTag.TR); 436 tr.addStyle(altColor ? HtmlStyle.altColor : HtmlStyle.rowColor); 437 altColor = !altColor; 438 HtmlTree tdFirst = new HtmlTree(HtmlTag.TD); 439 tdFirst.addStyle(HtmlStyle.colFirst); 440 writer.addSummaryType(this, element, tdFirst); 441 tr.addContent(tdFirst); 442 HtmlTree thType = new HtmlTree(HtmlTag.TH); 443 thType.addStyle(HtmlStyle.colSecond); 444 thType.addAttr(HtmlAttr.SCOPE, "row"); 445 if (te != null 446 && !utils.isConstructor(element) 447 && !utils.isClass(element) 448 && !utils.isInterface(element) 449 && !utils.isAnnotationType(element)) { 450 HtmlTree name = new HtmlTree(HtmlTag.SPAN); 451 name.addStyle(HtmlStyle.typeNameLabel); 452 name.addContent(name(te) + "."); 453 thType.addContent(name); 454 } 455 addSummaryLink(utils.isClass(element) || utils.isInterface(element) 456 ? LinkInfoImpl.Kind.CLASS_USE 457 : LinkInfoImpl.Kind.MEMBER, 458 te, element, thType); 459 tr.addContent(thType); 460 HtmlTree tdDesc = new HtmlTree(HtmlTag.TD); 461 tdDesc.addStyle(HtmlStyle.colLast); 462 writer.addSummaryLinkComment(this, element, tdDesc); 463 tr.addContent(tdDesc); 464 tbody.addContent(tr); 465 } 466 table.addContent(tbody); 467 contentTree.addContent(table); 468 } 469 } 470 471 /** 472 * Add the navigation detail link. 473 * 474 * @param members the members to be linked 475 * @param liNav the content tree to which the navigation detail link will be added 476 */ 477 protected void addNavDetailLink(SortedSet<Element> members, Content liNav) { 478 addNavDetailLink(!members.isEmpty(), liNav); 479 } 480 481 /** 482 * Add the navigation summary link. 483 * 484 * @param members members to be linked 485 * @param visibleMemberMap the visible inherited members map 486 * @param liNav the content tree to which the navigation summary link will be added 487 */ 488 protected void addNavSummaryLink(SortedSet<? extends Element> members, 489 VisibleMemberMap visibleMemberMap, Content liNav) { 490 if (!members.isEmpty()) { 491 liNav.addContent(getNavSummaryLink(null, true)); 492 return; 493 } 494 495 TypeElement superClass = utils.getSuperClass(typeElement); 496 while (superClass != null) { 497 if (visibleMemberMap.hasMembers(superClass)) { 498 liNav.addContent(getNavSummaryLink(superClass, true)); 499 return; 500 } 501 superClass = utils.getSuperClass(superClass); 502 } 503 liNav.addContent(getNavSummaryLink(null, false)); 504 } 505 506 protected void serialWarning(Element e, String key, String a1, String a2) { 507 if (configuration.serialwarn) { 508 configuration.messages.warning(e, key, a1, a2); 509 } 510 } 511 512 /** 513 * Add the member summary for the given class. 514 * 515 * @param tElement the class that is being documented 516 * @param member the member being documented 517 * @param firstSentenceTags the first sentence tags to be added to the summary 518 * @param tableContents the list of contents to which the documentation will be added 519 * @param counter the counter for determining id and style for the table row 520 */ 521 public void addMemberSummary(TypeElement tElement, Element member, 522 List<? extends DocTree> firstSentenceTags, List<Content> tableContents, int counter) { 523 HtmlTree tdSummaryType = new HtmlTree(HtmlTag.TD); 524 tdSummaryType.addStyle(HtmlStyle.colFirst); 525 writer.addSummaryType(this, member, tdSummaryType); 526 HtmlTree tr = HtmlTree.TR(tdSummaryType); 527 HtmlTree thSummaryLink = new HtmlTree(HtmlTag.TH); 528 setSummaryColumnStyleAndScope(thSummaryLink); 529 addSummaryLink(tElement, member, thSummaryLink); 530 tr.addContent(thSummaryLink); 531 HtmlTree tdDesc = new HtmlTree(HtmlTag.TD); 532 tdDesc.addStyle(HtmlStyle.colLast); 533 writer.addSummaryLinkComment(this, member, firstSentenceTags, tdDesc); 534 tr.addContent(tdDesc); 535 if (utils.isMethod(member) && !utils.isAnnotationType(member) && !utils.isProperty(name(member))) { 536 int methodType = utils.isStatic(member) ? MethodTypes.STATIC.tableTabs().value() : 537 MethodTypes.INSTANCE.tableTabs().value(); 538 if (utils.isInterface(member.getEnclosingElement())) { 539 methodType = utils.isAbstract(member) 540 ? methodType | MethodTypes.ABSTRACT.tableTabs().value() 541 : methodType | MethodTypes.DEFAULT.tableTabs().value(); 542 } else { 543 methodType = utils.isAbstract(member) 544 ? methodType | MethodTypes.ABSTRACT.tableTabs().value() 545 : methodType | MethodTypes.CONCRETE.tableTabs().value(); 546 } 547 if (utils.isDeprecated(member) || utils.isDeprecated(typeElement)) { 548 methodType = methodType | MethodTypes.DEPRECATED.tableTabs().value(); 549 } 550 methodTypesOr = methodTypesOr | methodType; 551 String tableId = "i" + counter; 552 typeMap.put(tableId, methodType); 553 tr.addAttr(HtmlAttr.ID, tableId); 554 } 555 if (counter%2 == 0) 556 tr.addStyle(HtmlStyle.altColor); 557 else 558 tr.addStyle(HtmlStyle.rowColor); 559 tableContents.add(tr); 560 } 561 562 /** 563 * Generate the method types set and return true if the method summary table 564 * needs to show tabs. 565 * 566 * @return true if the table should show tabs 567 */ 568 public boolean showTabs() { 569 int value; 570 for (MethodTypes type : EnumSet.allOf(MethodTypes.class)) { 571 value = type.tableTabs().value(); 572 if ((value & methodTypesOr) == value) { 573 methodTypes.add(type); 574 } 575 } 576 boolean showTabs = methodTypes.size() > 1; 577 if (showTabs) { 578 methodTypes.add(MethodTypes.ALL); 579 } 580 return showTabs; 581 } 582 583 /** 584 * Set the style and scope attribute for the summary column. 585 * 586 * @param thTree the column for which the style and scope attribute will be set 587 */ 588 public void setSummaryColumnStyleAndScope(HtmlTree thTree) { 589 thTree.addStyle(HtmlStyle.colSecond); 590 thTree.addAttr(HtmlAttr.SCOPE, "row"); 591 } 592 593 /** 594 * Add inherited member summary for the given class and member. 595 * 596 * @param tElement the class the inherited member belongs to 597 * @param nestedClass the inherited member that is summarized 598 * @param isFirst true if this is the first member in the list 599 * @param isLast true if this is the last member in the list 600 * @param linksTree the content tree to which the summary will be added 601 */ 602 public void addInheritedMemberSummary(TypeElement tElement, 603 Element nestedClass, boolean isFirst, boolean isLast, 604 Content linksTree) { 605 writer.addInheritedMemberSummary(this, tElement, nestedClass, isFirst, 606 linksTree); 607 } 608 609 /** 610 * Get the inherited summary header for the given class. 611 * 612 * @param tElement the class the inherited member belongs to 613 * @return a content tree for the inherited summary header 614 */ 615 public Content getInheritedSummaryHeader(TypeElement tElement) { 616 Content inheritedTree = writer.getMemberTreeHeader(); 617 writer.addInheritedSummaryHeader(this, tElement, inheritedTree); 618 return inheritedTree; 619 } 620 621 /** 622 * Get the inherited summary links tree. 623 * 624 * @return a content tree for the inherited summary links 625 */ 626 public Content getInheritedSummaryLinksTree() { 627 return new HtmlTree(HtmlTag.CODE); 628 } 629 630 /** 631 * Get the summary table tree for the given class. 632 * 633 * @param tElement the class for which the summary table is generated 634 * @param tableContents list of contents to be displayed in the summary table 635 * @return a content tree for the summary table 636 */ 637 public Content getSummaryTableTree(TypeElement tElement, List<Content> tableContents) { 638 return writer.getSummaryTableTree(this, tElement, tableContents, showTabs()); 639 } 640 641 /** 642 * Get the member tree to be documented. 643 * 644 * @param memberTree the content tree of member to be documented 645 * @return a content tree that will be added to the class documentation 646 */ 647 public Content getMemberTree(Content memberTree) { 648 return writer.getMemberTree(memberTree); 649 } 650 651 /** 652 * Get the member tree to be documented. 653 * 654 * @param memberTree the content tree of member to be documented 655 * @param isLastContent true if the content to be added is the last content 656 * @return a content tree that will be added to the class documentation 657 */ 658 public Content getMemberTree(Content memberTree, boolean isLastContent) { 659 if (isLastContent) 660 return HtmlTree.UL(HtmlStyle.blockListLast, memberTree); 661 else 662 return HtmlTree.UL(HtmlStyle.blockList, memberTree); 663 } 664 }