001/* 002 * Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package org.jdrupes.mdoclet.internal.doclets.toolkit.builders; 027 028import static org.jdrupes.mdoclet.internal.doclets.toolkit.util.VisibleMemberTable.Kind.*; 029 030import java.util.Collection; 031import java.util.Comparator; 032import java.util.EnumMap; 033import java.util.HashMap; 034import java.util.LinkedList; 035import java.util.List; 036import java.util.Map; 037import java.util.Objects; 038import java.util.Optional; 039import java.util.SortedSet; 040import java.util.TreeSet; 041import javax.lang.model.element.Element; 042import javax.lang.model.element.ExecutableElement; 043import javax.lang.model.element.TypeElement; 044import javax.lang.model.element.VariableElement; 045import javax.lang.model.util.ElementFilter; 046import javax.tools.Diagnostic; 047 048import org.jdrupes.mdoclet.internal.doclets.toolkit.BaseConfiguration; 049import org.jdrupes.mdoclet.internal.doclets.toolkit.ClassWriter; 050import org.jdrupes.mdoclet.internal.doclets.toolkit.Content; 051import org.jdrupes.mdoclet.internal.doclets.toolkit.MemberSummaryWriter; 052import org.jdrupes.mdoclet.internal.doclets.toolkit.WriterFactory; 053import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocFinder; 054import org.jdrupes.mdoclet.internal.doclets.toolkit.util.Utils; 055import org.jdrupes.mdoclet.internal.doclets.toolkit.util.VisibleMemberTable; 056import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocFinder.Result; 057 058import com.sun.source.doctree.DocCommentTree; 059import com.sun.source.doctree.DocTree; 060 061/** 062 * Builds the member summary. 063 * There are two anonymous subtype variants of this builder, created 064 * in the {@link #getInstance} methods. One is for general types; 065 * the other is for annotation types. 066 */ 067public abstract class MemberSummaryBuilder extends AbstractMemberBuilder { 068 069 /* 070 * Comparator used to sort the members in the summary. 071 */ 072 private final Comparator<Element> comparator; 073 074 /** 075 * The member summary writers for the given class. 076 */ 077 private final EnumMap<VisibleMemberTable.Kind, 078 MemberSummaryWriter> memberSummaryWriters; 079 080 final PropertyHelper pHelper; 081 082 /** 083 * Construct a new MemberSummaryBuilder. 084 * 085 * @param context the build context. 086 * @param typeElement the type element. 087 */ 088 private MemberSummaryBuilder(Context context, TypeElement typeElement) { 089 super(context, typeElement); 090 memberSummaryWriters = new EnumMap<>(VisibleMemberTable.Kind.class); 091 comparator = utils.comparators.makeIndexElementComparator(); 092 pHelper = new PropertyHelper(this); 093 } 094 095 /** 096 * Construct a new MemberSummaryBuilder for a general type. 097 * 098 * @param classWriter the writer for the class whose members are being 099 * summarized. 100 * @param context the build context. 101 * @return the instance 102 */ 103 public static MemberSummaryBuilder getInstance( 104 ClassWriter classWriter, Context context) { 105 MemberSummaryBuilder builder 106 = new MemberSummaryBuilder(context, classWriter.getTypeElement()) { 107 @Override 108 public void build(Content target) { 109 buildPropertiesSummary(target); 110 buildNestedClassesSummary(target); 111 buildEnumConstantsSummary(target); 112 buildAnnotationTypeRequiredMemberSummary(target); 113 buildAnnotationTypeOptionalMemberSummary(target); 114 buildFieldsSummary(target); 115 buildConstructorsSummary(target); 116 buildMethodsSummary(target); 117 } 118 119 @Override 120 public boolean hasMembersToDocument() { 121 return visibleMemberTable.hasVisibleMembers(); 122 } 123 }; 124 WriterFactory wf = context.configuration.getWriterFactory(); 125 for (VisibleMemberTable.Kind kind : VisibleMemberTable.Kind.values()) { 126 MemberSummaryWriter msw 127 = builder.getVisibleMemberTable().hasVisibleMembers(kind) 128 ? wf.getMemberSummaryWriter(classWriter, kind) 129 : null; 130 builder.memberSummaryWriters.put(kind, msw); 131 } 132 return builder; 133 } 134 135 /** 136 * Return the specified visible member map. 137 * 138 * @return the specified visible member map. 139 * @throws ArrayIndexOutOfBoundsException when the type is invalid. 140 * @see VisibleMemberTable 141 */ 142 public VisibleMemberTable getVisibleMemberTable() { 143 return visibleMemberTable; 144 } 145 146 /**. 147 * Return the specified member summary writer. 148 * 149 * @param kind the kind of member summary writer to return. 150 * @return the specified member summary writer. 151 * @throws ArrayIndexOutOfBoundsException when the type is invalid. 152 * @see VisibleMemberTable 153 */ 154 public MemberSummaryWriter 155 getMemberSummaryWriter(VisibleMemberTable.Kind kind) { 156 return memberSummaryWriters.get(kind); 157 } 158 159 /** 160 * Returns a list of methods that will be documented for the given class. 161 * This information can be used for doclet specific documentation 162 * generation. 163 * 164 * @param kind the kind of elements to return. 165 * @return a list of methods that will be documented. 166 * @see VisibleMemberTable 167 */ 168 public SortedSet<Element> members(VisibleMemberTable.Kind kind) { 169 TreeSet<Element> out = new TreeSet<>(comparator); 170 out.addAll(getVisibleMembers(kind)); 171 return out; 172 } 173 174 /** 175 * Builds the summary for any optional members of an annotation type. 176 * 177 * @param summariesList the list of summaries to which the summary will be added 178 */ 179 protected void 180 buildAnnotationTypeOptionalMemberSummary(Content summariesList) { 181 MemberSummaryWriter writer 182 = memberSummaryWriters.get(ANNOTATION_TYPE_MEMBER_OPTIONAL); 183 addSummary(writer, ANNOTATION_TYPE_MEMBER_OPTIONAL, false, 184 summariesList); 185 } 186 187 /** 188 * Builds the summary for any required members of an annotation type. 189 * 190 * @param summariesList the list of summaries to which the summary will be added 191 */ 192 protected void 193 buildAnnotationTypeRequiredMemberSummary(Content summariesList) { 194 MemberSummaryWriter writer 195 = memberSummaryWriters.get(ANNOTATION_TYPE_MEMBER_REQUIRED); 196 addSummary(writer, ANNOTATION_TYPE_MEMBER_REQUIRED, false, 197 summariesList); 198 } 199 200 /** 201 * Builds the summary for any enum constants of an enum type. 202 * 203 * @param summariesList the list of summaries to which the summary will be added 204 */ 205 protected void buildEnumConstantsSummary(Content summariesList) { 206 MemberSummaryWriter writer = memberSummaryWriters.get(ENUM_CONSTANTS); 207 addSummary(writer, ENUM_CONSTANTS, false, summariesList); 208 } 209 210 /** 211 * Builds the summary for any fields. 212 * 213 * @param summariesList the list of summaries to which the summary will be added 214 */ 215 protected void buildFieldsSummary(Content summariesList) { 216 MemberSummaryWriter writer = memberSummaryWriters.get(FIELDS); 217 addSummary(writer, FIELDS, true, summariesList); 218 } 219 220 /** 221 * Builds the summary for any properties. 222 * 223 * @param summariesList the list of summaries to which the summary will be added 224 */ 225 protected void buildPropertiesSummary(Content summariesList) { 226 MemberSummaryWriter writer = memberSummaryWriters.get(PROPERTIES); 227 addSummary(writer, PROPERTIES, true, summariesList); 228 } 229 230 /** 231 * Builds the summary for any nested classes. 232 * 233 * @param summariesList the list of summaries to which the summary will be added 234 */ 235 protected void buildNestedClassesSummary(Content summariesList) { 236 MemberSummaryWriter writer = memberSummaryWriters.get(NESTED_CLASSES); 237 addSummary(writer, NESTED_CLASSES, true, summariesList); 238 } 239 240 /** 241 * Builds the summary for any methods. 242 * 243 * @param summariesList the content to which the documentation will be added 244 */ 245 protected void buildMethodsSummary(Content summariesList) { 246 MemberSummaryWriter writer = memberSummaryWriters.get(METHODS); 247 addSummary(writer, METHODS, true, summariesList); 248 } 249 250 /** 251 * Builds the summary for any constructors. 252 * 253 * @param summariesList the content to which the documentation will be added 254 */ 255 protected void buildConstructorsSummary(Content summariesList) { 256 MemberSummaryWriter writer = memberSummaryWriters.get(CONSTRUCTORS); 257 addSummary(writer, CONSTRUCTORS, false, summariesList); 258 } 259 260 /** 261 * Build the member summary for the given members. 262 * 263 * @param writer the summary writer to write the output. 264 * @param kind the kind of members to summarize. 265 * @param summaryTreeList the list of contents to which the documentation will be added 266 */ 267 private void buildSummary(MemberSummaryWriter writer, 268 VisibleMemberTable.Kind kind, LinkedList<Content> summaryTreeList) { 269 SortedSet<? extends Element> members 270 = asSortedSet(getVisibleMembers(kind)); 271 if (!members.isEmpty()) { 272 for (Element member : members) { 273 final Element property = pHelper.getPropertyElement(member); 274 if (property != null 275 && member instanceof ExecutableElement ee) { 276 configuration.cmtUtils.updatePropertyMethodComment(ee, 277 property); 278 } 279 if (utils.isMethod(member)) { 280 var docFinder = utils.docFinder(); 281 Optional<List<? extends DocTree>> r 282 = docFinder.search((ExecutableElement) member, (m -> { 283 var firstSentenceTrees 284 = utils.getFirstSentenceTrees(m); 285 Optional<List<? extends DocTree>> optional 286 = firstSentenceTrees.isEmpty() 287 ? Optional.empty() 288 : Optional.of(firstSentenceTrees); 289 return Result.fromOptional(optional); 290 })).toOptional(); 291 // The fact that we use `member` for possibly unrelated tags 292 // is suspicious 293 writer.addMemberSummary(typeElement, member, 294 r.orElse(List.of())); 295 } else { 296 writer.addMemberSummary(typeElement, member, 297 utils.getFirstSentenceTrees(member)); 298 } 299 } 300 summaryTreeList.add(writer.getSummaryTable(typeElement)); 301 } 302 } 303 304 /** 305 * Build the inherited member summary for the given methods. 306 * 307 * @param writer the writer for this member summary. 308 * @param kind the kind of members to document. 309 * @param targets the list of contents to which the documentation will be added 310 */ 311 private void buildInheritedSummary(MemberSummaryWriter writer, 312 VisibleMemberTable.Kind kind, LinkedList<Content> targets) { 313 VisibleMemberTable visibleMemberTable = getVisibleMemberTable(); 314 SortedSet<? extends Element> inheritedMembersFromMap 315 = asSortedSet(visibleMemberTable.getAllVisibleMembers(kind)); 316 317 for (TypeElement inheritedClass : visibleMemberTable 318 .getVisibleTypeElements()) { 319 if (!(utils.isPublic(inheritedClass) 320 || utils.isLinkable(inheritedClass))) { 321 continue; 322 } 323 if (Objects.equals(inheritedClass, typeElement)) { 324 continue; 325 } 326 if (utils.hasHiddenTag(inheritedClass)) { 327 continue; 328 } 329 330 List<? extends Element> members = inheritedMembersFromMap.stream() 331 .filter(e -> Objects.equals(utils.getEnclosingTypeElement(e), 332 inheritedClass)) 333 .toList(); 334 335 if (!members.isEmpty()) { 336 SortedSet<Element> inheritedMembers = new TreeSet<>(comparator); 337 inheritedMembers.addAll(members); 338 Content inheritedHeader 339 = writer.getInheritedSummaryHeader(inheritedClass); 340 Content links = writer.getInheritedSummaryLinks(); 341 addSummaryFootNote(inheritedClass, inheritedMembers, links, 342 writer); 343 inheritedHeader.add(links); 344 targets.add(inheritedHeader); 345 } 346 } 347 } 348 349 private void addSummaryFootNote(TypeElement inheritedClass, 350 Iterable<Element> inheritedMembers, 351 Content links, MemberSummaryWriter writer) { 352 boolean isFirst = true; 353 for (var iterator = inheritedMembers.iterator(); iterator.hasNext();) { 354 var member = iterator.next(); 355 TypeElement t = utils.isUndocumentedEnclosure(inheritedClass) 356 ? typeElement 357 : inheritedClass; 358 writer.addInheritedMemberSummary(t, member, isFirst, 359 !iterator.hasNext(), links); 360 isFirst = false; 361 } 362 } 363 364 /** 365 * Adds the summary for the documentation. 366 * 367 * @param writer the writer for this member summary 368 * @param kind the kind of members to document 369 * @param showInheritedSummary true if a summary of any inherited elements should be documented 370 * @param summariesList the list of summaries to which the summary will be added 371 */ 372 private void addSummary(MemberSummaryWriter writer, 373 VisibleMemberTable.Kind kind, 374 boolean showInheritedSummary, 375 Content summariesList) { 376 LinkedList<Content> summaryTreeList = new LinkedList<>(); 377 buildSummary(writer, kind, summaryTreeList); 378 if (showInheritedSummary) 379 buildInheritedSummary(writer, kind, summaryTreeList); 380 if (!summaryTreeList.isEmpty()) { 381 Content member 382 = writer.getMemberSummaryHeader(typeElement, summariesList); 383 summaryTreeList.forEach(member::add); 384 writer.addSummary(summariesList, member); 385 } 386 } 387 388 private SortedSet<? extends Element> 389 asSortedSet(Collection<? extends Element> members) { 390 SortedSet<Element> out = new TreeSet<>(comparator); 391 out.addAll(members); 392 return out; 393 } 394 395 /** 396 * A utility class to manage the property-related methods that should be 397 * synthesized or updated. 398 * 399 * A property may comprise a field (that is typically private, if present), 400 * a {@code fooProperty()} method (which is the defining characteristic for 401 * a property), a {@code getFoo()} method and/or a {@code setFoo(Foo foo)} method. 402 * 403 * Either the field (if present) or the {@code fooProperty()} method should have a 404 * comment. If there is no field, or no comment on the field, the description for 405 * the property will be derived from the description of the {@code fooProperty()} 406 * method. If any method does not have a comment, one will be provided. 407 */ 408 static class PropertyHelper { 409 410 private final Map<Element, Element> classPropertiesMap 411 = new HashMap<>(); 412 413 private final MemberSummaryBuilder builder; 414 415 PropertyHelper(MemberSummaryBuilder builder) { 416 this.builder = builder; 417 computeProperties(); 418 } 419 420 private void computeProperties() { 421 VisibleMemberTable vmt = builder.getVisibleMemberTable(); 422 List<ExecutableElement> props 423 = ElementFilter.methodsIn(vmt.getVisibleMembers(PROPERTIES)); 424 for (ExecutableElement propertyMethod : props) { 425 ExecutableElement getter 426 = vmt.getPropertyGetter(propertyMethod); 427 ExecutableElement setter 428 = vmt.getPropertySetter(propertyMethod); 429 VariableElement field = vmt.getPropertyField(propertyMethod); 430 431 addToPropertiesMap(propertyMethod, field, getter, setter); 432 } 433 } 434 435 private void addToPropertiesMap(ExecutableElement propertyMethod, 436 VariableElement field, 437 ExecutableElement getter, 438 ExecutableElement setter) { 439 // determine the preferred element from which to derive the property 440 // description 441 Element e = field == null || !builder.utils.hasDocCommentTree(field) 442 ? propertyMethod 443 : field; 444 445 if (e == field && builder.utils.hasDocCommentTree(propertyMethod)) { 446 BaseConfiguration configuration = builder.configuration; 447 configuration.getReporter().print(Diagnostic.Kind.WARNING, 448 propertyMethod, configuration.getDocResources() 449 .getText("doclet.duplicate.comment.for.property")); 450 } 451 452 addToPropertiesMap(propertyMethod, e); 453 addToPropertiesMap(getter, e); 454 addToPropertiesMap(setter, e); 455 } 456 457 private void addToPropertiesMap(Element propertyMethod, 458 Element commentSource) { 459 Objects.requireNonNull(commentSource); 460 if (propertyMethod == null) { 461 return; 462 } 463 464 Utils utils = builder.utils; 465 DocCommentTree docTree = utils.hasDocCommentTree(propertyMethod) 466 ? utils.getDocCommentTree(propertyMethod) 467 : null; 468 469 /* 470 * The second condition is required for the property buckets. In 471 * this case the comment is at the property method (not at the 472 * field) 473 * and it needs to be listed in the map. 474 */ 475 if ((docTree == null) || propertyMethod.equals(commentSource)) { 476 classPropertiesMap.put(propertyMethod, commentSource); 477 } 478 } 479 480 /** 481 * Returns the element for the property documentation belonging to the given member. 482 * @param element the member for which the property documentation is needed. 483 * @return the element for the property documentation, null if there is none. 484 */ 485 public Element getPropertyElement(Element element) { 486 return classPropertiesMap.get(element); 487 } 488 } 489}