001/* 002 * Copyright (c) 2020, 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.formats.html; 027 028import jdk.javadoc.doclet.DocletEnvironment; 029 030import javax.lang.model.element.Element; 031import javax.lang.model.element.ElementKind; 032import javax.lang.model.element.Modifier; 033import javax.lang.model.element.ModuleElement; 034import javax.lang.model.element.PackageElement; 035import javax.lang.model.element.RecordComponentElement; 036import javax.lang.model.element.TypeElement; 037import javax.lang.model.type.TypeMirror; 038import javax.lang.model.util.ElementKindVisitor14; 039 040import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.ContentBuilder; 041import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.Entity; 042import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.HtmlStyle; 043import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.HtmlTree; 044import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.TagName; 045import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.Text; 046import org.jdrupes.mdoclet.internal.doclets.toolkit.Content; 047import org.jdrupes.mdoclet.internal.doclets.toolkit.util.Utils; 048 049import java.util.ArrayList; 050import java.util.List; 051import java.util.Set; 052import java.util.SortedSet; 053import java.util.TreeSet; 054import java.util.stream.Collectors; 055 056import static javax.lang.model.element.Modifier.ABSTRACT; 057import static javax.lang.model.element.Modifier.FINAL; 058import static javax.lang.model.element.Modifier.NATIVE; 059import static javax.lang.model.element.Modifier.PRIVATE; 060import static javax.lang.model.element.Modifier.PROTECTED; 061import static javax.lang.model.element.Modifier.PUBLIC; 062import static javax.lang.model.element.Modifier.STATIC; 063import static javax.lang.model.element.Modifier.STRICTFP; 064import static javax.lang.model.element.Modifier.SYNCHRONIZED; 065 066public class Signatures { 067 068 public static Content getModuleSignature(ModuleElement mdle, 069 ModuleWriterImpl moduleWriter) { 070 var signature = HtmlTree.DIV(HtmlStyle.moduleSignature); 071 Content annotations = moduleWriter.getAnnotationInfo(mdle, true); 072 if (!annotations.isEmpty()) { 073 signature.add(HtmlTree.SPAN(HtmlStyle.annotations, annotations)); 074 } 075 DocletEnvironment docEnv = moduleWriter.configuration.docEnv; 076 String label = mdle.isOpen() 077 && (docEnv.getModuleMode() == DocletEnvironment.ModuleMode.ALL) 078 ? "open module" 079 : "module"; 080 signature.add(label); 081 signature.add(" "); 082 var nameSpan = HtmlTree.SPAN(HtmlStyle.elementName); 083 nameSpan.add(mdle.getQualifiedName().toString()); 084 signature.add(nameSpan); 085 return signature; 086 } 087 088 public static Content getPackageSignature(PackageElement pkg, 089 PackageWriterImpl pkgWriter) { 090 if (pkg.isUnnamed()) { 091 return Text.EMPTY; 092 } 093 var signature = HtmlTree.DIV(HtmlStyle.packageSignature); 094 Content annotations = pkgWriter.getAnnotationInfo(pkg, true); 095 if (!annotations.isEmpty()) { 096 signature.add(HtmlTree.SPAN(HtmlStyle.annotations, annotations)); 097 } 098 signature.add("package "); 099 var nameSpan = HtmlTree.SPAN(HtmlStyle.elementName); 100 nameSpan.add(pkg.getQualifiedName().toString()); 101 signature.add(nameSpan); 102 return signature; 103 } 104 105 static class TypeSignature { 106 107 private final TypeElement typeElement; 108 private final HtmlDocletWriter writer; 109 private final Utils utils; 110 private final HtmlConfiguration configuration; 111 private Content modifiers; 112 113 private static final Set<String> previewModifiers = Set.of(); 114 115 TypeSignature(TypeElement typeElement, HtmlDocletWriter writer) { 116 this.typeElement = typeElement; 117 this.writer = writer; 118 this.utils = writer.utils; 119 this.configuration = writer.configuration; 120 this.modifiers = markPreviewModifiers(getModifiers()); 121 } 122 123 public TypeSignature setModifiers(Content modifiers) { 124 this.modifiers = modifiers; 125 return this; 126 } 127 128 public Content toContent() { 129 Content content = new ContentBuilder(); 130 Content annotationInfo 131 = writer.getAnnotationInfo(typeElement, true); 132 if (!annotationInfo.isEmpty()) { 133 content 134 .add(HtmlTree.SPAN(HtmlStyle.annotations, annotationInfo)); 135 } 136 content.add(HtmlTree.SPAN(HtmlStyle.modifiers, modifiers)); 137 138 var nameSpan = HtmlTree.SPAN(HtmlStyle.elementName); 139 Content className = Text.of(utils.getSimpleName(typeElement)); 140 if (configuration.getOptions().linkSource()) { 141 writer.addSrcLink(typeElement, className, nameSpan); 142 } else { 143 nameSpan.addStyle(HtmlStyle.typeNameLabel).add(className); 144 } 145 HtmlLinkInfo linkInfo = new HtmlLinkInfo(configuration, 146 HtmlLinkInfo.Kind.SHOW_TYPE_PARAMS_AND_BOUNDS, typeElement) 147 .linkToSelf(false) // Let's not link to ourselves in the 148 // signature 149 .showTypeParameterAnnotations(true); 150 nameSpan.add(writer.getTypeParameterLinks(linkInfo)); 151 content.add(nameSpan); 152 153 if (utils.isRecord(typeElement)) { 154 content.add(getRecordComponents()); 155 } 156 if (!utils.isAnnotationInterface(typeElement)) { 157 var extendsImplements 158 = HtmlTree.SPAN(HtmlStyle.extendsImplements); 159 if (!utils.isPlainInterface(typeElement)) { 160 TypeMirror superclass 161 = utils.getFirstVisibleSuperClass(typeElement); 162 if (superclass != null) { 163 content.add(Text.NL); 164 extendsImplements.add("extends "); 165 Content link 166 = writer.getLink(new HtmlLinkInfo(configuration, 167 HtmlLinkInfo.Kind.SHOW_TYPE_PARAMS, 168 superclass)); 169 extendsImplements.add(link); 170 } 171 } 172 List<? extends TypeMirror> interfaces 173 = typeElement.getInterfaces(); 174 if (!interfaces.isEmpty()) { 175 boolean isFirst = true; 176 for (TypeMirror type : interfaces) { 177 TypeElement tDoc = utils.asTypeElement(type); 178 if (!(utils.isPublic(tDoc) || utils.isLinkable(tDoc))) { 179 continue; 180 } 181 if (isFirst) { 182 extendsImplements.add(Text.NL); 183 extendsImplements.add( 184 utils.isPlainInterface(typeElement) ? "extends " 185 : "implements "); 186 isFirst = false; 187 } else { 188 extendsImplements.add(", "); 189 } 190 Content link 191 = writer.getLink(new HtmlLinkInfo(configuration, 192 HtmlLinkInfo.Kind.SHOW_TYPE_PARAMS, 193 type)); 194 extendsImplements.add(link); 195 } 196 } 197 if (!extendsImplements.isEmpty()) { 198 content.add(extendsImplements); 199 } 200 } 201 List<? extends TypeMirror> permits 202 = typeElement.getPermittedSubclasses(); 203 List<? extends TypeMirror> linkablePermits = permits.stream() 204 .filter(t -> utils.isLinkable(utils.asTypeElement(t))) 205 .toList(); 206 if (!linkablePermits.isEmpty()) { 207 var permitsSpan = HtmlTree.SPAN(HtmlStyle.permits); 208 boolean isFirst = true; 209 for (TypeMirror type : linkablePermits) { 210 if (isFirst) { 211 content.add(Text.NL); 212 permitsSpan.add("permits"); 213 permitsSpan.add(" "); 214 isFirst = false; 215 } else { 216 permitsSpan.add(", "); 217 } 218 Content link 219 = writer.getLink(new HtmlLinkInfo(configuration, 220 HtmlLinkInfo.Kind.SHOW_TYPE_PARAMS, 221 type)); 222 permitsSpan.add(link); 223 } 224 if (linkablePermits.size() < permits.size()) { 225 Content c = Text.of(configuration.getDocResources() 226 .getText("doclet.not.exhaustive")); 227 permitsSpan.add(" "); 228 permitsSpan.add(HtmlTree.SPAN(HtmlStyle.permitsNote, c)); 229 } 230 content.add(permitsSpan); 231 } 232 return HtmlTree.DIV(HtmlStyle.typeSignature, content); 233 } 234 235 private Content getRecordComponents() { 236 Content content = new ContentBuilder(); 237 content.add("("); 238 String sep = ""; 239 for (RecordComponentElement e : typeElement.getRecordComponents()) { 240 content.add(sep); 241 writer.getAnnotations(e.getAnnotationMirrors(), false) 242 .forEach(a -> content.add(a).add(" ")); 243 Content link = writer.getLink(new HtmlLinkInfo(configuration, 244 HtmlLinkInfo.Kind.LINK_TYPE_PARAMS_AND_BOUNDS, 245 e.asType())); 246 content.add(link); 247 content.add(Entity.NO_BREAK_SPACE); 248 content.add(e.getSimpleName()); 249 sep = ", "; 250 } 251 content.add(")"); 252 return content; 253 } 254 255 private Content markPreviewModifiers(List<String> modifiers) { 256 Content content = new ContentBuilder(); 257 String sep = null; 258 for (String modifier : modifiers) { 259 if (sep != null) { 260 content.add(sep); 261 } 262 content.add(modifier); 263 if (previewModifiers.contains(modifier)) { 264 content.add(HtmlTree.SUP(writer.links.createLink( 265 configuration.htmlIds.forPreviewSection(typeElement), 266 configuration.contents.previewMark))); 267 } 268 sep = " "; 269 } 270 content.add(" "); 271 return content; 272 } 273 274 private List<String> getModifiers() { 275 SortedSet<Modifier> modifiers 276 = new TreeSet<>(typeElement.getModifiers()); 277 modifiers.remove(NATIVE); 278 modifiers.remove(STRICTFP); 279 modifiers.remove(SYNCHRONIZED); 280 281 return new ElementKindVisitor14<List<String>, 282 SortedSet<Modifier>>() { 283 final List<String> list = new ArrayList<>(); 284 285 void addVisibilityModifier(Set<Modifier> modifiers) { 286 if (modifiers.contains(PUBLIC)) { 287 list.add("public"); 288 } else if (modifiers.contains(PROTECTED)) { 289 list.add("protected"); 290 } else if (modifiers.contains(PRIVATE)) { 291 list.add("private"); 292 } 293 } 294 295 void addStatic(Set<Modifier> modifiers) { 296 if (modifiers.contains(STATIC)) { 297 list.add("static"); 298 } 299 } 300 301 void addSealed(TypeElement e) { 302 if (e.getModifiers().contains(Modifier.SEALED)) { 303 list.add("sealed"); 304 } else if (e.getModifiers().contains(Modifier.NON_SEALED)) { 305 list.add("non-sealed"); 306 } 307 } 308 309 void addModifiers(Set<Modifier> modifiers) { 310 modifiers.stream() 311 .map(Modifier::toString) 312 .forEachOrdered(list::add); 313 } 314 315 @Override 316 public List<String> visitTypeAsInterface(TypeElement e, 317 SortedSet<Modifier> mods) { 318 addVisibilityModifier(mods); 319 addStatic(mods); 320 addSealed(e); 321 list.add("interface"); 322 return list; 323 } 324 325 @Override 326 public List<String> visitTypeAsEnum(TypeElement e, 327 SortedSet<Modifier> mods) { 328 addVisibilityModifier(mods); 329 addStatic(mods); 330 list.add("enum"); 331 return list; 332 } 333 334 @Override 335 public List<String> visitTypeAsAnnotationType(TypeElement e, 336 SortedSet<Modifier> mods) { 337 addVisibilityModifier(mods); 338 addStatic(mods); 339 list.add("@interface"); 340 return list; 341 } 342 343 @Override 344 public List<String> visitTypeAsRecord(TypeElement e, 345 SortedSet<Modifier> mods) { 346 mods.remove(FINAL); // suppress the implicit `final` 347 return visitTypeAsClass(e, mods); 348 } 349 350 @Override 351 public List<String> visitTypeAsClass(TypeElement e, 352 SortedSet<Modifier> mods) { 353 addModifiers(mods); 354 String keyword 355 = e.getKind() == ElementKind.RECORD ? "record" 356 : "class"; 357 list.add(keyword); 358 return list; 359 } 360 361 @Override 362 protected List<String> defaultAction(Element e, 363 SortedSet<Modifier> mods) { 364 addModifiers(mods); 365 return list; 366 } 367 368 }.visit(typeElement, modifiers); 369 } 370 } 371 372 /** 373 * A content builder for member signatures. 374 */ 375 static class MemberSignature { 376 377 private final AbstractMemberWriter memberWriter; 378 private final Utils utils; 379 380 private final Element element; 381 private Content annotations; 382 private Content typeParameters; 383 private Content returnType; 384 private Content parameters; 385 private Content exceptions; 386 387 // Threshold for length of type parameters before switching from inline 388 // to block representation. 389 private static final int TYPE_PARAMS_MAX_INLINE_LENGTH = 50; 390 391 // Threshold for combined length of modifiers, type params and return 392 // type before breaking 393 // it up with a line break before the return type. 394 private static final int RETURN_TYPE_MAX_LINE_LENGTH = 50; 395 396 /** 397 * Creates a new member signature builder. 398 * 399 * @param element the element for which to create a signature 400 * @param memberWriter the member writer 401 */ 402 MemberSignature(Element element, AbstractMemberWriter memberWriter) { 403 this.element = element; 404 this.memberWriter = memberWriter; 405 this.utils = memberWriter.utils; 406 } 407 408 /** 409 * Set the type parameters for an executable member. 410 * 411 * @param typeParameters the type parameters to add. 412 * @return this instance 413 */ 414 MemberSignature setTypeParameters(Content typeParameters) { 415 this.typeParameters = typeParameters; 416 return this; 417 } 418 419 /** 420 * Set the return type for an executable member. 421 * 422 * @param returnType the return type to add. 423 * @return this instance 424 */ 425 MemberSignature setReturnType(Content returnType) { 426 this.returnType = returnType; 427 return this; 428 } 429 430 /** 431 * Set the type information for a non-executable member. 432 * 433 * @param type the type of the member. 434 * @return this instance 435 */ 436 MemberSignature setType(TypeMirror type) { 437 this.returnType = memberWriter.writer 438 .getLink(new HtmlLinkInfo(memberWriter.configuration, 439 HtmlLinkInfo.Kind.LINK_TYPE_PARAMS_AND_BOUNDS, type)); 440 return this; 441 } 442 443 /** 444 * Set the parameter information of an executable member. 445 * 446 * @param content the parameter information. 447 * @return this instance 448 */ 449 MemberSignature setParameters(Content content) { 450 this.parameters = content; 451 return this; 452 } 453 454 /** 455 * Set the exception information of an executable member. 456 * 457 * @param content the exception information 458 * @return this instance 459 */ 460 MemberSignature setExceptions(Content content) { 461 this.exceptions = content; 462 return this; 463 } 464 465 /** 466 * Set the annotation information of a member. 467 * 468 * @param content the exception information 469 * @return this instance 470 */ 471 MemberSignature setAnnotations(Content content) { 472 this.annotations = content; 473 return this; 474 } 475 476 /** 477 * Returns an HTML tree containing the member signature. 478 * 479 * @return an HTML tree containing the member signature 480 */ 481 Content toContent() { 482 Content content = new ContentBuilder(); 483 // Position of last line separator. 484 int lastLineSeparator = 0; 485 486 // Annotations 487 if (annotations != null && !annotations.isEmpty()) { 488 content.add(HtmlTree.SPAN(HtmlStyle.annotations, annotations)); 489 lastLineSeparator = content.charCount(); 490 } 491 492 // Modifiers 493 appendModifiers(content); 494 495 // Type parameters 496 if (typeParameters != null && !typeParameters.isEmpty()) { 497 lastLineSeparator 498 = appendTypeParameters(content, lastLineSeparator); 499 } 500 501 // Return type 502 if (returnType != null) { 503 content.add(HtmlTree.SPAN(HtmlStyle.returnType, returnType)); 504 content.add(Entity.NO_BREAK_SPACE); 505 } 506 507 // Name 508 var nameSpan = HtmlTree.SPAN(HtmlStyle.elementName); 509 if (memberWriter.options.linkSource()) { 510 Content name = Text.of(memberWriter.name(element)); 511 memberWriter.writer.addSrcLink(element, name, nameSpan); 512 } else { 513 nameSpan.add(memberWriter.name(element)); 514 } 515 content.add(nameSpan); 516 517 // Parameters and exceptions 518 if (parameters != null) { 519 appendParametersAndExceptions(content, lastLineSeparator); 520 } 521 522 return HtmlTree.DIV(HtmlStyle.memberSignature, content); 523 } 524 525 /** 526 * Adds the modifiers for the member. The modifiers are ordered as specified 527 * by <em>The Java Language Specification</em>. 528 * 529 * @param target the content to which the modifier information will be added 530 */ 531 private void appendModifiers(Content target) { 532 Set<Modifier> set = new TreeSet<>(element.getModifiers()); 533 534 // remove the ones we really don't need 535 set.remove(NATIVE); 536 set.remove(SYNCHRONIZED); 537 set.remove(STRICTFP); 538 539 // According to JLS, we should not be showing public modifier for 540 // interface methods and fields. 541 if ((utils.isField(element) || utils.isMethod(element))) { 542 Element te = element.getEnclosingElement(); 543 if (utils.isInterface(te)) { 544 // Remove the implicit abstract and public modifiers 545 if (utils.isMethod(element)) { 546 set.remove(ABSTRACT); 547 } 548 set.remove(PUBLIC); 549 } 550 } 551 if (!set.isEmpty()) { 552 String mods = set.stream().map(Modifier::toString) 553 .collect(Collectors.joining(" ")); 554 target.add(HtmlTree.SPAN(HtmlStyle.modifiers, Text.of(mods))) 555 .add(Entity.NO_BREAK_SPACE); 556 } 557 } 558 559 /** 560 * Appends the type parameter information to the HTML tree. 561 * 562 * @param target the HTML tree 563 * @param lastLineSeparator index of last line separator in the HTML tree 564 * @return the new index of the last line separator 565 */ 566 private int appendTypeParameters(Content target, 567 int lastLineSeparator) { 568 // Apply different wrapping strategies for type parameters 569 // depending on the combined length of type parameters and return 570 // type. 571 int typeParamLength = typeParameters.charCount(); 572 573 if (typeParamLength >= TYPE_PARAMS_MAX_INLINE_LENGTH) { 574 target.add(HtmlTree.SPAN(HtmlStyle.typeParametersLong, 575 typeParameters)); 576 } else { 577 target.add( 578 HtmlTree.SPAN(HtmlStyle.typeParameters, typeParameters)); 579 } 580 581 int lineLength = target.charCount() - lastLineSeparator; 582 int newLastLineSeparator = lastLineSeparator; 583 584 // sum below includes length of modifiers plus type params added 585 // above 586 if (lineLength 587 + returnType.charCount() > RETURN_TYPE_MAX_LINE_LENGTH) { 588 target.add(Text.NL); 589 newLastLineSeparator = target.charCount(); 590 } else { 591 target.add(Entity.NO_BREAK_SPACE); 592 } 593 594 return newLastLineSeparator; 595 } 596 597 /** 598 * Appends the parameters and exceptions information to the HTML tree. 599 * 600 * @param target the HTML tree 601 * @param lastLineSeparator the index of the last line separator in the HTML tree 602 */ 603 private void appendParametersAndExceptions(Content target, 604 int lastLineSeparator) { 605 // Record current position for indentation of exceptions 606 int indentSize = target.charCount() - lastLineSeparator; 607 608 if (parameters.charCount() == 2) { 609 // empty parameters are added without packing 610 target.add(parameters); 611 } else { 612 target.add(new HtmlTree(TagName.WBR)) 613 .add(HtmlTree.SPAN(HtmlStyle.parameters, parameters)); 614 } 615 616 // Exceptions 617 if (exceptions != null && !exceptions.isEmpty()) { 618 CharSequence indent 619 = " ".repeat(Math.max(0, indentSize + 1 - 7)); 620 target.add(Text.NL) 621 .add(indent) 622 .add("throws ") 623 .add(HtmlTree.SPAN(HtmlStyle.exceptions, exceptions)); 624 } 625 } 626 } 627}