001/* 002 * Copyright (c) 2003, 2022, 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 java.util.List; 029import java.util.Set; 030import java.util.stream.Collectors; 031 032import javax.lang.model.element.Element; 033import javax.lang.model.element.ExecutableElement; 034import javax.lang.model.element.Name; 035import javax.lang.model.element.PackageElement; 036import javax.lang.model.element.TypeElement; 037import javax.lang.model.element.VariableElement; 038import javax.lang.model.type.TypeMirror; 039 040import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.ContentBuilder; 041import org.jdrupes.mdoclet.internal.doclets.toolkit.ClassWriter; 042import org.jdrupes.mdoclet.internal.doclets.toolkit.CommentUtils; 043import org.jdrupes.mdoclet.internal.doclets.toolkit.Content; 044import org.jdrupes.mdoclet.internal.doclets.toolkit.DocFilesHandler; 045import org.jdrupes.mdoclet.internal.doclets.toolkit.DocletException; 046import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocFileIOException; 047import org.jdrupes.mdoclet.internal.doclets.toolkit.util.Utils; 048 049/** 050 * Builds the summary for a given class. 051 */ 052public class ClassBuilder extends AbstractBuilder { 053 054 /** 055 * The class being documented. 056 */ 057 private final TypeElement typeElement; 058 059 /** 060 * The doclet specific writer. 061 */ 062 private final ClassWriter writer; 063 064 private final Utils utils; 065 066 /** 067 * Construct a new ClassBuilder. 068 * 069 * @param context the build context 070 * @param typeElement the class being documented. 071 * @param writer the doclet specific writer. 072 */ 073 private ClassBuilder(Context context, TypeElement typeElement, 074 ClassWriter writer) { 075 super(context); 076 this.typeElement = typeElement; 077 this.writer = writer; 078 this.utils = configuration.utils; 079 switch (typeElement.getKind()) { 080 case ENUM -> setEnumDocumentation(typeElement); 081 case RECORD -> setRecordDocumentation(typeElement); 082 } 083 } 084 085 /** 086 * Constructs a new ClassBuilder. 087 * 088 * @param context the build context 089 * @param typeElement the class being documented. 090 * @param writer the doclet specific writer. 091 * @return the new ClassBuilder 092 */ 093 public static ClassBuilder getInstance(Context context, 094 TypeElement typeElement, ClassWriter writer) { 095 return new ClassBuilder(context, typeElement, writer); 096 } 097 098 @Override 099 public void build() throws DocletException { 100 buildClassDoc(); 101 } 102 103 /** 104 * Handles the {@literal <TypeElement>} tag. 105 * 106 * @throws DocletException if there is a problem while building the documentation 107 */ 108 protected void buildClassDoc() throws DocletException { 109 String key = switch (typeElement.getKind()) { 110 case INTERFACE -> "doclet.Interface"; 111 case ENUM -> "doclet.Enum"; 112 case RECORD -> "doclet.RecordClass"; 113 case ANNOTATION_TYPE -> "doclet.AnnotationType"; 114 case CLASS -> "doclet.Class"; 115 default -> throw new IllegalStateException( 116 typeElement.getKind() + " " + typeElement); 117 }; 118 Content content = writer.getHeader(resources.getText(key) + " " 119 + utils.getSimpleName(typeElement)); 120 Content classContent = writer.getClassContentHeader(); 121 122 buildClassTree(classContent); 123 buildClassInfo(classContent); 124 buildMemberSummary(classContent); 125 buildMemberDetails(classContent); 126 127 writer.addClassContent(classContent); 128 writer.addFooter(); 129 writer.printDocument(content); 130 copyDocFiles(); 131 } 132 133 /** 134 * Build the class inheritance tree documentation. 135 * 136 * @param classContent the content to which the documentation will be added 137 */ 138 protected void buildClassTree(Content classContent) { 139 writer.addClassTree(classContent); 140 } 141 142 /** 143 * Build the class information documentation. 144 * 145 * @param target the content to which the documentation will be added 146 * @throws DocletException if there is a problem while building the documentation 147 */ 148 protected void buildClassInfo(Content target) throws DocletException { 149 Content c = new ContentBuilder(); 150 buildParamInfo(c); 151 buildSuperInterfacesInfo(c); 152 buildImplementedInterfacesInfo(c); 153 buildSubClassInfo(c); 154 buildSubInterfacesInfo(c); 155 buildInterfaceUsageInfo(c); 156 buildNestedClassInfo(c); 157 buildFunctionalInterfaceInfo(c); 158 buildClassSignature(c); 159 buildDeprecationInfo(c); 160 buildClassDescription(c); 161 buildClassTagInfo(c); 162 163 target.add(writer.getClassInfo(c)); 164 } 165 166 /** 167 * Build the type parameters and state components of this class. 168 * 169 * @param target the content to which the documentation will be added 170 */ 171 protected void buildParamInfo(Content target) { 172 writer.addParamInfo(target); 173 } 174 175 /** 176 * If this is an interface, list all superinterfaces. 177 * 178 * @param target the content to which the documentation will be added 179 */ 180 protected void buildSuperInterfacesInfo(Content target) { 181 writer.addSuperInterfacesInfo(target); 182 } 183 184 /** 185 * If this is a class, list all interfaces implemented by this class. 186 * 187 * @param target the content to which the documentation will be added 188 */ 189 protected void buildImplementedInterfacesInfo(Content target) { 190 writer.addImplementedInterfacesInfo(target); 191 } 192 193 /** 194 * List all the classes that extend this one. 195 * 196 * @param target the content to which the documentation will be added 197 */ 198 protected void buildSubClassInfo(Content target) { 199 writer.addSubClassInfo(target); 200 } 201 202 /** 203 * List all the interfaces that extend this one. 204 * 205 * @param target the content to which the documentation will be added 206 */ 207 protected void buildSubInterfacesInfo(Content target) { 208 writer.addSubInterfacesInfo(target); 209 } 210 211 /** 212 * If this is an interface, list all classes that implement this interface. 213 * 214 * @param target the content to which the documentation will be added 215 */ 216 protected void buildInterfaceUsageInfo(Content target) { 217 writer.addInterfaceUsageInfo(target); 218 } 219 220 /** 221 * If this is an functional interface, display appropriate message. 222 * 223 * @param target the content to which the documentation will be added 224 */ 225 protected void buildFunctionalInterfaceInfo(Content target) { 226 writer.addFunctionalInterfaceInfo(target); 227 } 228 229 /** 230 * If this class is deprecated, build the appropriate information. 231 * 232 * @param target the content to which the documentation will be added 233 */ 234 protected void buildDeprecationInfo(Content target) { 235 writer.addClassDeprecationInfo(target); 236 } 237 238 /** 239 * If this is an inner class or interface, list the enclosing class or interface. 240 * 241 * @param target the content to which the documentation will be added 242 */ 243 protected void buildNestedClassInfo(Content target) { 244 writer.addNestedClassInfo(target); 245 } 246 247 /** 248 * Copy the doc files. 249 * 250 * @throws DocFileIOException if there is a problem while copying the files 251 */ 252 private void copyDocFiles() throws DocletException { 253 PackageElement containingPackage = utils.containingPackage(typeElement); 254 if ((configuration.packages == null || 255 !configuration.packages.contains(containingPackage)) && 256 !containingPackagesSeen.contains(containingPackage)) { 257 // Only copy doc files dir if the containing package is not 258 // documented AND if we have not documented a class from the same 259 // package already. Otherwise, we are making duplicate copies. 260 DocFilesHandler docFilesHandler = configuration 261 .getWriterFactory() 262 .getDocFilesHandler(containingPackage); 263 docFilesHandler.copyDocFiles(); 264 containingPackagesSeen.add(containingPackage); 265 } 266 } 267 268 /** 269 * Build the signature of the current class. 270 * 271 * @param target the content to which the documentation will be added 272 */ 273 protected void buildClassSignature(Content target) { 274 writer.addClassSignature(target); 275 } 276 277 /** 278 * Build the class description. 279 * 280 * @param target the content to which the documentation will be added 281 */ 282 protected void buildClassDescription(Content target) { 283 writer.addClassDescription(target); 284 } 285 286 /** 287 * Build the tag information for the current class. 288 * 289 * @param target the content to which the documentation will be added 290 */ 291 protected void buildClassTagInfo(Content target) { 292 writer.addClassTagInfo(target); 293 } 294 295 /** 296 * Build the member summary contents of the page. 297 * 298 * @param classContent the content to which the documentation will be added 299 * @throws DocletException if there is a problem while building the documentation 300 */ 301 protected void buildMemberSummary(Content classContent) 302 throws DocletException { 303 Content summariesList = writer.getSummariesList(); 304 builderFactory.getMemberSummaryBuilder(writer).build(summariesList); 305 classContent.add(writer.getMemberSummary(summariesList)); 306 } 307 308 /** 309 * Build the member details contents of the page. 310 * 311 * @param classContent the content to which the documentation will be added 312 * @throws DocletException if there is a problem while building the documentation 313 */ 314 protected void buildMemberDetails(Content classContent) 315 throws DocletException { 316 Content detailsList = writer.getDetailsList(); 317 318 buildEnumConstantsDetails(detailsList); 319 buildPropertyDetails(detailsList); 320 buildFieldDetails(detailsList); 321 buildConstructorDetails(detailsList); 322 buildAnnotationTypeMemberDetails(detailsList); 323 buildMethodDetails(detailsList); 324 325 classContent.add(writer.getMemberDetails(detailsList)); 326 } 327 328 /** 329 * Build the enum constants documentation. 330 * 331 * @param detailsList the content to which the documentation will be added 332 * @throws DocletException if there is a problem while building the documentation 333 */ 334 protected void buildEnumConstantsDetails(Content detailsList) 335 throws DocletException { 336 builderFactory.getEnumConstantsBuilder(writer).build(detailsList); 337 } 338 339 /** 340 * Build the field documentation. 341 * 342 * @param detailsList the content to which the documentation will be added 343 * @throws DocletException if there is a problem while building the documentation 344 */ 345 protected void buildFieldDetails(Content detailsList) 346 throws DocletException { 347 builderFactory.getFieldBuilder(writer).build(detailsList); 348 } 349 350 /** 351 * Build the property documentation. 352 * 353 * @param detailsList the content to which the documentation will be added 354 * @throws DocletException if there is a problem while building the documentation 355 */ 356 public void buildPropertyDetails(Content detailsList) 357 throws DocletException { 358 builderFactory.getPropertyBuilder(writer).build(detailsList); 359 } 360 361 /** 362 * Build the constructor documentation. 363 * 364 * @param detailsList the content to which the documentation will be added 365 * @throws DocletException if there is a problem while building the documentation 366 */ 367 protected void buildConstructorDetails(Content detailsList) 368 throws DocletException { 369 builderFactory.getConstructorBuilder(writer).build(detailsList); 370 } 371 372 /** 373 * Build the method documentation. 374 * 375 * @param detailsList the content to which the documentation will be added 376 * @throws DocletException if there is a problem while building the documentation 377 */ 378 protected void buildMethodDetails(Content detailsList) 379 throws DocletException { 380 builderFactory.getMethodBuilder(writer).build(detailsList); 381 } 382 383 /** 384 * Build the annotation type optional member documentation. 385 * 386 * @param target the content to which the documentation will be added 387 * @throws DocletException if there is a problem building the documentation 388 */ 389 protected void buildAnnotationTypeMemberDetails(Content target) 390 throws DocletException { 391 builderFactory.getAnnotationTypeMemberBuilder(writer).build(target); 392 } 393 394 /** 395 * The documentation for values() and valueOf() in Enums are set by the 396 * doclet only iff the user or overridden methods are missing. 397 * @param elem the enum element 398 */ 399 private void setEnumDocumentation(TypeElement elem) { 400 CommentUtils cmtUtils = configuration.cmtUtils; 401 for (ExecutableElement ee : utils.getMethods(elem)) { 402 if (!utils.getFullBody(ee).isEmpty()) // ignore if already set 403 continue; 404 Name name = ee.getSimpleName(); 405 if (name.contentEquals("values") && ee.getParameters().isEmpty()) { 406 utils.removeCommentHelper(ee); // purge previous entry 407 cmtUtils.setEnumValuesTree(ee); 408 } else if (name.contentEquals("valueOf") 409 && ee.getParameters().size() == 1) { 410 // TODO: check parameter type 411 utils.removeCommentHelper(ee); // purge previous entry 412 cmtUtils.setEnumValueOfTree(ee); 413 } 414 } 415 } 416 417 /** 418 * Sets the documentation as needed for the mandated parts of a record type. 419 * This includes the canonical constructor, methods like {@code equals}, 420 * {@code hashCode}, {@code toString}, the accessor methods, and the underlying 421 * field. 422 * @param elem the record element 423 */ 424 425 private void setRecordDocumentation(TypeElement elem) { 426 CommentUtils cmtUtils = configuration.cmtUtils; 427 Set<Name> componentNames = elem.getRecordComponents().stream() 428 .map(Element::getSimpleName) 429 .collect(Collectors.toSet()); 430 431 for (ExecutableElement ee : utils.getConstructors(elem)) { 432 if (utils.isCanonicalRecordConstructor(ee)) { 433 if (utils.getFullBody(ee).isEmpty()) { 434 utils.removeCommentHelper(ee); // purge previous entry 435 cmtUtils.setRecordConstructorTree(ee); 436 } 437 // only one canonical constructor; no need to keep looking 438 break; 439 } 440 } 441 442 var fields = utils.isSerializable(elem) 443 ? utils.getFieldsUnfiltered(elem) 444 : utils.getFields(elem); 445 for (VariableElement ve : fields) { 446 // The fields for the record component cannot be declared by the 447 // user and so cannot have any pre-existing comment. 448 Name name = ve.getSimpleName(); 449 if (componentNames.contains(name)) { 450 utils.removeCommentHelper(ve); // purge previous entry 451 cmtUtils.setRecordFieldTree(ve); 452 } 453 } 454 455 TypeMirror objectType = utils.getObjectType(); 456 457 for (ExecutableElement ee : utils.getMethods(elem)) { 458 if (!utils.getFullBody(ee).isEmpty()) { 459 continue; 460 } 461 462 Name name = ee.getSimpleName(); 463 List<? extends VariableElement> params = ee.getParameters(); 464 if (name.contentEquals("equals")) { 465 if (params.size() == 1 && utils.typeUtils 466 .isSameType(params.get(0).asType(), objectType)) { 467 utils.removeCommentHelper(ee); // purge previous entry 468 cmtUtils.setRecordEqualsTree(ee); 469 } 470 } else if (name.contentEquals("hashCode")) { 471 if (params.isEmpty()) { 472 utils.removeCommentHelper(ee); // purge previous entry 473 cmtUtils.setRecordHashCodeTree(ee); 474 } 475 } else if (name.contentEquals("toString")) { 476 if (params.isEmpty()) { 477 utils.removeCommentHelper(ee); // purge previous entry 478 cmtUtils.setRecordToStringTree(ee); 479 } 480 } else if (componentNames.contains(name)) { 481 if (params.isEmpty()) { 482 utils.removeCommentHelper(ee); // purge previous entry 483 cmtUtils.setRecordAccessorTree(ee); 484 } 485 } 486 } 487 488 } 489}