1 /*
   2  * Copyright (c) 2003, 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.toolkit.builders;
  27 
  28 import java.text.MessageFormat;
  29 import java.util.*;
  30 
  31 import javax.lang.model.element.Element;
  32 import javax.lang.model.element.ExecutableElement;
  33 import javax.lang.model.element.TypeElement;
  34 import javax.lang.model.element.VariableElement;
  35 
  36 import com.sun.source.doctree.DocTree;
  37 import com.sun.source.doctree.DocTree.Kind;
  38 import jdk.javadoc.internal.doclets.toolkit.AnnotationTypeWriter;
  39 import jdk.javadoc.internal.doclets.toolkit.ClassWriter;
  40 import jdk.javadoc.internal.doclets.toolkit.Content;
  41 import jdk.javadoc.internal.doclets.toolkit.MemberSummaryWriter;
  42 import jdk.javadoc.internal.doclets.toolkit.WriterFactory;
  43 import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper;
  44 import jdk.javadoc.internal.doclets.toolkit.util.DocFinder;
  45 import jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberMap;
  46 import jdk.javadoc.internal.doclets.toolkit.CommentUtils;
  47 
  48 /**
  49  * Builds the member summary.
  50  * There are two anonymous subtype variants of this builder, created
  51  * in the {@link #getInstance} methods. One is for general types;
  52  * the other is for annotation types.
  53  *
  54  *  <p><b>This is NOT part of any supported API.
  55  *  If you write code that depends on this, you do so at your own risk.
  56  *  This code and its internal interfaces are subject to change or
  57  *  deletion without notice.</b>
  58  *
  59  * @author Jamie Ho
  60  * @author Bhavesh Patel (Modified)
  61  */
  62 public abstract class MemberSummaryBuilder extends AbstractMemberBuilder {
  63 
  64     /*
  65      * Comparator used to sort the members in the summary.
  66      */
  67     private final Comparator<Element> comparator;
  68 
  69     /**
  70      * The member summary writers for the given class.
  71      */
  72     private final EnumMap<VisibleMemberMap.Kind, MemberSummaryWriter> memberSummaryWriters;
  73 
  74     /**
  75      * The type being documented.
  76      */
  77     protected final TypeElement typeElement;
  78 
  79     /**
  80      * Construct a new MemberSummaryBuilder.
  81      *
  82      * @param context       the build context.
  83      * @param typeElement   the typeElement.
  84      */
  85     private MemberSummaryBuilder(Context context, TypeElement typeElement) {
  86         super(context);
  87         this.typeElement = typeElement;
  88         memberSummaryWriters = new EnumMap<>(VisibleMemberMap.Kind.class);
  89 
  90         comparator = utils.makeGeneralPurposeComparator();
  91     }
  92 
  93     /**
  94      * Construct a new MemberSummaryBuilder for a general type.
  95      *
  96      * @param classWriter   the writer for the class whose members are being
  97      *                      summarized.
  98      * @param context       the build context.
  99      * @return              the instance
 100      */
 101     public static MemberSummaryBuilder getInstance(
 102             ClassWriter classWriter, Context context) {
 103         MemberSummaryBuilder builder = new MemberSummaryBuilder(context, classWriter.getTypeElement()) {
 104             @Override
 105             public void build(Content contentTree) {
 106                 buildPropertiesSummary(contentTree);
 107                 buildNestedClassesSummary(contentTree);
 108                 buildEnumConstantsSummary(contentTree);
 109                 buildFieldsSummary(contentTree);
 110                 buildConstructorsSummary(contentTree);
 111                 buildMethodsSummary(contentTree);
 112             }
 113 
 114             @Override
 115             public boolean hasMembersToDocument() {
 116                 for (VisibleMemberMap.Kind kind : VisibleMemberMap.Kind.values()) {
 117                     VisibleMemberMap members = getVisibleMemberMap(kind);
 118                     if (!members.noVisibleMembers()) {
 119                         return true;
 120                     }
 121                 }
 122                 return false;
 123             }
 124         };
 125         WriterFactory wf = context.configuration.getWriterFactory();
 126         for (VisibleMemberMap.Kind kind : VisibleMemberMap.Kind.values()) {
 127             MemberSummaryWriter msw = builder.getVisibleMemberMap(kind).noVisibleMembers()
 128                     ? null
 129                     : wf.getMemberSummaryWriter(classWriter, kind);
 130             builder.memberSummaryWriters.put(kind, msw);
 131         }
 132         return builder;
 133     }
 134 
 135     /**
 136      * Construct a new MemberSummaryBuilder for an annotation type.
 137      *
 138      * @param annotationTypeWriter the writer for the class whose members are
 139      *                             being summarized.
 140      * @param context       the build context.
 141      * @return              the instance
 142      */
 143     public static MemberSummaryBuilder getInstance(
 144             AnnotationTypeWriter annotationTypeWriter, Context context) {
 145         MemberSummaryBuilder builder = new MemberSummaryBuilder(context,
 146                 annotationTypeWriter.getAnnotationTypeElement()) {
 147             @Override
 148             public void build(Content contentTree) {
 149                 buildAnnotationTypeFieldsSummary(contentTree);
 150                 buildAnnotationTypeRequiredMemberSummary(contentTree);
 151                 buildAnnotationTypeOptionalMemberSummary(contentTree);
 152             }
 153 
 154             @Override
 155             public boolean hasMembersToDocument() {
 156                 return !utils.getAnnotationMembers(typeElement).isEmpty();
 157             }
 158         };
 159         WriterFactory wf = context.configuration.getWriterFactory();
 160         for (VisibleMemberMap.Kind kind : VisibleMemberMap.Kind.values()) {
 161             MemberSummaryWriter msw = builder.getVisibleMemberMap(kind).noVisibleMembers()
 162                     ? null
 163                     : wf.getMemberSummaryWriter(annotationTypeWriter, kind);
 164             builder.memberSummaryWriters.put(kind, msw);
 165         }
 166         return builder;
 167     }
 168 
 169     /**
 170      * Return the specified visible member map.
 171      *
 172      * @param kind the kind of visible member map to return.
 173      * @return the specified visible member map.
 174      * @throws ArrayIndexOutOfBoundsException when the type is invalid.
 175      * @see VisibleMemberMap
 176      */
 177     public VisibleMemberMap getVisibleMemberMap(VisibleMemberMap.Kind kind) {
 178         return configuration.getVisibleMemberMap(typeElement, kind);
 179     }
 180 
 181     /**.
 182      * Return the specified member summary writer.
 183      *
 184      * @param kind the kind of member summary writer to return.
 185      * @return the specified member summary writer.
 186      * @throws ArrayIndexOutOfBoundsException when the type is invalid.
 187      * @see VisibleMemberMap
 188      */
 189     public MemberSummaryWriter getMemberSummaryWriter(VisibleMemberMap.Kind kind) {
 190         return memberSummaryWriters.get(kind);
 191     }
 192 
 193     /**
 194      * Returns a list of methods that will be documented for the given class.
 195      * This information can be used for doclet specific documentation
 196      * generation.
 197      *
 198      * @param kind the kind of elements to return.
 199      * @return a list of methods that will be documented.
 200      * @see VisibleMemberMap
 201      */
 202     public SortedSet<Element> members(VisibleMemberMap.Kind kind) {
 203         TreeSet<Element> out = new TreeSet<>(comparator);
 204         out.addAll(getVisibleMemberMap(kind).getLeafMembers());
 205         return out;
 206     }
 207 
 208     /**
 209      * Build the summary for the enum constants.
 210      *
 211      * @param memberSummaryTree the content tree to which the documentation will be added
 212      */
 213     protected void buildEnumConstantsSummary(Content memberSummaryTree) {
 214         MemberSummaryWriter writer =
 215                 memberSummaryWriters.get(VisibleMemberMap.Kind.ENUM_CONSTANTS);
 216         VisibleMemberMap visibleMemberMap =
 217                 getVisibleMemberMap(VisibleMemberMap.Kind.ENUM_CONSTANTS);
 218         addSummary(writer, visibleMemberMap, false, memberSummaryTree);
 219     }
 220 
 221     /**
 222      * Build the summary for fields.
 223      *
 224      * @param memberSummaryTree the content tree to which the documentation will be added
 225      */
 226     protected void buildAnnotationTypeFieldsSummary(Content memberSummaryTree) {
 227         MemberSummaryWriter writer =
 228                 memberSummaryWriters.get(VisibleMemberMap.Kind.ANNOTATION_TYPE_FIELDS);
 229         VisibleMemberMap visibleMemberMap =
 230                 getVisibleMemberMap(VisibleMemberMap.Kind.ANNOTATION_TYPE_FIELDS);
 231         addSummary(writer, visibleMemberMap, false, memberSummaryTree);
 232     }
 233 
 234     /**
 235      * Build the summary for the optional members.
 236      *
 237      * @param memberSummaryTree the content tree to which the documentation will be added
 238      */
 239     protected void buildAnnotationTypeOptionalMemberSummary(Content memberSummaryTree) {
 240         MemberSummaryWriter writer =
 241                 memberSummaryWriters.get(VisibleMemberMap.Kind.ANNOTATION_TYPE_MEMBER_OPTIONAL);
 242         VisibleMemberMap visibleMemberMap =
 243                 getVisibleMemberMap(VisibleMemberMap.Kind.ANNOTATION_TYPE_MEMBER_OPTIONAL);
 244         addSummary(writer, visibleMemberMap, false, memberSummaryTree);
 245     }
 246 
 247     /**
 248      * Build the summary for the optional members.
 249      *
 250      * @param memberSummaryTree the content tree to which the documentation will be added
 251      */
 252     protected void buildAnnotationTypeRequiredMemberSummary(Content memberSummaryTree) {
 253         MemberSummaryWriter writer =
 254                 memberSummaryWriters.get(VisibleMemberMap.Kind.ANNOTATION_TYPE_MEMBER_REQUIRED);
 255         VisibleMemberMap visibleMemberMap =
 256                 getVisibleMemberMap(VisibleMemberMap.Kind.ANNOTATION_TYPE_MEMBER_REQUIRED);
 257         addSummary(writer, visibleMemberMap, false, memberSummaryTree);
 258     }
 259 
 260     /**
 261      * Build the summary for the fields.
 262      *
 263      * @param memberSummaryTree the content tree to which the documentation will be added
 264      */
 265     protected void buildFieldsSummary(Content memberSummaryTree) {
 266         MemberSummaryWriter writer =
 267                 memberSummaryWriters.get(VisibleMemberMap.Kind.FIELDS);
 268         VisibleMemberMap visibleMemberMap =
 269                 getVisibleMemberMap(VisibleMemberMap.Kind.FIELDS);
 270         addSummary(writer, visibleMemberMap, true, memberSummaryTree);
 271     }
 272 
 273     /**
 274      * Build the summary for the fields.
 275      *
 276      * @param memberSummaryTree the content tree to which the documentation will be added
 277      */
 278     protected void buildPropertiesSummary(Content memberSummaryTree) {
 279         MemberSummaryWriter writer =
 280                 memberSummaryWriters.get(VisibleMemberMap.Kind.PROPERTIES);
 281         VisibleMemberMap visibleMemberMap =
 282                 getVisibleMemberMap(VisibleMemberMap.Kind.PROPERTIES);
 283         addSummary(writer, visibleMemberMap, true, memberSummaryTree);
 284     }
 285 
 286     /**
 287      * Build the summary for the nested classes.
 288      *
 289      * @param memberSummaryTree the content tree to which the documentation will be added
 290      */
 291     protected void buildNestedClassesSummary(Content memberSummaryTree) {
 292         MemberSummaryWriter writer =
 293                 memberSummaryWriters.get(VisibleMemberMap.Kind.INNER_CLASSES);
 294         VisibleMemberMap visibleMemberMap =
 295                 getVisibleMemberMap(VisibleMemberMap.Kind.INNER_CLASSES);
 296         addSummary(writer, visibleMemberMap, true, memberSummaryTree);
 297     }
 298 
 299     /**
 300      * Build the method summary.
 301      *
 302      * @param memberSummaryTree the content tree to which the documentation will be added
 303      */
 304     protected void buildMethodsSummary(Content memberSummaryTree) {
 305         MemberSummaryWriter writer =
 306                 memberSummaryWriters.get(VisibleMemberMap.Kind.METHODS);
 307         VisibleMemberMap visibleMemberMap =
 308                getVisibleMemberMap(VisibleMemberMap.Kind.METHODS);
 309         addSummary(writer, visibleMemberMap, true, memberSummaryTree);
 310     }
 311 
 312     /**
 313      * Build the constructor summary.
 314      *
 315      * @param memberSummaryTree the content tree to which the documentation will be added
 316      */
 317     protected void buildConstructorsSummary(Content memberSummaryTree) {
 318         MemberSummaryWriter writer =
 319                 memberSummaryWriters.get(VisibleMemberMap.Kind.CONSTRUCTORS);
 320         VisibleMemberMap visibleMemberMap =
 321                 getVisibleMemberMap(VisibleMemberMap.Kind.CONSTRUCTORS);
 322         addSummary(writer, visibleMemberMap, false, memberSummaryTree);
 323     }
 324 
 325     /**
 326      * Build the member summary for the given members.
 327      *
 328      * @param writer the summary writer to write the output.
 329      * @param visibleMemberMap the given members to summarize.
 330      * @param summaryTreeList list of content trees to which the documentation will be added
 331      */
 332     private void buildSummary(MemberSummaryWriter writer,
 333             VisibleMemberMap visibleMemberMap, LinkedList<Content> summaryTreeList) {
 334         SortedSet<Element> members = asSortedSet(visibleMemberMap.getLeafMembers());
 335         if (!members.isEmpty()) {
 336             List<Content> tableContents = new LinkedList<>();
 337             int counter = 0;
 338             for (Element member : members) {
 339                 final Element property = visibleMemberMap.getPropertyElement(member);
 340                 if (property != null) {
 341                     processProperty(visibleMemberMap, member, property);
 342                 }
 343                 List<? extends DocTree> firstSentenceTags = utils.getFirstSentenceTrees(member);
 344                 if (utils.isExecutableElement(member) && firstSentenceTags.isEmpty()) {
 345                     //Inherit comments from overriden or implemented method if
 346                     //necessary.
 347                     DocFinder.Output inheritedDoc =
 348                             DocFinder.search(configuration,
 349                                     new DocFinder.Input(utils, (ExecutableElement) member));
 350                     if (inheritedDoc.holder != null
 351                             && !utils.getFirstSentenceTrees(inheritedDoc.holder).isEmpty()) {
 352                         // let the comment helper know of the overridden element
 353                         CommentHelper ch = utils.getCommentHelper(member);
 354                         ch.setOverrideElement(inheritedDoc.holder);
 355                         firstSentenceTags = utils.getFirstSentenceTrees(inheritedDoc.holder);
 356                     }
 357                 }
 358                 writer.addMemberSummary(typeElement, member, firstSentenceTags,
 359                         tableContents, counter);
 360                 counter++;
 361             }
 362             summaryTreeList.add(writer.getSummaryTableTree(typeElement, tableContents));
 363         }
 364     }
 365 
 366     /**
 367      * Process the property method, property setter and/or property getter
 368      * comment text so that it contains the documentation from
 369      * the property field. The method adds the leading sentence,
 370      * copied documentation including the defaultValue tag and
 371      * the see tags if the appropriate property getter and setter are
 372      * available.
 373      *
 374      * @param visibleMemberMap the members information.
 375      * @param member the member which is to be augmented.
 376      * @param property the original property documentation.
 377      */
 378     private void processProperty(VisibleMemberMap visibleMemberMap,
 379                                  Element member,
 380                                  Element property) {
 381         CommentUtils cmtutils = configuration.cmtUtils;
 382         final boolean isSetter = isSetter(member);
 383         final boolean isGetter = isGetter(member);
 384 
 385         List<DocTree> fullBody = new ArrayList<>();
 386         List<DocTree> blockTags = new ArrayList<>();
 387         if (isGetter || isSetter) {
 388             //add "[GS]ets the value of the property PROPERTY_NAME."
 389             if (isSetter) {
 390                 String text = MessageFormat.format(
 391                         configuration.getText("doclet.PropertySetterWithName"),
 392                         utils.propertyName((ExecutableElement)member));
 393                 fullBody.addAll(cmtutils.makeFirstSentenceTree(text));
 394             }
 395             if (isGetter) {
 396                 String text = MessageFormat.format(
 397                         configuration.getText("doclet.PropertyGetterWithName"),
 398                         utils.propertyName((ExecutableElement) member));
 399                 fullBody.addAll(cmtutils.makeFirstSentenceTree(text));
 400             }
 401             List<? extends DocTree> propertyTags = utils.getBlockTags(property, "propertyDescription");
 402             if (propertyTags.isEmpty()) {
 403                 List<? extends DocTree> comment = utils.getFullBody(property);
 404                 blockTags.addAll(cmtutils.makePropertyDescriptionTree(comment));
 405             }
 406         } else {
 407             fullBody.addAll(utils.getFullBody(property));
 408         }
 409 
 410         // copy certain tags
 411         List<? extends DocTree> tags = utils.getBlockTags(property, Kind.SINCE);
 412         blockTags.addAll(tags);
 413 
 414         List<? extends DocTree> bTags = utils.getBlockTags(property, Kind.UNKNOWN_BLOCK_TAG);
 415         CommentHelper ch = utils.getCommentHelper(property);
 416         for (DocTree dt : bTags) {
 417             String tagName = ch.getTagName(dt);
 418             if ( "defaultValue".equals(tagName)) {
 419                 blockTags.add(dt);
 420             }
 421         }
 422 
 423         //add @see tags
 424         if (!isGetter && !isSetter) {
 425             ExecutableElement getter = (ExecutableElement) visibleMemberMap.getGetterForProperty(member);
 426             ExecutableElement setter = (ExecutableElement) visibleMemberMap.getSetterForProperty(member);
 427 
 428             if (null != getter) {
 429                 StringBuilder sb = new StringBuilder("#");
 430                 sb.append(utils.getSimpleName(getter)).append("()");
 431                 blockTags.add(cmtutils.makeSeeTree(sb.toString(), getter));
 432             }
 433 
 434             if (null != setter) {
 435                 VariableElement param = setter.getParameters().get(0);
 436                 StringBuilder sb = new StringBuilder("#");
 437                 sb.append(utils.getSimpleName(setter));
 438                 if (!utils.isTypeVariable(param.asType())) {
 439                     sb.append("(").append(utils.getTypeSignature(param.asType(), false, true)).append(")");
 440                 }
 441                 blockTags.add(cmtutils.makeSeeTree(sb.toString(), setter));
 442             }
 443         }
 444         cmtutils.setDocCommentTree(member, fullBody, blockTags, utils);
 445     }
 446 
 447     /**
 448      * Test whether the method is a getter.
 449      * @param element property method documentation. Needs to be either property
 450      * method, property getter, or property setter.
 451      * @return true if the given documentation belongs to a getter.
 452      */
 453     private boolean isGetter(Element element) {
 454         final String pedName = element.getSimpleName().toString();
 455         return pedName.startsWith("get") || pedName.startsWith("is");
 456     }
 457 
 458     /**
 459      * Test whether the method is a setter.
 460      * @param element property method documentation. Needs to be either property
 461      * method, property getter, or property setter.
 462      * @return true if the given documentation belongs to a setter.
 463      */
 464     private boolean isSetter(Element element) {
 465         return element.getSimpleName().toString().startsWith("set");
 466     }
 467 
 468     /**
 469      * Build the inherited member summary for the given methods.
 470      *
 471      * @param writer the writer for this member summary.
 472      * @param visibleMemberMap the map for the members to document.
 473      * @param summaryTreeList list of content trees to which the documentation will be added
 474      */
 475     private void buildInheritedSummary(MemberSummaryWriter writer,
 476             VisibleMemberMap visibleMemberMap, LinkedList<Content> summaryTreeList) {
 477         for (TypeElement inheritedClass : visibleMemberMap.getVisibleClasses()) {
 478             if (!(utils.isPublic(inheritedClass) || utils.isLinkable(inheritedClass))) {
 479                 continue;
 480             }
 481             if (inheritedClass == typeElement) {
 482                 continue;
 483             }
 484             SortedSet<Element> inheritedMembersFromMap = asSortedSet(
 485                     visibleMemberMap.getMembers(inheritedClass));
 486 
 487             if (!inheritedMembersFromMap.isEmpty()) {
 488                 SortedSet<Element> inheritedMembers = new TreeSet<>(comparator);
 489                 List<ExecutableElement> enclosedSuperMethods = utils.getMethods(inheritedClass);
 490                 for (Element inheritedMember : inheritedMembersFromMap) {
 491                     if (visibleMemberMap.kind != VisibleMemberMap.Kind.METHODS) {
 492                         inheritedMembers.add(inheritedMember);
 493                         continue;
 494                     }
 495 
 496                     // If applicable, filter those overridden methods that
 497                     // should not be documented in the summary/detail sections
 498                     // instead document them in the footnote, care must be taken
 499                     // to handle fx property methods which have no source comments,
 500                     // but comments are synthesized on the output.
 501                     ExecutableElement inheritedMethod = (ExecutableElement)inheritedMember;
 502                     if (enclosedSuperMethods.stream()
 503                             .anyMatch(e -> utils.executableMembersEqual(inheritedMethod, e)
 504                                     && (!utils.isSimpleOverride(e)
 505                                     || visibleMemberMap.getPropertyElement(e) != null))) {
 506                         inheritedMembers.add(inheritedMember);
 507                     }
 508                 }
 509 
 510                 Content inheritedTree = writer.getInheritedSummaryHeader(inheritedClass);
 511                 Content linksTree = writer.getInheritedSummaryLinksTree();
 512                 addSummaryFootNote(inheritedClass, inheritedMembers, linksTree, writer);
 513                 inheritedTree.addContent(linksTree);
 514                 summaryTreeList.add(writer.getMemberTree(inheritedTree));
 515             }
 516         }
 517     }
 518 
 519     private void addSummaryFootNote(TypeElement inheritedClass, SortedSet<Element> inheritedMembers,
 520                                     Content linksTree, MemberSummaryWriter writer) {
 521         for (Element member : inheritedMembers) {
 522             TypeElement t = (utils.isPackagePrivate(inheritedClass) && !utils.isLinkable(inheritedClass))
 523                     ? typeElement : inheritedClass;
 524             writer.addInheritedMemberSummary(t, member, inheritedMembers.first() == member,
 525                     inheritedMembers.last() == member, linksTree);
 526         }
 527     }
 528 
 529     /**
 530      * Add the summary for the documentation.
 531      *
 532      * @param writer the writer for this member summary.
 533      * @param visibleMemberMap the map for the members to document.
 534      * @param showInheritedSummary true if inherited summary should be documented
 535      * @param memberSummaryTree the content tree to which the documentation will be added
 536      */
 537     private void addSummary(MemberSummaryWriter writer,
 538             VisibleMemberMap visibleMemberMap, boolean showInheritedSummary,
 539             Content memberSummaryTree) {
 540         LinkedList<Content> summaryTreeList = new LinkedList<>();
 541         buildSummary(writer, visibleMemberMap, summaryTreeList);
 542         if (showInheritedSummary)
 543             buildInheritedSummary(writer, visibleMemberMap, summaryTreeList);
 544         if (!summaryTreeList.isEmpty()) {
 545             Content memberTree = writer.getMemberSummaryHeader(typeElement, memberSummaryTree);
 546             summaryTreeList.stream().forEach(memberTree::addContent);
 547             writer.addMemberTree(memberSummaryTree, memberTree);
 548         }
 549     }
 550 
 551     private SortedSet<Element> asSortedSet(Collection<Element> members) {
 552         SortedSet<Element> out = new TreeSet<>(comparator);
 553         out.addAll(members);
 554         return out;
 555     }
 556 }