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