001/* 002 * Copyright (c) 1998, 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 java.net.URI; 029import java.util.ArrayList; 030import java.util.Comparator; 031import java.util.EnumSet; 032import java.util.HashMap; 033import java.util.HashSet; 034import java.util.LinkedList; 035import java.util.List; 036import java.util.ListIterator; 037import java.util.Locale; 038import java.util.Map; 039import java.util.Objects; 040import java.util.Optional; 041import java.util.Set; 042import java.util.regex.Matcher; 043import java.util.regex.Pattern; 044import javax.lang.model.element.AnnotationMirror; 045import javax.lang.model.element.AnnotationValue; 046import javax.lang.model.element.Element; 047import javax.lang.model.element.ExecutableElement; 048import javax.lang.model.element.ModuleElement; 049import javax.lang.model.element.Name; 050import javax.lang.model.element.PackageElement; 051import javax.lang.model.element.QualifiedNameable; 052import javax.lang.model.element.TypeElement; 053import javax.lang.model.element.VariableElement; 054import javax.lang.model.type.DeclaredType; 055import javax.lang.model.type.TypeMirror; 056import javax.lang.model.util.SimpleAnnotationValueVisitor9; 057import javax.lang.model.util.SimpleElementVisitor14; 058import javax.lang.model.util.SimpleTypeVisitor9; 059 060import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.ContentBuilder; 061import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.Entity; 062import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.Head; 063import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.HtmlDocument; 064import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.HtmlId; 065import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.HtmlStyle; 066import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.HtmlTree; 067import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.Links; 068import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.RawHtml; 069import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.Script; 070import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.TagName; 071import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.Text; 072import org.jdrupes.mdoclet.internal.doclets.toolkit.Content; 073import org.jdrupes.mdoclet.internal.doclets.toolkit.Messages; 074import org.jdrupes.mdoclet.internal.doclets.toolkit.Resources; 075import org.jdrupes.mdoclet.internal.doclets.toolkit.taglets.DocRootTaglet; 076import org.jdrupes.mdoclet.internal.doclets.toolkit.taglets.Taglet; 077import org.jdrupes.mdoclet.internal.doclets.toolkit.taglets.TagletWriter; 078import org.jdrupes.mdoclet.internal.doclets.toolkit.util.CommentHelper; 079import org.jdrupes.mdoclet.internal.doclets.toolkit.util.Comparators; 080import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocFile; 081import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocFileIOException; 082import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocLink; 083import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocPath; 084import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocPaths; 085import org.jdrupes.mdoclet.internal.doclets.toolkit.util.IndexItem; 086import org.jdrupes.mdoclet.internal.doclets.toolkit.util.Utils; 087import org.jdrupes.mdoclet.internal.doclets.toolkit.util.VisibleMemberTable; 088import org.jdrupes.mdoclet.internal.doclets.toolkit.util.Utils.DeclarationPreviewLanguageFeatures; 089import org.jdrupes.mdoclet.internal.doclets.toolkit.util.Utils.ElementFlag; 090import org.jdrupes.mdoclet.internal.doclets.toolkit.util.Utils.PreviewSummary; 091import org.jdrupes.mdoclet.internal.doclint.HtmlTag; 092 093import com.sun.source.doctree.AttributeTree; 094import com.sun.source.doctree.AttributeTree.ValueKind; 095import com.sun.source.doctree.CommentTree; 096import com.sun.source.doctree.DeprecatedTree; 097import com.sun.source.doctree.DocRootTree; 098import com.sun.source.doctree.DocTree; 099import com.sun.source.doctree.DocTree.Kind; 100import com.sun.source.doctree.EndElementTree; 101import com.sun.source.doctree.EntityTree; 102import com.sun.source.doctree.ErroneousTree; 103import com.sun.source.doctree.EscapeTree; 104import com.sun.source.doctree.IndexTree; 105import com.sun.source.doctree.InheritDocTree; 106import com.sun.source.doctree.LinkTree; 107import com.sun.source.doctree.LiteralTree; 108import com.sun.source.doctree.StartElementTree; 109import com.sun.source.doctree.SummaryTree; 110import com.sun.source.doctree.SystemPropertyTree; 111import com.sun.source.doctree.TextTree; 112import com.sun.source.util.DocTreePath; 113import com.sun.source.util.SimpleDocTreeVisitor; 114 115import static com.sun.source.doctree.DocTree.Kind.CODE; 116import static com.sun.source.doctree.DocTree.Kind.COMMENT; 117import static com.sun.source.doctree.DocTree.Kind.LINK; 118import static com.sun.source.doctree.DocTree.Kind.LINK_PLAIN; 119import static com.sun.source.doctree.DocTree.Kind.SEE; 120import static com.sun.source.doctree.DocTree.Kind.TEXT; 121 122/** 123 * Class for the Html Format Code Generation specific to JavaDoc. 124 * This Class contains methods related to the Html Code Generation which 125 * are used extensively while generating the entire documentation. 126 */ 127public class HtmlDocletWriter { 128 129 /** 130 * Relative path from the file getting generated to the destination 131 * directory. For example, if the file getting generated is 132 * "java/lang/Object.html", then the path to the root is "../..". 133 * This string can be empty if the file getting generated is in 134 * the destination directory. 135 */ 136 public final DocPath pathToRoot; 137 138 /** 139 * Platform-independent path from the current or the 140 * destination directory to the file getting generated. 141 * Used when creating the file. 142 */ 143 public final DocPath path; 144 145 /** 146 * Name of the file getting generated. If the file getting generated is 147 * "java/lang/Object.html", then the filename is "Object.html". 148 */ 149 public final DocPath filename; 150 151 /** 152 * The global configuration information for this run. 153 */ 154 public final HtmlConfiguration configuration; 155 156 protected final HtmlOptions options; 157 158 protected final Utils utils; 159 160 protected final Contents contents; 161 162 protected final Messages messages; 163 164 protected final Resources resources; 165 166 protected final Links links; 167 168 protected final DocPaths docPaths; 169 170 protected final Comparators comparators; 171 172 protected final HtmlIds htmlIds; 173 174 private final Set<String> headingIds = new HashSet<>(); 175 176 /** 177 * To check whether the repeated annotations is documented or not. 178 */ 179 private boolean isAnnotationDocumented = false; 180 181 /** 182 * To check whether the container annotations is documented or not. 183 */ 184 private boolean isContainerDocumented = false; 185 186 /** 187 * The window title of this file. 188 */ 189 protected String winTitle; 190 191 protected Script mainBodyScript; 192 193 /** 194 * A table of the anchors used for at-index and related tags, 195 * so that they can be made unique by appending a suitable suffix. 196 * (Ideally, javadoc should be tracking all id's generated in a file 197 * to avoid generating duplicates.) 198 */ 199 Map<String, Integer> indexAnchorTable = new HashMap<>(); 200 201 /** 202 * Creates an {@code HtmlDocletWriter}. 203 * 204 * @param configuration the configuration for this doclet 205 * @param path the file to be generated. 206 */ 207 public HtmlDocletWriter(HtmlConfiguration configuration, DocPath path) { 208 this.configuration = configuration; 209 this.options = configuration.getOptions(); 210 this.contents = configuration.getContents(); 211 this.messages = configuration.messages; 212 this.resources = configuration.docResources; 213 this.links = new Links(path); 214 this.utils = configuration.utils; 215 this.comparators = utils.comparators; 216 this.htmlIds = configuration.htmlIds; 217 this.path = path; 218 this.pathToRoot = path.parent().invert(); 219 this.filename = path.basename(); 220 this.docPaths = configuration.docPaths; 221 this.mainBodyScript = new Script(); 222 223 messages.notice("doclet.Generating_0", 224 DocFile.createFileForOutput(configuration, path).getPath()); 225 } 226 227 /** 228 * Replace {@docRoot} tag used in options that accept HTML text, such 229 * as -header, -footer, -top and -bottom, and when converting a relative 230 * HREF where commentTagsToString inserts a {@docRoot} where one was 231 * missing. (Also see DocRootTaglet for {@docRoot} tags in doc 232 * comments.) 233 * <p> 234 * Replace {@docRoot} tag in htmlstr with the relative path to the 235 * destination directory from the directory where the file is being 236 * written, looping to handle all such tags in htmlstr. 237 * <p> 238 * For example, for "-d docs" and -header containing {@docRoot}, when 239 * the HTML page for source file p/C1.java is being generated, the 240 * {@docRoot} tag would be inserted into the header as "../", 241 * the relative path from docs/p/ to docs/ (the document root). 242 * <p> 243 * Note: This doc comment was written with '&#064;' representing '@' 244 * to prevent the inline tag from being interpreted. 245 */ 246 public String replaceDocRootDir(String htmlstr) { 247 // Return if no inline tags exist 248 int index = htmlstr.indexOf("{@"); 249 if (index < 0) { 250 return htmlstr; 251 } 252 Matcher docrootMatcher = docrootPattern.matcher(htmlstr); 253 if (!docrootMatcher.find()) { 254 return htmlstr; 255 } 256 StringBuilder buf = new StringBuilder(); 257 int prevEnd = 0; 258 do { 259 int match = docrootMatcher.start(); 260 // append htmlstr up to start of next {@docroot} 261 buf.append(htmlstr, prevEnd, match); 262 prevEnd = docrootMatcher.end(); 263 if (options.docrootParent().length() > 0 264 && htmlstr.startsWith("/..", prevEnd)) { 265 // Insert the absolute link if {@docRoot} is followed by "/..". 266 buf.append(options.docrootParent()); 267 prevEnd += 3; 268 } else { 269 // Insert relative path where {@docRoot} was located 270 buf.append(pathToRoot.isEmpty() ? "." : pathToRoot.getPath()); 271 } 272 // Append slash if next character is not a slash 273 if (prevEnd < htmlstr.length() && htmlstr.charAt(prevEnd) != '/') { 274 buf.append('/'); 275 } 276 } while (docrootMatcher.find()); 277 buf.append(htmlstr.substring(prevEnd)); 278 return buf.toString(); 279 } 280 281 // where: 282 // Note: {@docRoot} is not case sensitive when passed in with a command-line 283 // option: 284 private static final Pattern docrootPattern = Pattern 285 .compile(Pattern.quote("{@docroot}"), Pattern.CASE_INSENSITIVE); 286 287 /** 288 * Add method information. 289 * 290 * @param method the method to be documented 291 * @param dl the content to which the method information will be added 292 */ 293 private void addMethodInfo(ExecutableElement method, Content dl) { 294 var enclosing = (TypeElement) method.getEnclosingElement(); 295 var overrideInfo = utils.overriddenMethod(method); 296 var enclosingVmt = configuration.getVisibleMemberTable(enclosing); 297 var implementedMethods = enclosingVmt.getImplementedMethods(method); 298 if ((!enclosing.getInterfaces().isEmpty() 299 && !implementedMethods.isEmpty()) 300 || overrideInfo != null) { 301 // TODO note that if there are any overridden interface methods 302 // throughout the 303 // hierarchy, !enclosingVmt.getImplementedMethods(method).isEmpty(), 304 // their information 305 // will be printed if *any* of the below is true: 306 // * the enclosing has _directly_ implemented interfaces 307 // * the overridden method is not null 308 // If both are false, the information will not be printed: there 309 // will be no 310 // "Specified by" documentation. The examples of that can be seen in 311 // documentation 312 // for these methods: 313 // * ForkJoinPool.execute(java.lang.Runnable) 314 // This is a long-standing bug, which must be fixed separately: 315 // JDK-8302316 316 MethodWriterImpl.addImplementsInfo(this, method, implementedMethods, 317 dl); 318 } 319 if (overrideInfo != null) { 320 MethodWriterImpl.addOverridden(this, 321 overrideInfo.overriddenMethodOwner(), 322 overrideInfo.overriddenMethod(), 323 dl); 324 } 325 } 326 327 /** 328 * Adds the tags information. 329 * 330 * @param e the Element for which the tags will be generated 331 * @param content the content to which the tags will be added 332 */ 333 protected void addTagsInfo(Element e, Content content) { 334 if (options.noComment()) { 335 return; 336 } 337 var dl = HtmlTree.DL(HtmlStyle.notes); 338 if (utils.isMethod(e)) { 339 addMethodInfo((ExecutableElement) e, dl); 340 } 341 Content output = getBlockTagOutput(e); 342 dl.add(output); 343 content.add(dl); 344 } 345 346 /** 347 * Returns the content generated from the default supported set of block tags 348 * for this element. 349 * 350 * @param element the element 351 * 352 * @return the content 353 */ 354 protected Content getBlockTagOutput(Element element) { 355 return getBlockTagOutput(element, 356 configuration.tagletManager.getBlockTaglets(element)); 357 } 358 359 /** 360 * Returns the content generated from a specified set of block tags 361 * for this element. 362 * 363 * @param element the element 364 * @param taglets the taglets to handle the required set of tags 365 * 366 * @return the content 367 */ 368 protected Content getBlockTagOutput(Element element, List<Taglet> taglets) { 369 return getTagletWriterInstance(false) 370 .getBlockTagOutput(configuration.tagletManager, element, taglets); 371 } 372 373 /** 374 * Returns whether there are any tags in a field for the Serialization Overview 375 * section to be generated. 376 * 377 * @param field the field to check 378 * @return {@code true} if and only if there are tags to be included 379 */ 380 protected boolean hasSerializationOverviewTags(VariableElement field) { 381 Content output = getBlockTagOutput(field); 382 return !output.isEmpty(); 383 } 384 385 private Content getInlineTagOutput(Element element, DocTree tree, 386 TagletWriterImpl.Context context) { 387 return getTagletWriterInstance(context) 388 .getInlineTagOutput(element, configuration.tagletManager, tree); 389 } 390 391 /** 392 * Returns a TagletWriter that knows how to write HTML. 393 * 394 * @param isFirstSentence true if we want to write the first sentence 395 * @return a TagletWriter that knows how to write HTML. 396 */ 397 public TagletWriter getTagletWriterInstance(boolean isFirstSentence) { 398 return new TagletWriterImpl(this, isFirstSentence); 399 } 400 401 /** 402 * Returns a TagletWriter that knows how to write HTML. 403 * 404 * @param context the enclosing context 405 * @return a TagletWriter 406 */ 407 public TagletWriterImpl 408 getTagletWriterInstance(TagletWriterImpl.Context context) { 409 return new TagletWriterImpl(this, context); 410 } 411 412 /** 413 * Generates the HTML document tree and prints it out. 414 * 415 * @param metakeywords Array of String keywords for META tag. Each element 416 * of the array is assigned to a separate META tag. 417 * Pass in null for no array 418 * @param description the content for the description META tag. 419 * @param body the body htmltree to be included in the document 420 * @throws DocFileIOException if there is a problem writing the file 421 */ 422 public void printHtmlDocument(List<String> metakeywords, 423 String description, 424 Content body) 425 throws DocFileIOException { 426 printHtmlDocument(metakeywords, description, new ContentBuilder(), 427 List.of(), body); 428 } 429 430 /** 431 * Generates the HTML document tree and prints it out. 432 * 433 * @param metakeywords Array of String keywords for META tag. Each element 434 * of the array is assigned to a separate META tag. 435 * Pass in null for no array 436 * @param description the content for the description META tag. 437 * @param localStylesheets local stylesheets to be included in the HEAD element 438 * @param body the body htmltree to be included in the document 439 * @throws DocFileIOException if there is a problem writing the file 440 */ 441 public void printHtmlDocument(List<String> metakeywords, 442 String description, 443 List<DocPath> localStylesheets, 444 Content body) 445 throws DocFileIOException { 446 printHtmlDocument(metakeywords, description, new ContentBuilder(), 447 localStylesheets, body); 448 } 449 450 /** 451 * Generates the HTML document tree and prints it out. 452 * 453 * @param metakeywords Array of String keywords for META tag. Each element 454 * of the array is assigned to a separate META tag. 455 * Pass in null for no array 456 * @param description the content for the description META tag. 457 * @param extraHeadContent any additional content to be included in the HEAD element 458 * @param localStylesheets local stylesheets to be included in the HEAD element 459 * @param body the body htmltree to be included in the document 460 * @throws DocFileIOException if there is a problem writing the file 461 */ 462 public void printHtmlDocument(List<String> metakeywords, 463 String description, 464 Content extraHeadContent, 465 List<DocPath> localStylesheets, 466 Content body) 467 throws DocFileIOException { 468 List<DocPath> additionalStylesheets 469 = configuration.getAdditionalStylesheets(); 470 additionalStylesheets.addAll(localStylesheets); 471 Head head = new Head(path, configuration.getDocletVersion(), 472 configuration.getBuildDate()) 473 .setTimestamp(!options.noTimestamp()) 474 .setDescription(description) 475 .setGenerator(getGenerator(getClass())) 476 .setTitle(winTitle) 477 .setCharset(options.charset()) 478 .addKeywords(metakeywords) 479 .setStylesheets(configuration.getMainStylesheet(), 480 additionalStylesheets) 481 .setAdditionalScripts(configuration.getAdditionalScripts()) 482 .setIndex(options.createIndex(), mainBodyScript) 483 .addContent(extraHeadContent); 484 485 HtmlDocument htmlDocument = new HtmlDocument( 486 HtmlTree.HTML(configuration.getLocale().getLanguage(), head, body)); 487 htmlDocument.write(DocFile.createFileForOutput(configuration, path)); 488 } 489 490 /** 491 * Get the window title. 492 * 493 * @param title the title string to construct the complete window title 494 * @return the window title string 495 */ 496 public String getWindowTitle(String title) { 497 if (options.windowTitle().length() > 0) { 498 title += " (" + options.windowTitle() + ")"; 499 } 500 return title; 501 } 502 503 /** 504 * Returns a {@code <header>} element, containing the user "top" text, if any, 505 * and the main navigation bar. 506 * 507 * @param pageMode the pageMode used to configure the navigation bar 508 * 509 * @return the {@code <header>} element 510 */ 511 protected HtmlTree getHeader(Navigation.PageMode pageMode) { 512 return getHeader(pageMode, null); 513 } 514 515 /** 516 * Returns a {@code <header>} element, containing the user "top" text, if any, 517 * and the main navigation bar. 518 * 519 * @param pageMode the page mode used to configure the navigation bar 520 * @param element the element used to configure the navigation bar 521 * 522 * @return the {@code <header>} element 523 */ 524 protected HtmlTree getHeader(Navigation.PageMode pageMode, 525 Element element) { 526 return HtmlTree.HEADER() 527 .add(RawHtml.of(replaceDocRootDir(options.top()))) 528 .add(getNavBar(pageMode, element).getContent()); 529 } 530 531 /** 532 * Returns a basic navigation bar for a kind of page and element. 533 * 534 * @apiNote the result may be further configured by overriding this method 535 * 536 * @param pageMode the page mode 537 * @param element the defining element for the navigation bar, or {@code null} if none 538 * @return the basic navigation bar 539 */ 540 protected Navigation getNavBar(Navigation.PageMode pageMode, 541 Element element) { 542 return new Navigation(element, configuration, pageMode, path) 543 .setUserHeader(RawHtml.of(replaceDocRootDir(options.header()))); 544 } 545 546 /** 547 * Returns a {@code <footer>} element containing the user's "bottom" text, 548 * or {@code null} if there is no such text. 549 * 550 * @return the {@code <footer>} element or {@code null}. 551 */ 552 public HtmlTree getFooter() { 553 String bottom = options.bottom(); 554 return (bottom == null || bottom.isEmpty()) 555 ? null 556 : HtmlTree.FOOTER() 557 .add(new HtmlTree(TagName.HR)) 558 .add(HtmlTree.P(HtmlStyle.legalCopy, 559 HtmlTree.SMALL( 560 RawHtml.of(replaceDocRootDir(bottom))))); 561 } 562 563 /** 564 * {@return an "overview tree" link for a navigation bar} 565 * 566 * @param label the label for the link 567 */ 568 protected Content getNavLinkToOverviewTree(String label) { 569 Content link 570 = links.createLink(pathToRoot.resolve(DocPaths.OVERVIEW_TREE), 571 Text.of(label)); 572 return HtmlTree.LI(link); 573 } 574 575 /** 576 * {@return a package name} 577 * 578 * A localized name is returned for an unnamed package. 579 * Use {@link Utils#getPackageName(PackageElement)} to get a static string 580 * for the unnamed package instead. 581 * 582 * @param packageElement the package to get the name for 583 */ 584 public Content getLocalizedPackageName(PackageElement packageElement) { 585 return packageElement == null || packageElement.isUnnamed() 586 ? contents.defaultPackageLabel 587 : getPackageLabel(packageElement.getQualifiedName()); 588 } 589 590 /** 591 * Returns a package name label. 592 * 593 * @param packageName the package name 594 * @return the package name content 595 */ 596 public Content getPackageLabel(CharSequence packageName) { 597 return Text.of(packageName); 598 } 599 600 /** 601 * Return the path to the class page for a typeElement. 602 * 603 * @param te TypeElement for which the path is requested. 604 * @param name Name of the file(doesn't include path). 605 */ 606 protected DocPath pathString(TypeElement te, DocPath name) { 607 return pathString(utils.containingPackage(te), name); 608 } 609 610 /** 611 * Return path to the given file name in the given package. So if the name 612 * passed is "Object.html" and the name of the package is "java.lang", and 613 * if the relative path is "../.." then returned string will be 614 * "../../java/lang/Object.html" 615 * 616 * @param packageElement Package in which the file name is assumed to be. 617 * @param name File name, to which path string is. 618 */ 619 protected DocPath pathString(PackageElement packageElement, DocPath name) { 620 return pathToRoot 621 .resolve(docPaths.forPackage(packageElement).resolve(name)); 622 } 623 624 /** 625 * {@return the link to the given package} 626 * 627 * @param packageElement the package to link to 628 * @param label the label for the link 629 */ 630 public Content getPackageLink(PackageElement packageElement, 631 Content label) { 632 return getPackageLink(packageElement, label, null); 633 } 634 635 /** 636 * {@return the link to the given package} 637 * 638 * @param packageElement the package to link to 639 * @param label the label for the link 640 * @param fragment the link fragment 641 */ 642 public Content getPackageLink(PackageElement packageElement, Content label, 643 String fragment) { 644 boolean included 645 = packageElement != null && utils.isIncluded(packageElement); 646 if (!included) { 647 for (PackageElement p : configuration.packages) { 648 if (p.equals(packageElement)) { 649 included = true; 650 break; 651 } 652 } 653 } 654 Set<ElementFlag> flags; 655 if (packageElement != null) { 656 flags = utils.elementFlags(packageElement); 657 } else { 658 flags = EnumSet.noneOf(ElementFlag.class); 659 } 660 DocLink targetLink; 661 if (included || packageElement == null) { 662 targetLink = new DocLink( 663 pathString(packageElement, DocPaths.PACKAGE_SUMMARY), fragment); 664 } else { 665 targetLink = getCrossPackageLink(packageElement); 666 } 667 if (targetLink != null) { 668 if (flags.contains(ElementFlag.PREVIEW)) { 669 return new ContentBuilder( 670 links.createLink(targetLink, label), 671 HtmlTree.SUP(links.createLink( 672 targetLink.withFragment( 673 htmlIds.forPreviewSection(packageElement).name()), 674 contents.previewMark))); 675 } 676 return links.createLink(targetLink, label); 677 } else { 678 if (flags.contains(ElementFlag.PREVIEW)) { 679 return new ContentBuilder( 680 label, 681 HtmlTree.SUP(contents.previewMark)); 682 } 683 return label; 684 } 685 } 686 687 /** 688 * {@return a link to module} 689 * 690 * @param mdle the module being documented 691 * @param label tag for the link 692 */ 693 public Content getModuleLink(ModuleElement mdle, Content label) { 694 return getModuleLink(mdle, label, null); 695 } 696 697 /** 698 * {@return a link to module} 699 * 700 * @param mdle the module being documented 701 * @param label tag for the link 702 * @param fragment the link fragment 703 */ 704 public Content getModuleLink(ModuleElement mdle, Content label, 705 String fragment) { 706 Set<ElementFlag> flags = mdle != null ? utils.elementFlags(mdle) 707 : EnumSet.noneOf(ElementFlag.class); 708 boolean included = utils.isIncluded(mdle); 709 if (included) { 710 DocLink targetLink; 711 targetLink = new DocLink( 712 pathToRoot.resolve(docPaths.moduleSummary(mdle)), fragment); 713 Content link = links.createLink(targetLink, label, ""); 714 if (flags.contains(ElementFlag.PREVIEW) 715 && label != contents.moduleLabel) { 716 link = new ContentBuilder( 717 link, 718 HtmlTree.SUP(links.createLink( 719 targetLink.withFragment( 720 htmlIds.forPreviewSection(mdle).name()), 721 contents.previewMark))); 722 } 723 return link; 724 } 725 if (flags.contains(ElementFlag.PREVIEW)) { 726 return new ContentBuilder( 727 label, 728 HtmlTree.SUP(contents.previewMark)); 729 } 730 return label; 731 } 732 733 /** 734 * Add the link to the content. 735 * 736 * @param element program element for which the link will be added 737 * @param label label for the link 738 * @param target the content to which the link will be added 739 */ 740 public void addSrcLink(Element element, Content label, Content target) { 741 if (element == null) { 742 return; 743 } 744 TypeElement te = utils.getEnclosingTypeElement(element); 745 if (te == null) { 746 // must be a typeElement since in has no containing class. 747 te = (TypeElement) element; 748 } 749 if (utils.isIncluded(te)) { 750 DocPath href = pathToRoot 751 .resolve(DocPaths.SOURCE_OUTPUT) 752 .resolve(docPaths.forClass(te)); 753 Content content = links.createLink(href 754 .fragment( 755 SourceToHTMLConverter.getAnchorName(utils, element).name()), 756 label, ""); 757 target.add(content); 758 } else { 759 target.add(label); 760 } 761 } 762 763 /** 764 * Return the link to the given class. 765 * 766 * @param linkInfo the information about the link. 767 * 768 * @return the link for the given class. 769 */ 770 public Content getLink(HtmlLinkInfo linkInfo) { 771 HtmlLinkFactory factory = new HtmlLinkFactory(this); 772 return factory.getLink(linkInfo); 773 } 774 775 /** 776 * Return the type parameters for the given class. 777 * 778 * @param linkInfo the information about the link. 779 * @return the type for the given class. 780 */ 781 public Content getTypeParameterLinks(HtmlLinkInfo linkInfo) { 782 HtmlLinkFactory factory = new HtmlLinkFactory(this); 783 return factory.getTypeParameterLinks(linkInfo); 784 } 785 786 /************************************************************* 787 * Return a class cross link to external class documentation. 788 * The -link option does not allow users to 789 * link to external classes in the "default" package. 790 * 791 * @param classElement the class element 792 * @param refMemName the name of the member being referenced. This should 793 * be null or empty string if no member is being referenced. 794 * @param label the label for the external link. 795 * @param style optional style for the link. 796 * @param code true if the label should be code font. 797 * @return the link 798 */ 799 public Content getCrossClassLink(TypeElement classElement, 800 String refMemName, 801 Content label, HtmlStyle style, boolean code) { 802 if (classElement != null) { 803 String className = utils.getSimpleName(classElement); 804 PackageElement packageElement 805 = utils.containingPackage(classElement); 806 Content defaultLabel = Text.of(className); 807 if (code) 808 defaultLabel = HtmlTree.CODE(defaultLabel); 809 if (getCrossPackageLink(packageElement) != null) { 810 /* 811 * The package exists in external documentation, so link to the 812 * external 813 * class (assuming that it exists). This is definitely a 814 * limitation of 815 * the -link option. There are ways to determine if an external 816 * package 817 * exists, but no way to determine if the external class exists. 818 * We just 819 * have to assume that it does. 820 */ 821 DocLink link = configuration.extern.getExternalLink( 822 packageElement, pathToRoot, 823 className + ".html", refMemName); 824 return links.createLink(link, 825 (label == null) || label.isEmpty() ? defaultLabel : label, 826 style, 827 resources.getText("doclet.Href_Class_Or_Interface_Title", 828 getLocalizedPackageName(packageElement)), 829 true); 830 } 831 } 832 return null; 833 } 834 835 public DocLink getCrossPackageLink(PackageElement element) { 836 return configuration.extern.getExternalLink(element, pathToRoot, 837 DocPaths.PACKAGE_SUMMARY.getPath()); 838 } 839 840 public DocLink getCrossModuleLink(ModuleElement element) { 841 return configuration.extern.getExternalLink(element, pathToRoot, 842 docPaths.moduleSummary(utils.getModuleName(element)).getPath()); 843 } 844 845 /** 846 * {@return a link to the given class} 847 * 848 * @param context the id of the context where the link will be added 849 * @param element the class to link to 850 */ 851 public Content getQualifiedClassLink(HtmlLinkInfo.Kind context, 852 Element element) { 853 HtmlLinkInfo htmlLinkInfo 854 = new HtmlLinkInfo(configuration, context, (TypeElement) element); 855 return getLink( 856 htmlLinkInfo.label(utils.getFullyQualifiedName(element))); 857 } 858 859 /** 860 * Adds a link to the given class. 861 * 862 * @param context the id of the context where the link will be added 863 * @param typeElement the class to link to 864 * @param target the content to which the link will be added 865 */ 866 public void addPreQualifiedClassLink(HtmlLinkInfo.Kind context, 867 TypeElement typeElement, Content target) { 868 addPreQualifiedClassLink(context, typeElement, null, target); 869 } 870 871 /** 872 * Retrieve the class link with the package portion of the label in 873 * plain text. If the qualifier is excluded, it will not be included in the 874 * link label. 875 * 876 * @param typeElement the class to link to. 877 * @return the link with the package portion of the label in plain text. 878 */ 879 public Content getPreQualifiedClassLink(HtmlLinkInfo.Kind context, 880 TypeElement typeElement) { 881 ContentBuilder classlink = new ContentBuilder(); 882 PackageElement pkg = utils.containingPackage(typeElement); 883 if (pkg != null && !configuration 884 .shouldExcludeQualifier(pkg.getSimpleName().toString())) { 885 classlink.add(getEnclosingPackageName(typeElement)); 886 } 887 classlink.add(getLink(new HtmlLinkInfo(configuration, 888 context, typeElement).label(utils.getSimpleName(typeElement)))); 889 return classlink; 890 } 891 892 /** 893 * Add the class link with the package portion of the label in 894 * plain text. If the qualifier is excluded, it will not be included in the 895 * link label. 896 * 897 * @param context the id of the context where the link will be added 898 * @param typeElement the class to link to 899 * @param style optional style for the link 900 * @param target the content to which the link with be added 901 */ 902 public void addPreQualifiedClassLink(HtmlLinkInfo.Kind context, 903 TypeElement typeElement, HtmlStyle style, Content target) { 904 PackageElement pkg = utils.containingPackage(typeElement); 905 if (pkg != null && !configuration 906 .shouldExcludeQualifier(pkg.getSimpleName().toString())) { 907 target.add(getEnclosingPackageName(typeElement)); 908 } 909 HtmlLinkInfo linkinfo 910 = new HtmlLinkInfo(configuration, context, typeElement) 911 .label(utils.getSimpleName(typeElement)) 912 .style(style); 913 Content link = getLink(linkinfo); 914 target.add(link); 915 } 916 917 /** 918 * Get the enclosed name of the package 919 * 920 * @param te TypeElement 921 * @return the name 922 */ 923 public String getEnclosingPackageName(TypeElement te) { 924 925 PackageElement encl = configuration.utils.containingPackage(te); 926 return (encl.isUnnamed()) ? "" : (encl.getQualifiedName() + "."); 927 } 928 929 /** 930 * Return the main type element of the current page or null for pages that don't have one. 931 * 932 * @return the type element of the current page. 933 */ 934 protected TypeElement getCurrentPageElement() { 935 return null; 936 } 937 938 /** 939 * Add the class link, with only class name as the strong link and prefixing 940 * plain package name. 941 * 942 * @param context the id of the context where the link will be added 943 * @param typeElement the class to link to 944 * @param content the content to which the link with be added 945 */ 946 public void addPreQualifiedStrongClassLink(HtmlLinkInfo.Kind context, 947 TypeElement typeElement, Content content) { 948 addPreQualifiedClassLink(context, typeElement, HtmlStyle.typeNameLink, 949 content); 950 } 951 952 /** 953 * {@return a link to the given member} 954 * 955 * @param context the id of the context where the link will be added 956 * @param element the member being linked to 957 * @param label the label for the link 958 */ 959 public Content getDocLink(HtmlLinkInfo.Kind context, Element element, 960 CharSequence label) { 961 return getDocLink(context, utils.getEnclosingTypeElement(element), 962 element, 963 Text.of(label), null, false); 964 } 965 966 /** 967 * Return the link for the given member. 968 * 969 * @param context the id of the context where the link will be printed. 970 * @param typeElement the typeElement that we should link to. This is 971 * not necessarily the type containing element since we may be 972 * inheriting comments. 973 * @param element the member being linked to. 974 * @param label the label for the link. 975 * @return the link for the given member. 976 */ 977 public Content getDocLink(HtmlLinkInfo.Kind context, 978 TypeElement typeElement, Element element, 979 CharSequence label) { 980 return getDocLink(context, typeElement, element, Text.of(label), null, 981 false); 982 } 983 984 /** 985 * Return the link for the given member. 986 * 987 * @param context the id of the context where the link will be printed. 988 * @param typeElement the typeElement that we should link to. This is 989 * not necessarily the type containing element since we may be 990 * inheriting comments. 991 * @param element the member being linked to. 992 * @param label the label for the link. 993 * @param style optional style for the link. 994 * @return the link for the given member. 995 */ 996 public Content getDocLink(HtmlLinkInfo.Kind context, 997 TypeElement typeElement, Element element, 998 CharSequence label, HtmlStyle style) { 999 return getDocLink(context, typeElement, element, Text.of(label), style, 1000 false); 1001 } 1002 1003 /** 1004 * Return the link for the given member. 1005 * 1006 * @param context the id of the context where the link will be printed. 1007 * @param typeElement the typeElement that we should link to. This is 1008 * not necessarily the type containing element since we may be 1009 * inheriting comments. 1010 * @param element the member being linked to. 1011 * @param label the label for the link. 1012 * @return the link for the given member. 1013 */ 1014 public Content getDocLink(HtmlLinkInfo.Kind context, 1015 TypeElement typeElement, Element element, 1016 CharSequence label, boolean isProperty) { 1017 return getDocLink(context, typeElement, element, Text.of(label), null, 1018 isProperty); 1019 } 1020 1021 /** 1022 * Return the link for the given member. 1023 * 1024 * @param context the id of the context where the link will be printed. 1025 * @param typeElement the typeElement that we should link to. This is 1026 * not necessarily the type containing element since we may be 1027 * inheriting comments. 1028 * @param element the member being linked to. 1029 * @param label the label for the link. 1030 * @param style optional style to use for the link. 1031 * @param isProperty true if the element parameter is a JavaFX property. 1032 * @return the link for the given member. 1033 */ 1034 public Content getDocLink(HtmlLinkInfo.Kind context, 1035 TypeElement typeElement, Element element, 1036 Content label, HtmlStyle style, boolean isProperty) { 1037 if (!utils.isLinkable(typeElement, element)) { 1038 return label; 1039 } 1040 1041 if (utils.isExecutableElement(element)) { 1042 ExecutableElement ee = (ExecutableElement) element; 1043 HtmlId id 1044 = isProperty ? htmlIds.forProperty(ee) : htmlIds.forMember(ee); 1045 return getLink(new HtmlLinkInfo(configuration, context, typeElement) 1046 .label(label) 1047 .fragment(id.name()) 1048 .style(style) 1049 .targetMember(element)); 1050 } 1051 1052 if (utils.isVariableElement(element) || utils.isTypeElement(element)) { 1053 return getLink(new HtmlLinkInfo(configuration, context, typeElement) 1054 .label(label) 1055 .fragment(element.getSimpleName().toString()) 1056 .style(style) 1057 .targetMember(element)); 1058 } 1059 1060 return label; 1061 } 1062 1063 /** 1064 * Add the inline comment. 1065 * 1066 * @param element the Element for which the inline comment will be added 1067 * @param tag the inline tag to be added 1068 * @param target the content to which the comment will be added 1069 */ 1070 public void addInlineComment(Element element, DocTree tag, Content target) { 1071 CommentHelper ch = utils.getCommentHelper(element); 1072 List<? extends DocTree> description = ch.getDescription(tag); 1073 addCommentTags(element, description, false, false, false, target); 1074 } 1075 1076 /** 1077 * {@return a phrase describing the type of deprecation} 1078 * 1079 * @param e the Element for which the inline deprecated comment will be added 1080 */ 1081 public Content getDeprecatedPhrase(Element e) { 1082 // TODO e should be checked to being deprecated 1083 return (utils.isDeprecatedForRemoval(e)) 1084 ? contents.deprecatedForRemovalPhrase 1085 : contents.deprecatedPhrase; 1086 } 1087 1088 /** 1089 * Add the inline deprecated comment. 1090 * 1091 * @param e the Element for which the inline deprecated comment will be added 1092 * @param tag the inline tag to be added 1093 * @param target the content to which the comment will be added 1094 */ 1095 public void addInlineDeprecatedComment(Element e, DeprecatedTree tag, 1096 Content target) { 1097 CommentHelper ch = utils.getCommentHelper(e); 1098 addCommentTags(e, ch.getBody(tag), true, false, false, target); 1099 } 1100 1101 /** 1102 * Adds the summary content. 1103 * 1104 * @param element the Element for which the summary will be generated 1105 * @param target the content to which the summary will be added 1106 */ 1107 public void addSummaryComment(Element element, Content target) { 1108 addSummaryComment(element, utils.getFirstSentenceTrees(element), 1109 target); 1110 } 1111 1112 /** 1113 * Adds the preview content. 1114 * 1115 * @param element the Element for which the summary will be generated 1116 * @param firstSentenceTags the first sentence tags for the doc 1117 * @param target the content to which the summary will be added 1118 */ 1119 public void addPreviewComment(Element element, 1120 List<? extends DocTree> firstSentenceTags, Content target) { 1121 addCommentTags(element, firstSentenceTags, false, true, true, target); 1122 } 1123 1124 /** 1125 * Adds the summary content. 1126 * 1127 * @param element the Element for which the summary will be generated 1128 * @param firstSentenceTags the first sentence tags for the doc 1129 * @param target the content to which the summary will be added 1130 */ 1131 public void addSummaryComment(Element element, 1132 List<? extends DocTree> firstSentenceTags, Content target) { 1133 addCommentTags(element, firstSentenceTags, false, true, true, target); 1134 } 1135 1136 public void addSummaryDeprecatedComment(Element element, DeprecatedTree tag, 1137 Content target) { 1138 CommentHelper ch = utils.getCommentHelper(element); 1139 List<? extends DocTree> body = ch.getBody(tag); 1140 addCommentTags(element, ch.getFirstSentenceTrees(body), true, true, 1141 true, target); 1142 } 1143 1144 /** 1145 * Adds the full-body content of the given element. 1146 * 1147 * @param element the element for which the content will be added 1148 * @param target the content to which the content will be added 1149 */ 1150 public void addInlineComment(Element element, Content target) { 1151 addCommentTags(element, utils.getFullBody(element), false, false, false, 1152 target); 1153 } 1154 1155 /** 1156 * Adds the comment tags. 1157 * 1158 * @param element the Element for which the comment tags will be generated 1159 * @param tags the first sentence tags for the doc 1160 * @param depr true if it is deprecated 1161 * @param first true if the first sentence tags should be added 1162 * @param inSummary true if the comment tags are added into the summary section 1163 * @param target the content to which the comment tags will be added 1164 */ 1165 private void addCommentTags(Element element, List<? extends DocTree> tags, 1166 boolean depr, 1167 boolean first, boolean inSummary, Content target) { 1168 if (options.noComment()) { 1169 return; 1170 } 1171 Content div; 1172 Content result = commentTagsToContent(element, tags, first, inSummary); 1173 if (!result.isEmpty()) { 1174 if (depr) { 1175 div = HtmlTree.DIV(HtmlStyle.deprecationComment, result); 1176 target.add(div); 1177 } else { 1178 div = HtmlTree.DIV(HtmlStyle.block, result); 1179 target.add(div); 1180 } 1181 } 1182 if (tags.isEmpty()) { 1183 target.add(Entity.NO_BREAK_SPACE); 1184 } 1185 } 1186 1187 boolean ignoreNonInlineTag(DocTree dtree) { 1188 Name name = null; 1189 if (dtree.getKind() == Kind.START_ELEMENT) { 1190 StartElementTree setree = (StartElementTree) dtree; 1191 name = setree.getName(); 1192 } else if (dtree.getKind() == Kind.END_ELEMENT) { 1193 EndElementTree eetree = (EndElementTree) dtree; 1194 name = eetree.getName(); 1195 } 1196 1197 if (name != null) { 1198 HtmlTag htmlTag = HtmlTag.get(name); 1199 if (htmlTag != null && 1200 htmlTag.blockType != org.jdrupes.mdoclet.internal.doclint.HtmlTag.BlockType.INLINE) { 1201 return true; 1202 } 1203 } 1204 return false; 1205 } 1206 1207 // Notify the next DocTree handler to take necessary action 1208 private boolean commentRemoved = false; 1209 1210 /** 1211 * Converts inline tags and text to content, expanding the 1212 * inline tags along the way. Called wherever text can contain 1213 * an inline tag, such as in comments or in free-form text arguments 1214 * to block tags. 1215 * 1216 * @param element specific element where comment resides 1217 * @param tags list of text trees and inline tag trees (often alternating) 1218 * @param isFirstSentence true if text is first sentence 1219 * @return a Content object 1220 */ 1221 public Content commentTagsToContent(Element element, 1222 List<? extends DocTree> tags, 1223 boolean isFirstSentence) { 1224 return commentTagsToContent(element, tags, isFirstSentence, false); 1225 } 1226 1227 /** 1228 * Converts inline tags and text to content, expanding the 1229 * inline tags along the way. Called wherever text can contain 1230 * an inline tag, such as in comments or in free-form text arguments 1231 * to block tags. 1232 * 1233 * @param element specific element where comment resides 1234 * @param trees list of text trees and inline tag trees (often alternating) 1235 * @param isFirstSentence true if text is first sentence 1236 * @param inSummary if the comment tags are added into the summary section 1237 * @return a Content object 1238 */ 1239 public Content commentTagsToContent(Element element, 1240 List<? extends DocTree> trees, 1241 boolean isFirstSentence, 1242 boolean inSummary) { 1243 return commentTagsToContent(element, trees, 1244 new TagletWriterImpl.Context(isFirstSentence, inSummary)); 1245 } 1246 1247 /** 1248 * Converts inline tags and text to content, expanding the 1249 * inline tags along the way. Called wherever text can contain 1250 * an inline tag, such as in comments or in free-form text arguments 1251 * to block tags. 1252 * 1253 * @param element specific element where comment resides 1254 * @param trees list of text trees and inline tag trees (often alternating) 1255 * @param context the enclosing context for the trees 1256 * 1257 * @return a Content object 1258 */ 1259 public Content commentTagsToContent(Element element, 1260 List<? extends DocTree> trees, 1261 TagletWriterImpl.Context context) { 1262 final Content result = new ContentBuilder() { 1263 @Override 1264 public ContentBuilder add(CharSequence text) { 1265 return super.add(Text.normalizeNewlines(text)); 1266 } 1267 }; 1268 CommentHelper ch = utils.getCommentHelper(element); 1269 configuration.tagletManager.checkTags(element, trees); 1270 commentRemoved = false; 1271 1272 for (ListIterator<? extends DocTree> iterator = trees.listIterator(); 1273 iterator.hasNext();) { 1274 boolean isFirstNode = !iterator.hasPrevious(); 1275 DocTree tag = iterator.next(); 1276 boolean isLastNode = !iterator.hasNext(); 1277 1278 if (context.isFirstSentence) { 1279 // Ignore block tags 1280 if (ignoreNonInlineTag(tag)) 1281 continue; 1282 1283 // Ignore any trailing whitespace OR whitespace after removed 1284 // html comment 1285 if ((isLastNode || commentRemoved) 1286 && tag.getKind() == TEXT 1287 && ((tag instanceof TextTree tt) && tt.getBody().isBlank())) 1288 continue; 1289 1290 // Ignore any leading html comments 1291 if ((isFirstNode || commentRemoved) 1292 && tag.getKind() == COMMENT) { 1293 commentRemoved = true; 1294 continue; 1295 } 1296 } 1297 1298 var docTreeVisitor = new SimpleDocTreeVisitor<Boolean, Content>() { 1299 1300 private boolean inAnAtag() { 1301 return (tag instanceof StartElementTree st) 1302 && equalsIgnoreCase(st.getName(), "a"); 1303 } 1304 1305 @Override 1306 public Boolean visitAttribute(AttributeTree node, 1307 Content content) { 1308 if (!content.isEmpty()) { 1309 content.add(" "); 1310 } 1311 content.add(node.getName()); 1312 if (node.getValueKind() == ValueKind.EMPTY) { 1313 return false; 1314 } 1315 content.add("="); 1316 String quote = switch (node.getValueKind()) { 1317 case DOUBLE -> "\""; 1318 case SINGLE -> "'"; 1319 default -> ""; 1320 }; 1321 content.add(quote); 1322 1323 /* 1324 * In the following code for an attribute value: 1325 * 1. {@docRoot} followed by text beginning "/.." is 1326 * replaced by the value 1327 * of the docrootParent option, followed by the remainder of 1328 * the text 1329 * 2. in the value of an "href" attribute in a <a> tag, an 1330 * initial text 1331 * value will have a relative link redirected. 1332 * Note that, realistically, it only makes sense to ever use 1333 * {@docRoot} 1334 * at the beginning of a URL in an attribute value, but this 1335 * is not 1336 * required or enforced. 1337 */ 1338 boolean isHRef = inAnAtag() 1339 && equalsIgnoreCase(node.getName(), "href"); 1340 boolean first = true; 1341 DocRootTree pendingDocRoot = null; 1342 for (DocTree dt : node.getValue()) { 1343 if (pendingDocRoot != null) { 1344 if (dt instanceof TextTree tt) { 1345 String text = tt.getBody(); 1346 if (text.startsWith("/..") 1347 && !options.docrootParent().isEmpty()) { 1348 content.add(options.docrootParent()); 1349 content.add(textCleanup(text.substring(3), 1350 isLastNode)); 1351 pendingDocRoot = null; 1352 continue; 1353 } 1354 } 1355 pendingDocRoot.accept(this, content); 1356 pendingDocRoot = null; 1357 } 1358 1359 if (dt instanceof TextTree tt) { 1360 String text = tt.getBody(); 1361 if (first && isHRef) { 1362 text = redirectRelativeLinks(element, tt); 1363 } 1364 content.add(textCleanup(text, isLastNode)); 1365 } else if (dt instanceof DocRootTree drt) { 1366 // defer until we see what, if anything, follows 1367 // this node 1368 pendingDocRoot = drt; 1369 } else { 1370 dt.accept(this, content); 1371 } 1372 first = false; 1373 } 1374 if (pendingDocRoot != null) { 1375 pendingDocRoot.accept(this, content); 1376 } 1377 1378 content.add(quote); 1379 return false; 1380 } 1381 1382 @Override 1383 public Boolean visitComment(CommentTree node, Content content) { 1384 content.add(RawHtml.comment(node.getBody())); 1385 return false; 1386 } 1387 1388 @Override 1389 public Boolean visitDocRoot(DocRootTree node, Content content) { 1390 content.add(getInlineTagOutput(element, node, context)); 1391 return false; 1392 } 1393 1394 @Override 1395 public Boolean visitEndElement(EndElementTree node, 1396 Content content) { 1397 content.add(RawHtml.endElement(node.getName())); 1398 return false; 1399 } 1400 1401 @Override 1402 public Boolean visitEntity(EntityTree node, Content content) { 1403 content.add(Entity.of(node.getName())); 1404 return false; 1405 } 1406 1407 @Override 1408 public Boolean visitErroneous(ErroneousTree node, 1409 Content content) { 1410 DocTreePath dtp = ch.getDocTreePath(node); 1411 if (dtp != null) { 1412 String body = node.getBody(); 1413 Matcher m = Pattern.compile("(?i)\\{@([a-z]+).*") 1414 .matcher(body); 1415 String tagName = m.matches() ? m.group(1) : null; 1416 if (tagName == null) { 1417 if (!configuration.isDocLintSyntaxGroupEnabled()) { 1418 messages.warning(dtp, 1419 "doclet.tag.invalid_input", body); 1420 } 1421 content.add(invalidTagOutput( 1422 resources.getText("doclet.tag.invalid_input", 1423 body), 1424 Optional.empty())); 1425 } else { 1426 messages.warning(dtp, "doclet.tag.invalid_usage", 1427 body); 1428 content.add(invalidTagOutput( 1429 resources.getText("doclet.tag.invalid", 1430 tagName), 1431 Optional.of(Text.of(body)))); 1432 } 1433 } 1434 return false; 1435 } 1436 1437 @Override 1438 public Boolean visitEscape(EscapeTree node, Content content) { 1439 result.add(node.getBody()); 1440 return false; 1441 } 1442 1443 @Override 1444 public Boolean visitInheritDoc(InheritDocTree node, 1445 Content content) { 1446 Content output = getInlineTagOutput(element, node, context); 1447 content.add(output); 1448 // if we obtained the first sentence successfully, nothing 1449 // more to do 1450 return (context.isFirstSentence && !output.isEmpty()); 1451 } 1452 1453 @Override 1454 public Boolean visitIndex(IndexTree node, Content content) { 1455 Content output = getInlineTagOutput(element, node, context); 1456 if (output != null) { 1457 content.add(output); 1458 } 1459 return false; 1460 } 1461 1462 @Override 1463 public Boolean visitLink(LinkTree node, Content content) { 1464 var inTags = context.inTags; 1465 if (inTags.contains(LINK) || inTags.contains(LINK_PLAIN) 1466 || inTags.contains(SEE)) { 1467 DocTreePath dtp = ch.getDocTreePath(node); 1468 if (dtp != null) { 1469 messages.warning(dtp, "doclet.see.nested_link", 1470 "{@" + node.getTagName() + "}"); 1471 } 1472 Content label = commentTagsToContent(element, 1473 node.getLabel(), context); 1474 if (label.isEmpty()) { 1475 label = Text.of(node.getReference().getSignature()); 1476 } 1477 content.add(label); 1478 } else { 1479 TagletWriterImpl t 1480 = getTagletWriterInstance(context.within(node)); 1481 content.add(t.linkTagOutput(element, node)); 1482 } 1483 return false; 1484 } 1485 1486 @Override 1487 public Boolean visitLiteral(LiteralTree node, Content content) { 1488 String s = node.getBody().getBody(); 1489 Content t = Text.of(Text.normalizeNewlines(s)); 1490 content.add(node.getKind() == CODE ? HtmlTree.CODE(t) : t); 1491 return false; 1492 } 1493 1494 @Override 1495 public Boolean visitStartElement(StartElementTree node, 1496 Content content) { 1497 Content attrs = new ContentBuilder(); 1498 if (node.getName().toString().matches("(?i)h[1-6]")) { 1499 createSectionIdAndIndex(node, trees, attrs, element, 1500 context); 1501 } 1502 for (DocTree dt : node.getAttributes()) { 1503 dt.accept(this, attrs); 1504 } 1505 content.add(RawHtml.startElement(node.getName(), attrs, 1506 node.isSelfClosing())); 1507 return false; 1508 } 1509 1510 @Override 1511 public Boolean visitSummary(SummaryTree node, Content content) { 1512 Content output = getInlineTagOutput(element, node, context); 1513 content.add(output); 1514 return false; 1515 } 1516 1517 @Override 1518 public Boolean visitSystemProperty(SystemPropertyTree node, 1519 Content content) { 1520 Content output = getInlineTagOutput(element, node, context); 1521 if (output != null) { 1522 content.add(output); 1523 } 1524 return false; 1525 } 1526 1527 private CharSequence textCleanup(String text, boolean isLast) { 1528 return textCleanup(text, isLast, false); 1529 } 1530 1531 private CharSequence textCleanup(String text, boolean isLast, 1532 boolean stripLeading) { 1533 boolean stripTrailing = context.isFirstSentence && isLast; 1534 if (stripLeading && stripTrailing) { 1535 text = text.strip(); 1536 } else if (stripLeading) { 1537 text = text.stripLeading(); 1538 } else if (stripTrailing) { 1539 text = text.stripTrailing(); 1540 } 1541 text = utils.replaceTabs(text); 1542 return Text.normalizeNewlines(text); 1543 } 1544 1545 @Override 1546 public Boolean visitText(TextTree node, Content content) { 1547 String text = node.getBody(); 1548 result.add(RawHtml 1549 .of(textCleanup(text, isLastNode, commentRemoved))); 1550 return false; 1551 } 1552 1553 @Override 1554 protected Boolean defaultAction(DocTree node, Content content) { 1555 Content output = getInlineTagOutput(element, node, context); 1556 if (output != null) { 1557 content.add(output); 1558 } 1559 return false; 1560 } 1561 1562 }; 1563 1564 boolean allDone = docTreeVisitor.visit(tag, result); 1565 commentRemoved = false; 1566 1567 if (allDone) 1568 break; 1569 } 1570 return result; 1571 } 1572 1573 private boolean equalsIgnoreCase(Name name, String s) { 1574 return name != null && name.toString().equalsIgnoreCase(s); 1575 } 1576 1577 private Optional<String> getIdAttributeValue(StartElementTree node) { 1578 return node.getAttributes().stream() 1579 .filter(dt -> dt instanceof AttributeTree at 1580 && equalsIgnoreCase(at.getName(), "id")) 1581 .map(dt -> ((AttributeTree) dt).getValue().toString()) 1582 .findFirst(); 1583 } 1584 1585 private void createSectionIdAndIndex(StartElementTree node, 1586 List<? extends DocTree> trees, Content attrs, 1587 Element element, TagletWriterImpl.Context context) { 1588 // Use existing id attribute if available 1589 String id = getIdAttributeValue(node).orElse(null); 1590 StringBuilder sb = new StringBuilder(); 1591 String tagName = node.getName().toString().toLowerCase(Locale.ROOT); 1592 // Go through heading content to collect content and look for existing 1593 // id 1594 for (DocTree docTree : trees.subList(trees.indexOf(node) + 1, 1595 trees.size())) { 1596 if (docTree instanceof TextTree text) { 1597 sb.append(text.getBody()); 1598 } else if (docTree instanceof LiteralTree literal) { 1599 sb.append(literal.getBody().getBody()); 1600 } else if (docTree instanceof LinkTree link) { 1601 var label = link.getLabel(); 1602 sb.append(label.isEmpty() ? link.getReference().getSignature() 1603 : label.toString()); 1604 } else if (id == null && docTree instanceof StartElementTree nested 1605 && equalsIgnoreCase(nested.getName(), "a")) { 1606 // Use id of embedded anchor element if present 1607 id = getIdAttributeValue(nested).orElse(null); 1608 } else if (docTree instanceof EndElementTree endElement 1609 && equalsIgnoreCase(endElement.getName(), tagName)) { 1610 break; 1611 } 1612 } 1613 String headingContent = sb.toString().trim(); 1614 if (id == null) { 1615 // Generate id attribute 1616 HtmlId htmlId = htmlIds.forHeading(headingContent, headingIds); 1617 id = htmlId.name(); 1618 attrs.add("id=\"").add(htmlId.name()).add("\""); 1619 } 1620 // Generate index item 1621 if (!headingContent.isEmpty() && configuration.mainIndex != null) { 1622 String tagText = headingContent.replaceAll("\\s+", " "); 1623 IndexItem item = IndexItem.of(element, node, tagText, 1624 getTagletWriterInstance(context).getHolderName(element), 1625 resources.getText("doclet.Section"), 1626 new DocLink(path, id)); 1627 configuration.mainIndex.add(item); 1628 } 1629 } 1630 1631 /** 1632 * Returns true if relative links should be redirected. 1633 * 1634 * @return true if a relative link should be redirected. 1635 */ 1636 private boolean shouldRedirectRelativeLinks(Element element) { 1637 if (element == null || utils.isOverviewElement(element)) { 1638 // Can't redirect unless there is a valid source element. 1639 return false; 1640 } 1641 // Retrieve the element of this writer if it is a "primary" writer for 1642 // an element. 1643 // Note: It would be nice to have getCurrentPageElement() return package 1644 // and module elements 1645 // in their respective writers, but other uses of the method are only 1646 // interested in TypeElements. 1647 Element currentPageElement = getCurrentPageElement(); 1648 if (currentPageElement == null) { 1649 if (this instanceof PackageWriterImpl packageWriter) { 1650 currentPageElement = packageWriter.packageElement; 1651 } else if (this instanceof ModuleWriterImpl moduleWriter) { 1652 currentPageElement = moduleWriter.mdle; 1653 } 1654 } 1655 // Redirect link if the current writer is not the primary writer for the 1656 // source element. 1657 return currentPageElement == null 1658 || (currentPageElement != element 1659 && currentPageElement != utils 1660 .getEnclosingTypeElement(element)); 1661 } 1662 1663 /** 1664 * Returns the output for an invalid tag. The returned content uses special styling to 1665 * highlight the problem. Depending on the presence of the {@code detail} string the method 1666 * returns a plain text span or an expandable component. 1667 * 1668 * @param summary the single-line summary message 1669 * @param detail the optional detail message which may contain preformatted text 1670 * @return the output 1671 */ 1672 protected Content invalidTagOutput(String summary, 1673 Optional<Content> detail) { 1674 if (detail.isEmpty() || detail.get().isEmpty()) { 1675 return HtmlTree.SPAN(HtmlStyle.invalidTag, Text.of(summary)); 1676 } 1677 return HtmlTree.DETAILS(HtmlStyle.invalidTag) 1678 .add(HtmlTree.SUMMARY(Text.of(summary))) 1679 .add(HtmlTree.PRE(detail.get())); 1680 } 1681 1682 /** 1683 * Returns true if element lives in the same package as the type or package 1684 * element of this writer. 1685 */ 1686 private boolean inSamePackage(Element element) { 1687 Element currentPageElement 1688 = (this instanceof PackageWriterImpl packageWriter) 1689 ? packageWriter.packageElement 1690 : getCurrentPageElement(); 1691 return currentPageElement != null && !utils.isModule(element) 1692 && Objects.equals(utils.containingPackage(currentPageElement), 1693 utils.containingPackage(element)); 1694 } 1695 1696 /** 1697 * Suppose a piece of documentation has a relative link. When you copy 1698 * that documentation to another place such as the index or class-use page, 1699 * that relative link will no longer work. We should redirect those links 1700 * so that they will work again. 1701 * <p> 1702 * Here is the algorithm used to fix the link: 1703 * <p> 1704 * {@literal <relative link> => docRoot + <relative path to file> + <relative link> } 1705 * <p> 1706 * For example, suppose DocletEnvironment has this link: 1707 * {@literal <a href="package-summary.html">The package Page</a> } 1708 * <p> 1709 * If this link appeared in the index, we would redirect 1710 * the link like this: 1711 * 1712 * {@literal <a href="./jdk/javadoc/doclet/package-summary.html">The package Page</a>} 1713 * 1714 * @param element the Element object whose documentation is being written. 1715 * @param tt the text being written. 1716 * 1717 * @return the text, with all the relative links redirected to work. 1718 */ 1719 private String redirectRelativeLinks(Element element, TextTree tt) { 1720 String text = tt.getBody(); 1721 if (!shouldRedirectRelativeLinks(element)) { 1722 return text; 1723 } 1724 String lower = Utils.toLowerCase(text); 1725 if (lower.startsWith("mailto:") 1726 || lower.startsWith("http:") 1727 || lower.startsWith("https:") 1728 || lower.startsWith("file:") 1729 || lower.startsWith("ftp:")) { 1730 return text; 1731 } 1732 if (text.startsWith("#")) { 1733 // Redirected fragment link: prepend HTML file name to make it work 1734 if (utils.isModule(element)) { 1735 text = "module-summary.html" + text; 1736 } else if (utils.isPackage(element)) { 1737 text = DocPaths.PACKAGE_SUMMARY.getPath() + text; 1738 } else { 1739 TypeElement typeElement = element instanceof TypeElement 1740 ? (TypeElement) element 1741 : utils.getEnclosingTypeElement(element); 1742 text = docPaths.forName(typeElement).getPath() + text; 1743 } 1744 } 1745 1746 if (!inSamePackage(element)) { 1747 DocPath redirectPathFromRoot 1748 = new SimpleElementVisitor14<DocPath, Void>() { 1749 @Override 1750 public DocPath visitType(TypeElement e, Void p) { 1751 return docPaths.forPackage(utils.containingPackage(e)); 1752 } 1753 1754 @Override 1755 public DocPath visitPackage(PackageElement e, Void p) { 1756 return docPaths.forPackage(e); 1757 } 1758 1759 @Override 1760 public DocPath visitVariable(VariableElement e, Void p) { 1761 return docPaths.forPackage(utils.containingPackage(e)); 1762 } 1763 1764 @Override 1765 public DocPath visitExecutable(ExecutableElement e, 1766 Void p) { 1767 return docPaths.forPackage(utils.containingPackage(e)); 1768 } 1769 1770 @Override 1771 public DocPath visitModule(ModuleElement e, Void p) { 1772 return DocPaths.forModule(e); 1773 } 1774 1775 @Override 1776 protected DocPath defaultAction(Element e, Void p) { 1777 return null; 1778 } 1779 }.visit(element); 1780 if (redirectPathFromRoot != null) { 1781 text = "{@" + (new DocRootTaglet()).getName() + "}/" 1782 + redirectPathFromRoot.resolve(text).getPath(); 1783 return replaceDocRootDir(text); 1784 } 1785 } 1786 return text; 1787 } 1788 1789 /** 1790 * {@return the annotation types info for the given element} 1791 * 1792 * @param element an Element 1793 * @param lineBreak if true add new line between each member value 1794 */ 1795 Content getAnnotationInfo(Element element, boolean lineBreak) { 1796 return getAnnotationInfo(element.getAnnotationMirrors(), lineBreak); 1797 } 1798 1799 /** 1800 * {@return the description for the given annotations} 1801 * 1802 * @param descList a list of annotation mirrors 1803 * @param lineBreak if true add new line between each member value 1804 */ 1805 Content getAnnotationInfo(List<? extends AnnotationMirror> descList, 1806 boolean lineBreak) { 1807 List<Content> annotations = getAnnotations(descList, lineBreak); 1808 String sep = ""; 1809 ContentBuilder result = new ContentBuilder(); 1810 for (Content annotation : annotations) { 1811 result.add(sep); 1812 result.add(annotation); 1813 if (!lineBreak) { 1814 sep = " "; 1815 } 1816 } 1817 return result; 1818 } 1819 1820 /** 1821 * Return the string representations of the annotation types for 1822 * the given doc. 1823 * 1824 * @param descList a list of annotation mirrors. 1825 * @param lineBreak if true, add new line between each member value. 1826 * @return a list of strings representing the annotations being 1827 * documented. 1828 */ 1829 public List<Content> getAnnotations( 1830 List<? extends AnnotationMirror> descList, boolean lineBreak) { 1831 List<Content> results = new ArrayList<>(); 1832 ContentBuilder annotation; 1833 for (AnnotationMirror aDesc : descList) { 1834 TypeElement annotationElement 1835 = (TypeElement) aDesc.getAnnotationType().asElement(); 1836 // If an annotation is not documented, do not add it to the list. If 1837 // the annotation is of a repeatable type, and if it is not 1838 // documented 1839 // and also if its container annotation is not documented, do not 1840 // add it 1841 // to the list. If an annotation of a repeatable type is not 1842 // documented 1843 // but its container is documented, it will be added to the list. 1844 if (!utils.isDocumentedAnnotation(annotationElement) && 1845 (!isAnnotationDocumented && !isContainerDocumented)) { 1846 continue; 1847 } 1848 annotation = new ContentBuilder(); 1849 isAnnotationDocumented = false; 1850 HtmlLinkInfo linkInfo = new HtmlLinkInfo(configuration, 1851 HtmlLinkInfo.Kind.PLAIN, annotationElement); 1852 Map<? extends ExecutableElement, ? extends AnnotationValue> pairs 1853 = aDesc.getElementValues(); 1854 // If the annotation is mandated, do not print the container. 1855 if (utils.configuration.workArounds.isMandated(aDesc)) { 1856 for (ExecutableElement ee : pairs.keySet()) { 1857 AnnotationValue annotationValue = pairs.get(ee); 1858 List<AnnotationValue> annotationTypeValues 1859 = new ArrayList<>(); 1860 1861 new SimpleAnnotationValueVisitor9<Void, 1862 List<AnnotationValue>>() { 1863 @Override 1864 public Void visitArray( 1865 List<? extends AnnotationValue> vals, 1866 List<AnnotationValue> p) { 1867 p.addAll(vals); 1868 return null; 1869 } 1870 1871 @Override 1872 protected Void defaultAction(Object o, 1873 List<AnnotationValue> p) { 1874 p.add(annotationValue); 1875 return null; 1876 } 1877 }.visit(annotationValue, annotationTypeValues); 1878 1879 String sep = ""; 1880 for (AnnotationValue av : annotationTypeValues) { 1881 annotation.add(sep); 1882 annotation.add(annotationValueToContent(av)); 1883 sep = " "; 1884 } 1885 } 1886 } else if (isAnnotationArray(pairs)) { 1887 // If the container has 1 or more value defined and if the 1888 // repeatable type annotation is not documented, do not print 1889 // the container. 1890 if (pairs.size() == 1 && isAnnotationDocumented) { 1891 List<AnnotationValue> annotationTypeValues 1892 = new ArrayList<>(); 1893 for (AnnotationValue a : pairs.values()) { 1894 new SimpleAnnotationValueVisitor9<Void, 1895 List<AnnotationValue>>() { 1896 @Override 1897 public Void visitArray( 1898 List<? extends AnnotationValue> vals, 1899 List<AnnotationValue> annotationTypeValues) { 1900 annotationTypeValues.addAll(vals); 1901 return null; 1902 } 1903 }.visit(a, annotationTypeValues); 1904 } 1905 String sep = ""; 1906 for (AnnotationValue av : annotationTypeValues) { 1907 annotation.add(sep); 1908 annotation.add(annotationValueToContent(av)); 1909 sep = " "; 1910 } 1911 } 1912 // If the container has 1 or more value defined and if the 1913 // repeatable type annotation is not documented, print the 1914 // container. 1915 else { 1916 addAnnotations(annotationElement, linkInfo, annotation, 1917 pairs, false); 1918 } 1919 } else { 1920 addAnnotations(annotationElement, linkInfo, annotation, pairs, 1921 lineBreak); 1922 } 1923 annotation.add(lineBreak ? Text.NL : ""); 1924 results.add(annotation); 1925 } 1926 return results; 1927 } 1928 1929 /** 1930 * Add annotation to the annotation string. 1931 * 1932 * @param annotationDoc the annotation being documented 1933 * @param linkInfo the information about the link 1934 * @param annotation the annotation string to which the annotation will be added 1935 * @param map annotation type element to annotation value pairs 1936 * @param linkBreak if true, add new line between each member value 1937 */ 1938 private void addAnnotations(TypeElement annotationDoc, 1939 HtmlLinkInfo linkInfo, 1940 ContentBuilder annotation, 1941 Map<? extends ExecutableElement, ? extends AnnotationValue> map, 1942 boolean linkBreak) { 1943 linkInfo.label("@" + annotationDoc.getSimpleName()); 1944 annotation.add(getLink(linkInfo)); 1945 if (!map.isEmpty()) { 1946 annotation.add("("); 1947 boolean isFirst = true; 1948 Set<? extends ExecutableElement> keys = map.keySet(); 1949 boolean multipleValues = keys.size() > 1; 1950 for (ExecutableElement element : keys) { 1951 if (isFirst) { 1952 isFirst = false; 1953 } else { 1954 annotation.add(","); 1955 if (linkBreak) { 1956 annotation.add(Text.NL); 1957 int spaces = annotationDoc.getSimpleName().length() + 2; 1958 for (int k = 0; k < (spaces); k++) { 1959 annotation.add(" "); 1960 } 1961 } 1962 } 1963 String simpleName = element.getSimpleName().toString(); 1964 if (multipleValues || !"value".equals(simpleName)) { // Omit 1965 // "value=" 1966 // where 1967 // unnecessary 1968 annotation.add(getDocLink(HtmlLinkInfo.Kind.PLAIN, element, 1969 simpleName)); 1970 annotation.add("="); 1971 } 1972 AnnotationValue annotationValue = map.get(element); 1973 List<AnnotationValue> annotationTypeValues = new ArrayList<>(); 1974 new SimpleAnnotationValueVisitor9<Void, AnnotationValue>() { 1975 @Override 1976 public Void visitArray(List<? extends AnnotationValue> vals, 1977 AnnotationValue p) { 1978 annotationTypeValues.addAll(vals); 1979 return null; 1980 } 1981 1982 @Override 1983 protected Void defaultAction(Object o, AnnotationValue p) { 1984 annotationTypeValues.add(p); 1985 return null; 1986 } 1987 }.visit(annotationValue, annotationValue); 1988 annotation.add(annotationTypeValues.size() == 1 ? "" : "{"); 1989 String sep = ""; 1990 for (AnnotationValue av : annotationTypeValues) { 1991 annotation.add(sep); 1992 annotation.add(annotationValueToContent(av)); 1993 sep = ","; 1994 } 1995 annotation.add(annotationTypeValues.size() == 1 ? "" : "}"); 1996 isContainerDocumented = false; 1997 } 1998 annotation.add(")"); 1999 } 2000 } 2001 2002 /** 2003 * Check if the annotation contains an array of annotation as a value. This 2004 * check is to verify if a repeatable type annotation is present or not. 2005 * 2006 * @param pairs annotation type element and value pairs 2007 * 2008 * @return true if the annotation contains an array of annotation as a value. 2009 */ 2010 private boolean isAnnotationArray( 2011 Map<? extends ExecutableElement, ? extends AnnotationValue> pairs) { 2012 AnnotationValue annotationValue; 2013 for (ExecutableElement ee : pairs.keySet()) { 2014 annotationValue = pairs.get(ee); 2015 boolean rvalue 2016 = new SimpleAnnotationValueVisitor9<Boolean, Void>() { 2017 @Override 2018 public Boolean visitArray( 2019 List<? extends AnnotationValue> vals, Void p) { 2020 if (vals.size() > 1) { 2021 if (vals.get(0) instanceof AnnotationMirror) { 2022 isContainerDocumented = true; 2023 return new SimpleAnnotationValueVisitor9< 2024 Boolean, Void>() { 2025 @Override 2026 public Boolean visitAnnotation( 2027 AnnotationMirror a, Void p) { 2028 isContainerDocumented = true; 2029 Element asElement 2030 = a.getAnnotationType().asElement(); 2031 if (utils.isDocumentedAnnotation( 2032 (TypeElement) asElement)) { 2033 isAnnotationDocumented = true; 2034 } 2035 return true; 2036 } 2037 2038 @Override 2039 protected Boolean defaultAction(Object o, 2040 Void p) { 2041 return false; 2042 } 2043 }.visit(vals.get(0)); 2044 } 2045 } 2046 return false; 2047 } 2048 2049 @Override 2050 protected Boolean defaultAction(Object o, Void p) { 2051 return false; 2052 } 2053 }.visit(annotationValue); 2054 if (rvalue) { 2055 return true; 2056 } 2057 } 2058 return false; 2059 } 2060 2061 private Content annotationValueToContent(AnnotationValue annotationValue) { 2062 return new SimpleAnnotationValueVisitor9<Content, Void>() { 2063 2064 @Override 2065 public Content visitType(TypeMirror t, Void p) { 2066 return new SimpleTypeVisitor9<Content, Void>() { 2067 @Override 2068 public Content visitDeclared(DeclaredType t, Void p) { 2069 HtmlLinkInfo linkInfo = new HtmlLinkInfo(configuration, 2070 HtmlLinkInfo.Kind.PLAIN, t); 2071 String name = utils.isIncluded(t.asElement()) 2072 ? t.asElement().getSimpleName().toString() 2073 : utils.getFullyQualifiedName(t.asElement()); 2074 linkInfo.label(name + utils.getDimension(t) + ".class"); 2075 return getLink(linkInfo); 2076 } 2077 2078 @Override 2079 protected Content defaultAction(TypeMirror e, Void p) { 2080 return Text.of(t + utils.getDimension(t) + ".class"); 2081 } 2082 }.visit(t); 2083 } 2084 2085 @Override 2086 public Content visitAnnotation(AnnotationMirror a, Void p) { 2087 List<Content> list = getAnnotations(List.of(a), false); 2088 ContentBuilder buf = new ContentBuilder(); 2089 for (Content c : list) { 2090 buf.add(c); 2091 } 2092 return buf; 2093 } 2094 2095 @Override 2096 public Content visitEnumConstant(VariableElement c, Void p) { 2097 return getDocLink(HtmlLinkInfo.Kind.PLAIN, c, 2098 c.getSimpleName()); 2099 } 2100 2101 @Override 2102 public Content visitArray(List<? extends AnnotationValue> vals, 2103 Void p) { 2104 ContentBuilder buf = new ContentBuilder(); 2105 String sep = ""; 2106 for (AnnotationValue av : vals) { 2107 buf.add(sep); 2108 buf.add(visit(av)); 2109 sep = " "; 2110 } 2111 return buf; 2112 } 2113 2114 @Override 2115 protected Content defaultAction(Object o, Void p) { 2116 return Text.of(annotationValue.toString()); 2117 } 2118 }.visit(annotationValue); 2119 } 2120 2121 protected TableHeader getPackageTableHeader() { 2122 return new TableHeader(contents.packageLabel, 2123 contents.descriptionLabel); 2124 } 2125 2126 /** 2127 * Generates a string for use in a description meta element, 2128 * based on an element and its enclosing elements 2129 * @param prefix a prefix for the string 2130 * @param elem the element 2131 * @return the description 2132 */ 2133 static String getDescription(String prefix, Element elem) { 2134 LinkedList<Element> chain = new LinkedList<>(); 2135 for (Element e = elem; e != null; e = e.getEnclosingElement()) { 2136 // ignore unnamed enclosing elements 2137 if (e.getSimpleName().length() == 0 && e != elem) { 2138 break; 2139 } 2140 chain.addFirst(e); 2141 } 2142 StringBuilder sb = new StringBuilder(); 2143 for (Element e : chain) { 2144 String name; 2145 switch (e.getKind()) { 2146 case MODULE, PACKAGE -> { 2147 name = ((QualifiedNameable) e).getQualifiedName().toString(); 2148 if (name.length() == 0) { 2149 name = "<unnamed>"; 2150 } 2151 } 2152 default -> name = e.getSimpleName().toString(); 2153 } 2154 2155 if (sb.length() == 0) { 2156 sb.append(prefix).append(": "); 2157 } else { 2158 sb.append(", "); 2159 } 2160 sb.append( 2161 e.getKind().toString().toLowerCase(Locale.US).replace("_", " ")) 2162 .append(": ") 2163 .append(name); 2164 } 2165 return sb.toString(); 2166 } 2167 2168 static String getGenerator(Class<?> clazz) { 2169 return "javadoc/" + clazz.getSimpleName(); 2170 } 2171 2172 /** 2173 * Returns an HtmlTree for the BODY element. 2174 * 2175 * @param title title for the window 2176 * @return an HtmlTree for the BODY tag 2177 */ 2178 public HtmlTree getBody(String title) { 2179 var body = new HtmlTree(TagName.BODY).setStyle(getBodyStyle()); 2180 2181 this.winTitle = title; 2182 // Don't print windowtitle script for overview-frame, allclasses-frame 2183 // and package-frame 2184 body.add(mainBodyScript.asContent()); 2185 var noScript 2186 = HtmlTree.NOSCRIPT(HtmlTree.DIV(contents.noScriptMessage)); 2187 body.add(noScript); 2188 return body; 2189 } 2190 2191 public HtmlStyle getBodyStyle() { 2192 String kind = getClass().getSimpleName() 2193 .replaceAll("(Writer)?(Impl)?$", "") 2194 .replaceAll("AnnotationType", "Class") 2195 .replaceAll("^(Module|Package|Class)$", "$1Declaration") 2196 .replace("API", "Api"); 2197 String page = kind.substring(0, 1).toLowerCase(Locale.US) 2198 + kind.substring(1) + "Page"; 2199 return HtmlStyle.valueOf(page); 2200 } 2201 2202 /** 2203 * Returns the path of module/package specific stylesheets for the element. 2204 * @param element module/Package element 2205 * @return list of path of module/package specific stylesheets 2206 * @throws DocFileIOException 2207 */ 2208 List<DocPath> getLocalStylesheets(Element element) 2209 throws DocFileIOException { 2210 List<DocPath> stylesheets = new ArrayList<>(); 2211 DocPath basePath = null; 2212 if (element instanceof PackageElement pkg) { 2213 stylesheets.addAll(getModuleStylesheets(pkg)); 2214 basePath = docPaths.forPackage(pkg); 2215 } else if (element instanceof ModuleElement mdle) { 2216 basePath = DocPaths.forModule(mdle); 2217 } 2218 for (DocPath stylesheet : getStylesheets(element)) { 2219 stylesheets.add(basePath.resolve(stylesheet.getPath())); 2220 } 2221 return stylesheets; 2222 } 2223 2224 private List<DocPath> getModuleStylesheets(PackageElement pkgElement) 2225 throws DocFileIOException { 2226 List<DocPath> moduleStylesheets = new ArrayList<>(); 2227 ModuleElement moduleElement = utils.containingModule(pkgElement); 2228 if (moduleElement != null && !moduleElement.isUnnamed()) { 2229 List<DocPath> localStylesheets = getStylesheets(moduleElement); 2230 DocPath basePath = DocPaths.forModule(moduleElement); 2231 for (DocPath stylesheet : localStylesheets) { 2232 moduleStylesheets.add(basePath.resolve(stylesheet)); 2233 } 2234 } 2235 return moduleStylesheets; 2236 } 2237 2238 private List<DocPath> getStylesheets(Element element) 2239 throws DocFileIOException { 2240 List<DocPath> localStylesheets 2241 = configuration.localStylesheetMap.get(element); 2242 if (localStylesheets == null) { 2243 DocFilesHandlerImpl docFilesHandler 2244 = (DocFilesHandlerImpl) configuration 2245 .getWriterFactory().getDocFilesHandler(element); 2246 localStylesheets = docFilesHandler.getStylesheets(); 2247 configuration.localStylesheetMap.put(element, localStylesheets); 2248 } 2249 return localStylesheets; 2250 } 2251 2252 public void addPreviewSummary(Element forWhat, Content target) { 2253 if (utils.isPreviewAPI(forWhat)) { 2254 var div = HtmlTree.DIV(HtmlStyle.block); 2255 div.add( 2256 HtmlTree.SPAN(HtmlStyle.previewLabel, contents.previewPhrase)); 2257 target.add(div); 2258 } 2259 } 2260 2261 public void addPreviewInfo(Element forWhat, Content target) { 2262 if (utils.isPreviewAPI(forWhat)) { 2263 // in Java platform: 2264 var previewDiv = HtmlTree.DIV(HtmlStyle.previewBlock); 2265 previewDiv.setId(htmlIds.forPreviewSection(forWhat)); 2266 String name = (switch (forWhat.getKind()) { 2267 case PACKAGE, MODULE -> ((QualifiedNameable) forWhat) 2268 .getQualifiedName(); 2269 case CONSTRUCTOR -> forWhat.getEnclosingElement().getSimpleName(); 2270 default -> forWhat.getSimpleName(); 2271 }).toString(); 2272 var nameCode = HtmlTree.CODE(Text.of(name)); 2273 boolean isReflectivePreview = utils.isReflectivePreviewAPI(forWhat); 2274 String leadingNoteKey 2275 = !isReflectivePreview ? "doclet.PreviewPlatformLeadingNote" 2276 : "doclet.ReflectivePreviewPlatformLeadingNote"; 2277 Content leadingNote = contents.getContent(leadingNoteKey, nameCode); 2278 previewDiv.add(HtmlTree.SPAN(HtmlStyle.previewLabel, 2279 leadingNote)); 2280 if (!isReflectivePreview) { 2281 Content note1 = contents 2282 .getContent("doclet.PreviewTrailingNote1", nameCode); 2283 previewDiv.add(HtmlTree.DIV(HtmlStyle.previewComment, note1)); 2284 } 2285 Content note2 2286 = contents.getContent("doclet.PreviewTrailingNote2", nameCode); 2287 previewDiv.add(HtmlTree.DIV(HtmlStyle.previewComment, note2)); 2288 target.add(previewDiv); 2289 } else if (forWhat.getKind().isClass() 2290 || forWhat.getKind().isInterface()) { 2291 // in custom code: 2292 List<Content> previewNotes = getPreviewNotes((TypeElement) forWhat); 2293 if (!previewNotes.isEmpty()) { 2294 Name name = forWhat.getSimpleName(); 2295 var nameCode = HtmlTree.CODE(Text.of(name)); 2296 var previewDiv = HtmlTree.DIV(HtmlStyle.previewBlock); 2297 previewDiv.setId(htmlIds.forPreviewSection(forWhat)); 2298 Content leadingNote = contents 2299 .getContent("doclet.PreviewLeadingNote", nameCode); 2300 previewDiv.add(HtmlTree.SPAN(HtmlStyle.previewLabel, 2301 leadingNote)); 2302 var ul = HtmlTree.UL(HtmlStyle.previewComment); 2303 for (Content note : previewNotes) { 2304 ul.add(HtmlTree.LI(note)); 2305 } 2306 previewDiv.add(ul); 2307 Content note1 2308 = contents.getContent("doclet.PreviewTrailingNote1", 2309 nameCode); 2310 previewDiv.add(HtmlTree.DIV(HtmlStyle.previewComment, note1)); 2311 Content note2 2312 = contents.getContent("doclet.PreviewTrailingNote2", 2313 name); 2314 previewDiv.add(HtmlTree.DIV(HtmlStyle.previewComment, note2)); 2315 target.add(previewDiv); 2316 } 2317 } 2318 } 2319 2320 private List<Content> getPreviewNotes(TypeElement el) { 2321 String className = el.getSimpleName().toString(); 2322 List<Content> result = new ArrayList<>(); 2323 PreviewSummary previewAPITypes = utils.declaredUsingPreviewAPIs(el); 2324 Set<TypeElement> previewAPI = new HashSet<>(previewAPITypes.previewAPI); 2325 Set<TypeElement> reflectivePreviewAPI 2326 = new HashSet<>(previewAPITypes.reflectivePreviewAPI); 2327 Set<TypeElement> declaredUsingPreviewFeature 2328 = new HashSet<>(previewAPITypes.declaredUsingPreviewFeature); 2329 Set<DeclarationPreviewLanguageFeatures> previewLanguageFeatures 2330 = new HashSet<>(); 2331 for (Element enclosed : el.getEnclosedElements()) { 2332 if (!utils.isIncluded(enclosed)) { 2333 continue; 2334 } 2335 if (utils.isPreviewAPI(enclosed)) { 2336 // for class summary, ignore methods that are themselves 2337 // preview: 2338 continue; 2339 } 2340 if (!enclosed.getKind().isClass() 2341 && !enclosed.getKind().isInterface()) { 2342 PreviewSummary memberAPITypes 2343 = utils.declaredUsingPreviewAPIs(enclosed); 2344 declaredUsingPreviewFeature 2345 .addAll(memberAPITypes.declaredUsingPreviewFeature); 2346 previewAPI.addAll(memberAPITypes.previewAPI); 2347 reflectivePreviewAPI 2348 .addAll(memberAPITypes.reflectivePreviewAPI); 2349 previewLanguageFeatures 2350 .addAll(utils.previewLanguageFeaturesUsed(enclosed)); 2351 } else if (!utils.previewLanguageFeaturesUsed(enclosed).isEmpty()) { 2352 declaredUsingPreviewFeature.add((TypeElement) enclosed); 2353 } 2354 } 2355 previewLanguageFeatures.addAll(utils.previewLanguageFeaturesUsed(el)); 2356 if (!previewLanguageFeatures.isEmpty()) { 2357 for (DeclarationPreviewLanguageFeatures feature : previewLanguageFeatures) { 2358 String featureDisplayName = resources 2359 .getText("doclet.Declared_Using_Preview." + feature.name()); 2360 result.add(withPreviewFeatures("doclet.Declared_Using_Preview", 2361 className, 2362 featureDisplayName, feature.features)); 2363 } 2364 } 2365 if (!declaredUsingPreviewFeature.isEmpty()) { 2366 result.add(withLinks("doclet.UsesDeclaredUsingPreview", className, 2367 declaredUsingPreviewFeature)); 2368 } 2369 if (!previewAPI.isEmpty()) { 2370 result.add(withLinks("doclet.PreviewAPI", className, previewAPI)); 2371 } 2372 if (!reflectivePreviewAPI.isEmpty()) { 2373 result.add(withLinks("doclet.ReflectivePreviewAPI", className, 2374 reflectivePreviewAPI)); 2375 } 2376 return result; 2377 } 2378 2379 private Content withPreviewFeatures(String key, String className, 2380 String featureName, List<String> features) { 2381 String[] sep = new String[] { "" }; 2382 ContentBuilder featureCodes = new ContentBuilder(); 2383 features.forEach(c -> { 2384 featureCodes.add(sep[0]); 2385 featureCodes.add(HtmlTree.CODE(new ContentBuilder().add(c))); 2386 sep[0] = ", "; 2387 }); 2388 return contents.getContent(key, 2389 HtmlTree.CODE(Text.of(className)), 2390 new HtmlTree(TagName.EM).add(featureName), 2391 featureCodes); 2392 } 2393 2394 private Content withLinks(String key, String className, 2395 Set<TypeElement> elements) { 2396 String[] sep = new String[] { "" }; 2397 ContentBuilder links = new ContentBuilder(); 2398 elements.stream() 2399 .sorted(Comparator.comparing(te -> te.getSimpleName().toString())) 2400 .distinct() 2401 .map(te -> getLink(new HtmlLinkInfo(configuration, 2402 HtmlLinkInfo.Kind.LINK_TYPE_PARAMS_AND_BOUNDS, te) 2403 .label(HtmlTree.CODE(Text.of(te.getSimpleName()))) 2404 .skipPreview(true))) 2405 .forEach(c -> { 2406 links.add(sep[0]); 2407 links.add(c); 2408 sep[0] = ", "; 2409 }); 2410 return contents.getContent(key, 2411 HtmlTree.CODE(Text.of(className)), 2412 links); 2413 } 2414 2415 public URI resolveExternalSpecURI(URI specURI) { 2416 if (!specURI.isAbsolute()) { 2417 URI baseURI = configuration.getOptions().specBaseURI(); 2418 if (baseURI == null) { 2419 baseURI = URI.create("../specs/"); 2420 } 2421 if (!baseURI.isAbsolute() && !pathToRoot.isEmpty()) { 2422 baseURI 2423 = URI.create(pathToRoot.getPath() + "/").resolve(baseURI); 2424 } 2425 specURI = baseURI.resolve(specURI); 2426 } 2427 return specURI; 2428 } 2429 2430}