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 java.util.Collection; 029import java.util.Iterator; 030import java.util.List; 031import java.util.SortedSet; 032import java.util.TreeSet; 033 034import javax.lang.model.element.Element; 035import javax.lang.model.element.ExecutableElement; 036import javax.lang.model.element.PackageElement; 037import javax.lang.model.element.TypeElement; 038import javax.lang.model.element.VariableElement; 039import javax.lang.model.type.TypeMirror; 040 041import org.jdrupes.mdoclet.internal.doclets.toolkit.Content; 042import org.jdrupes.mdoclet.internal.doclets.toolkit.DocletException; 043import org.jdrupes.mdoclet.internal.doclets.toolkit.SerializedFormWriter; 044import org.jdrupes.mdoclet.internal.doclets.toolkit.util.CommentHelper; 045import org.jdrupes.mdoclet.internal.doclets.toolkit.util.Utils; 046 047import com.sun.source.doctree.SerialFieldTree; 048import com.sun.source.doctree.SerialTree; 049 050/** 051 * Builds the serialized form. 052 */ 053public class SerializedFormBuilder extends AbstractBuilder { 054 055 /** 056 * The writer for this builder. 057 */ 058 private SerializedFormWriter writer; 059 060 /** 061 * The writer for serializable fields. 062 */ 063 private SerializedFormWriter.SerialFieldWriter fieldWriter; 064 065 /** 066 * The writer for serializable method documentation. 067 */ 068 private SerializedFormWriter.SerialMethodWriter methodWriter; 069 070 /** 071 * The header for the serial version UID. Save the string 072 * here instead of the properties file because we do not want 073 * this string to be localized. 074 */ 075 private static final String SERIAL_VERSION_UID = "serialVersionUID"; 076 private static final String SERIAL_VERSION_UID_HEADER 077 = SERIAL_VERSION_UID + ":"; 078 079 /** 080 * The current package being documented. 081 */ 082 private PackageElement currentPackage; 083 084 /** 085 * The current class being documented. 086 */ 087 private TypeElement currentTypeElement; 088 089 /** 090 * The current member being documented. 091 */ 092 protected Element currentMember; 093 094 /** 095 * Construct a new SerializedFormBuilder. 096 * @param context the build context. 097 */ 098 private SerializedFormBuilder(Context context) { 099 super(context); 100 } 101 102 /** 103 * Construct a new SerializedFormBuilder. 104 * 105 * @param context the build context. 106 * @return the new SerializedFormBuilder 107 */ 108 public static SerializedFormBuilder getInstance(Context context) { 109 return new SerializedFormBuilder(context); 110 } 111 112 /** 113 * Build the serialized form. 114 * 115 * @throws DocletException if there is a problem while building the documentation 116 */ 117 @Override 118 public void build() throws DocletException { 119 SortedSet<TypeElement> rootclasses 120 = new TreeSet<>(utils.comparators.makeGeneralPurposeComparator()); 121 rootclasses.addAll(configuration.getIncludedTypeElements()); 122 if (!serialClassFoundToDocument(rootclasses)) { 123 // Nothing to document. 124 return; 125 } 126 writer = configuration.getWriterFactory().getSerializedFormWriter(); 127 if (writer == null) { 128 // Doclet does not support this output. 129 return; 130 } 131 buildSerializedForm(); 132 } 133 134 /** 135 * Build the serialized form. 136 * 137 * @throws DocletException if there is a problem while building the documentation 138 */ 139 protected void buildSerializedForm() throws DocletException { 140 Content content = writer.getHeader(resources.getText( 141 "doclet.Serialized_Form")); 142 143 buildSerializedFormSummaries(); 144 145 writer.addFooter(); 146 writer.printDocument(content); 147 } 148 149 /** 150 * Build the serialized form summaries. 151 * 152 * @throws DocletException if there is a problem while building the documentation 153 */ 154 protected void buildSerializedFormSummaries() 155 throws DocletException { 156 Content c = writer.getSerializedSummariesHeader(); 157 for (PackageElement pkg : configuration.packages) { 158 currentPackage = pkg; 159 160 buildPackageSerializedForm(c); 161 } 162 writer.addSerializedContent(c); 163 } 164 165 /** 166 * Build the package serialized form for the current package being processed. 167 * 168 * @param target the content to which the documentation will be added 169 * @throws DocletException if there is a problem while building the documentation 170 */ 171 protected void buildPackageSerializedForm(Content target) 172 throws DocletException { 173 Content packageSerializedHeader = writer.getPackageSerializedHeader(); 174 SortedSet<TypeElement> classes 175 = utils.getAllClassesUnfiltered(currentPackage); 176 if (classes.isEmpty()) { 177 return; 178 } 179 if (!serialInclude(utils, currentPackage)) { 180 return; 181 } 182 if (!serialClassFoundToDocument(classes)) { 183 return; 184 } 185 186 buildPackageHeader(packageSerializedHeader); 187 buildClassSerializedForm(packageSerializedHeader); 188 189 writer.addPackageSerialized(target, packageSerializedHeader); 190 } 191 192 /** 193 * Build the package header. 194 * 195 * @param target the content to which the documentation will be added 196 */ 197 protected void buildPackageHeader(Content target) { 198 target.add(writer.getPackageHeader(currentPackage)); 199 } 200 201 /** 202 * Build the class serialized form. 203 * 204 * @param target the content to which the documentation will be added 205 * @throws DocletException if there is a problem while building the documentation 206 */ 207 protected void buildClassSerializedForm(Content target) 208 throws DocletException { 209 Content classSerializedHeader = writer.getClassSerializedHeader(); 210 SortedSet<TypeElement> typeElements 211 = utils.getAllClassesUnfiltered(currentPackage); 212 for (TypeElement typeElement : typeElements) { 213 currentTypeElement = typeElement; 214 fieldWriter = writer.getSerialFieldWriter(currentTypeElement); 215 methodWriter = writer.getSerialMethodWriter(currentTypeElement); 216 if (utils.isClass(currentTypeElement) 217 && utils.isSerializable(currentTypeElement)) { 218 if (!serialClassInclude(utils, currentTypeElement)) { 219 continue; 220 } 221 Content classHeader = writer.getClassHeader(currentTypeElement); 222 223 buildSerialUIDInfo(classHeader); 224 buildClassContent(classHeader); 225 226 classSerializedHeader.add(writer.getMember(classHeader)); 227 } 228 } 229 target.add(classSerializedHeader); 230 } 231 232 /** 233 * Build the serial UID information for the given class. 234 * 235 * @param target the content to which the serial UID information will be added 236 */ 237 protected void buildSerialUIDInfo(Content target) { 238 Content serialUIDHeader = writer.getSerialUIDInfoHeader(); 239 for (VariableElement field : utils 240 .getFieldsUnfiltered(currentTypeElement)) { 241 if (field.getSimpleName().toString() 242 .compareTo(SERIAL_VERSION_UID) == 0 && 243 field.getConstantValue() != null) { 244 writer.addSerialUIDInfo(SERIAL_VERSION_UID_HEADER, 245 utils.constantValueExpression(field), serialUIDHeader); 246 break; 247 } 248 } 249 target.add(serialUIDHeader); 250 } 251 252 /** 253 * Build the summaries for the methods and fields. 254 * 255 * @param target the content to which the documentation will be added 256 * @throws DocletException if there is a problem while building the documentation 257 */ 258 protected void buildClassContent(Content target) throws DocletException { 259 Content classContent = writer.getClassContentHeader(); 260 261 buildSerializableMethods(classContent); 262 buildFieldHeader(classContent); 263 buildSerializableFields(classContent); 264 265 target.add(classContent); 266 } 267 268 /** 269 * Build the summaries for the methods that belong to the given class. 270 * 271 * @param target the content to which the documentation will be added 272 * @throws DocletException if there is a problem while building the documentation 273 */ 274 protected void buildSerializableMethods(Content target) 275 throws DocletException { 276 Content serializableMethodsHeader 277 = methodWriter.getSerializableMethodsHeader(); 278 for (var i = utils.serializationMethods(currentTypeElement).iterator(); 279 i.hasNext();) { 280 currentMember = i.next(); 281 Content methodsContent 282 = methodWriter.getMethodsContentHeader(!i.hasNext()); 283 284 buildMethodSubHeader(methodsContent); 285 buildDeprecatedMethodInfo(methodsContent); 286 buildMethodInfo(methodsContent); 287 288 serializableMethodsHeader.add(methodsContent); 289 } 290 if (!utils.serializationMethods(currentTypeElement).isEmpty()) { 291 target.add(methodWriter.getSerializableMethods( 292 resources.getText("doclet.Serialized_Form_methods"), 293 serializableMethodsHeader)); 294 if (utils.isSerializable(currentTypeElement) 295 && !utils.isExternalizable(currentTypeElement)) { 296 if (utils.serializationMethods(currentTypeElement).isEmpty()) { 297 Content noCustomizationMsg 298 = methodWriter.getNoCustomizationMsg( 299 resources.getText( 300 "doclet.Serializable_no_customization")); 301 target.add(methodWriter.getSerializableMethods( 302 resources.getText("doclet.Serialized_Form_methods"), 303 noCustomizationMsg)); 304 } 305 } 306 } 307 } 308 309 /** 310 * Build the method sub header. 311 * 312 * @param methodsContent the content to which the documentation will be added 313 */ 314 protected void buildMethodSubHeader(Content methodsContent) { 315 methodWriter.addMemberHeader((ExecutableElement) currentMember, 316 methodsContent); 317 } 318 319 /** 320 * Build the deprecated method description. 321 * 322 * @param methodsContent the content to which the documentation will be added 323 */ 324 protected void buildDeprecatedMethodInfo(Content methodsContent) { 325 methodWriter.addDeprecatedMemberInfo((ExecutableElement) currentMember, 326 methodsContent); 327 } 328 329 /** 330 * Build the information for the method. 331 * 332 * @param methodsContent the content to which the documentation will be added 333 * @throws DocletException if there is a problem while building the documentation 334 */ 335 protected void buildMethodInfo(Content methodsContent) 336 throws DocletException { 337 if (options.noComment()) { 338 return; 339 } 340 341 buildMethodDescription(methodsContent); 342 buildMethodTags(methodsContent); 343 } 344 345 /** 346 * Build method description. 347 * 348 * @param methodsContent the content to which the documentation will be added 349 */ 350 protected void buildMethodDescription(Content methodsContent) { 351 methodWriter.addMemberDescription((ExecutableElement) currentMember, 352 methodsContent); 353 } 354 355 /** 356 * Build the method tags. 357 * 358 * @param methodsContent the content to which the documentation will be added 359 */ 360 protected void buildMethodTags(Content methodsContent) { 361 methodWriter.addMemberTags((ExecutableElement) currentMember, 362 methodsContent); 363 ExecutableElement method = (ExecutableElement) currentMember; 364 if (method.getSimpleName().toString().compareTo("writeExternal") == 0 365 && utils.getSerialDataTrees(method).isEmpty()) { 366 if (options.serialWarn()) { 367 TypeElement encl = (TypeElement) method.getEnclosingElement(); 368 messages.warning(currentMember, 369 "doclet.MissingSerialDataTag", 370 encl.getQualifiedName().toString(), 371 method.getSimpleName().toString()); 372 } 373 } 374 } 375 376 /** 377 * Build the field header. 378 * 379 * @param classContent the content to which the documentation will be added 380 */ 381 protected void buildFieldHeader(Content classContent) { 382 if (!utils.serializableFields(currentTypeElement).isEmpty()) { 383 buildFieldSerializationOverview(currentTypeElement, classContent); 384 } 385 } 386 387 /** 388 * Build the serialization overview for the given class. 389 * 390 * @param typeElement the class to print the overview for. 391 * @param classContent the content to which the documentation will be added 392 */ 393 public void buildFieldSerializationOverview(TypeElement typeElement, 394 Content classContent) { 395 if (utils.definesSerializableFields(typeElement)) { 396 VariableElement ve = utils.serializableFields(typeElement).first(); 397 // Check to see if there are inline comments, tags or deprecation 398 // information to be printed. 399 if (fieldWriter.shouldPrintOverview(ve)) { 400 Content serializableFieldsHeader 401 = fieldWriter.getSerializableFieldsHeader(); 402 Content fieldsOverviewContent 403 = fieldWriter.getFieldsContentHeader(true); 404 fieldWriter.addMemberDeprecatedInfo(ve, fieldsOverviewContent); 405 if (!options.noComment()) { 406 fieldWriter.addMemberDescription(ve, fieldsOverviewContent); 407 fieldWriter.addMemberTags(ve, fieldsOverviewContent); 408 } 409 serializableFieldsHeader.add(fieldsOverviewContent); 410 classContent.add(fieldWriter.getSerializableFields( 411 resources.getText("doclet.Serialized_Form_class"), 412 serializableFieldsHeader)); 413 } 414 } 415 } 416 417 /** 418 * Build the summaries for the fields that belong to the given class. 419 * 420 * @param target the content to which the documentation will be added 421 * @throws DocletException if there is a problem while building the documentation 422 */ 423 protected void buildSerializableFields(Content target) 424 throws DocletException { 425 Collection<VariableElement> members 426 = utils.serializableFields(currentTypeElement); 427 if (!members.isEmpty()) { 428 Content serializableFieldsHeader 429 = fieldWriter.getSerializableFieldsHeader(); 430 for (var i = members.iterator(); i.hasNext();) { 431 currentMember = i.next(); 432 if (!utils.definesSerializableFields(currentTypeElement)) { 433 Content fieldsContent 434 = fieldWriter.getFieldsContentHeader(!i.hasNext()); 435 436 buildFieldSubHeader(fieldsContent); 437 buildFieldDeprecationInfo(fieldsContent); 438 buildFieldInfo(fieldsContent); 439 440 serializableFieldsHeader.add(fieldsContent); 441 } else { 442 buildSerialFieldTagsInfo(serializableFieldsHeader); 443 } 444 } 445 target.add(fieldWriter.getSerializableFields( 446 resources.getText("doclet.Serialized_Form_fields"), 447 serializableFieldsHeader)); 448 } 449 } 450 451 /** 452 * Build the field sub header. 453 * 454 * @param fieldsContent the content to which the documentation will be added 455 */ 456 protected void buildFieldSubHeader(Content fieldsContent) { 457 if (!utils.definesSerializableFields(currentTypeElement)) { 458 VariableElement field = (VariableElement) currentMember; 459 fieldWriter.addMemberHeader(field.asType(), 460 utils.getSimpleName(field), 461 fieldsContent); 462 } 463 } 464 465 /** 466 * Build the field deprecation information. 467 * 468 * @param fieldsContent the content to which the documentation will be added 469 */ 470 protected void buildFieldDeprecationInfo(Content fieldsContent) { 471 if (!utils.definesSerializableFields(currentTypeElement)) { 472 fieldWriter.addMemberDeprecatedInfo((VariableElement) currentMember, 473 fieldsContent); 474 } 475 } 476 477 /** 478 * Build the serial field tags information. 479 * 480 * @param target the content to which the documentation will be added 481 */ 482 protected void buildSerialFieldTagsInfo(Content target) { 483 if (options.noComment()) { 484 return; 485 } 486 VariableElement field = (VariableElement) currentMember; 487 // Process Serializable Fields specified as array of 488 // ObjectStreamFields. Print a member for each serialField tag. 489 // (There should be one serialField tag per ObjectStreamField 490 // element.) 491 SortedSet<SerialFieldTree> tags 492 = new TreeSet<>(utils.comparators.makeSerialFieldTreeComparator()); 493 // sort the elements 494 tags.addAll(utils.getSerialFieldTrees(field)); 495 496 CommentHelper ch = utils.getCommentHelper(field); 497 for (SerialFieldTree tag : tags) { 498 if (tag.getName() == null || tag.getType() == null) // ignore 499 // malformed 500 // @serialField 501 // tags 502 continue; 503 Content fieldsContent 504 = fieldWriter.getFieldsContentHeader(tag.equals(tags.last())); 505 TypeMirror type = ch.getReferencedType(tag); 506 fieldWriter.addMemberHeader(type, 507 tag.getName().getName().toString(), fieldsContent); 508 fieldWriter.addMemberDescription(field, tag, fieldsContent); 509 target.add(fieldsContent); 510 } 511 } 512 513 /** 514 * Build the field information. 515 * 516 * @param fieldsContent the content to which the documentation will be added 517 */ 518 protected void buildFieldInfo(Content fieldsContent) { 519 if (options.noComment()) { 520 return; 521 } 522 VariableElement field = (VariableElement) currentMember; 523 TypeElement te = utils.getEnclosingTypeElement(currentMember); 524 // Process default Serializable field. 525 if ((utils.getSerialTrees(field).isEmpty()) /* 526 * && !field.isSynthetic() 527 */ 528 && options.serialWarn()) { 529 messages.warning(field, 530 "doclet.MissingSerialTag", utils.getFullyQualifiedName(te), 531 utils.getSimpleName(field)); 532 } 533 fieldWriter.addMemberDescription(field, fieldsContent); 534 fieldWriter.addMemberTags(field, fieldsContent); 535 } 536 537 /** 538 * Returns true if the given Element should be included 539 * in the serialized form. 540 * 541 * @param utils the utils object 542 * @param element the Element object to check for serializability 543 * @return true if the element should be included in the serial form 544 */ 545 public static boolean serialInclude(Utils utils, Element element) { 546 if (element == null) { 547 return false; 548 } 549 return utils.isClass(element) 550 ? serialClassInclude(utils, (TypeElement) element) 551 : serialDocInclude(utils, element); 552 } 553 554 /** 555 * Returns true if the given TypeElement should be included 556 * in the serialized form. 557 * 558 * @param te the TypeElement object to check for serializability. 559 */ 560 private static boolean serialClassInclude(Utils utils, TypeElement te) { 561 if (utils.isEnum(te)) { 562 return false; 563 } 564 if (utils.isSerializable(te)) { 565 if (utils.hasDocCommentTree(te) 566 && !utils.getSerialTrees(te).isEmpty()) { 567 return serialDocInclude(utils, te); 568 } else { 569 return utils.isPublic(te) || utils.isProtected(te); 570 } 571 } 572 return false; 573 } 574 575 /** 576 * Return true if the given Element should be included 577 * in the serialized form. 578 * 579 * @param element the Element to check for serializability. 580 */ 581 private static boolean serialDocInclude(Utils utils, Element element) { 582 if (utils.isEnum(element)) { 583 return false; 584 } 585 List<? extends SerialTree> serial = utils.getSerialTrees(element); 586 if (!serial.isEmpty()) { 587 CommentHelper ch = utils.getCommentHelper(element); 588 // look for `@serial include|exclude` 589 String serialtext = Utils.toLowerCase(serial.get(0).toString()); 590 if (serialtext.contains("exclude")) { 591 return false; 592 } else if (serialtext.contains("include")) { 593 return true; 594 } 595 } 596 return true; 597 } 598 599 /** 600 * Return true if any of the given typeElements have a {@code @serial include} tag. 601 * 602 * @param classes the typeElements to check. 603 * @return true if any of the given typeElements have a {@code @serial include} tag. 604 */ 605 private boolean serialClassFoundToDocument(SortedSet<TypeElement> classes) { 606 for (TypeElement aClass : classes) { 607 if (serialClassInclude(utils, aClass)) { 608 return true; 609 } 610 } 611 return false; 612 } 613}