1 /*
   2  * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.javadoc.internal.doclets.formats.html;
  27 
  28 import jdk.javadoc.internal.doclets.formats.html.markup.Table;
  29 import jdk.javadoc.internal.doclets.formats.html.markup.TableHeader;
  30 
  31 import java.util.ArrayList;
  32 import java.util.Collections;
  33 import java.util.List;
  34 import java.util.Map;
  35 import java.util.Set;
  36 import java.util.SortedSet;
  37 import java.util.TreeMap;
  38 import java.util.TreeSet;
  39 
  40 import javax.lang.model.element.Element;
  41 import javax.lang.model.element.ModuleElement;
  42 import javax.lang.model.element.PackageElement;
  43 import javax.lang.model.element.TypeElement;
  44 import javax.lang.model.util.ElementFilter;
  45 
  46 import com.sun.source.doctree.DocTree;
  47 import jdk.javadoc.doclet.DocletEnvironment.ModuleMode;
  48 import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder;
  49 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlConstants;
  50 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle;
  51 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag;
  52 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree;
  53 import jdk.javadoc.internal.doclets.formats.html.markup.Links;
  54 import jdk.javadoc.internal.doclets.formats.html.markup.RawHtml;
  55 import jdk.javadoc.internal.doclets.formats.html.markup.StringContent;
  56 import jdk.javadoc.internal.doclets.toolkit.Content;
  57 import jdk.javadoc.internal.doclets.toolkit.ModuleSummaryWriter;
  58 import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper;
  59 import jdk.javadoc.internal.doclets.toolkit.util.DocFileIOException;
  60 import jdk.javadoc.internal.doclets.toolkit.util.DocPaths;
  61 
  62 /**
  63  * Class to generate file for each module contents in the right-hand frame. This will list all the
  64  * required modules, packages and service types for the module. A click on any of the links will update
  65  * the frame with the clicked element page.
  66  *
  67  *  <p><b>This is NOT part of any supported API.
  68  *  If you write code that depends on this, you do so at your own risk.
  69  *  This code and its internal interfaces are subject to change or
  70  *  deletion without notice.</b>
  71  *
  72  * @author Bhavesh Patel
  73  */
  74 public class ModuleWriterImpl extends HtmlDocletWriter implements ModuleSummaryWriter {
  75 
  76     /**
  77      * The prev module name in the alpha-order list.
  78      */
  79     protected ModuleElement prevModule;
  80 
  81     /**
  82      * The next module name in the alpha-order list.
  83      */
  84     protected ModuleElement nextModule;
  85 
  86     /**
  87      * The module being documented.
  88      */
  89     protected ModuleElement mdle;
  90 
  91     /**
  92      * The module mode for this javadoc run. It can be set to "api" or "all".
  93      */
  94     private final ModuleMode moduleMode;
  95 
  96     /**
  97      * Map of module elements and modifiers required by this module.
  98      */
  99     private final Map<ModuleElement, Content> requires
 100             = new TreeMap<>(utils.makeModuleComparator());
 101 
 102     /**
 103      * Map of indirect modules and modifiers, transitive closure, required by this module.
 104      */
 105     private final Map<ModuleElement, Content> indirectModules
 106             = new TreeMap<>(utils.makeModuleComparator());
 107 
 108     /**
 109      * Details about a package in a module.
 110      * A package may be not exported, or exported to some modules, or exported to all modules.
 111      * A package may be not opened, or opened to some modules, or opened to all modules.
 112      * A package that is neither exported or opened to any modules is a concealed package.
 113      * An open module opens all its packages to all modules.
 114      */
 115     class PackageEntry {
 116         /**
 117          * Summary of package exports:
 118          * If null, the package is not exported to any modules;
 119          * if empty, the package is exported to all modules;
 120          * otherwise, the package is exported to these modules.
 121          */
 122         Set<ModuleElement> exportedTo;
 123 
 124         /**
 125          * Summary of package opens:
 126          * If null, the package is not opened to any modules;
 127          * if empty, the package is opened to all modules;
 128          * otherwise, the package is opened to these modules.
 129          */
 130         Set<ModuleElement> openedTo;
 131     }
 132 
 133     /**
 134      * Map of packages of this module, and details of whether they are exported or opened.
 135      */
 136     private final Map<PackageElement, PackageEntry> packages = new TreeMap<>(utils.makePackageComparator());
 137 
 138     /**
 139      * Map of indirect modules (transitive closure) and their exported packages.
 140      */
 141     private final Map<ModuleElement, SortedSet<PackageElement>> indirectPackages
 142             = new TreeMap<>(utils.makeModuleComparator());
 143 
 144     /**
 145      * Map of indirect modules (transitive closure) and their open packages.
 146      */
 147     private final Map<ModuleElement, SortedSet<PackageElement>> indirectOpenPackages
 148             = new TreeMap<>(utils.makeModuleComparator());
 149 
 150     /**
 151      * Set of services used by the module.
 152      */
 153     private final SortedSet<TypeElement> uses
 154             = new TreeSet<>(utils.makeAllClassesComparator());
 155 
 156     /**
 157      * Map of services used by the module and specified using @uses javadoc tag, and description.
 158      */
 159     private final Map<TypeElement, Content> usesTrees
 160             = new TreeMap<>(utils.makeAllClassesComparator());
 161 
 162     /**
 163      * Map of services provided by this module, and set of its implementations.
 164      */
 165     private final Map<TypeElement, SortedSet<TypeElement>> provides
 166             = new TreeMap<>(utils.makeAllClassesComparator());
 167 
 168     /**
 169      * Map of services provided by the module and specified using @provides javadoc tag, and
 170      * description.
 171      */
 172     private final Map<TypeElement, Content> providesTrees
 173             = new TreeMap<>(utils.makeAllClassesComparator());
 174 
 175     /**
 176      * The HTML tree for main tag.
 177      */
 178     protected HtmlTree mainTree = HtmlTree.MAIN();
 179 
 180     /**
 181      * The HTML tree for section tag.
 182      */
 183     protected HtmlTree sectionTree = HtmlTree.SECTION();
 184 
 185     /**
 186      * Constructor to construct ModuleWriter object and to generate "moduleName-summary.html" file.
 187      *
 188      * @param configuration the configuration of the doclet.
 189      * @param mdle        Module under consideration.
 190      * @param prevModule   Previous module in the sorted array.
 191      * @param nextModule   Next module in the sorted array.
 192      */
 193     public ModuleWriterImpl(HtmlConfiguration configuration,
 194             ModuleElement mdle, ModuleElement prevModule, ModuleElement nextModule) {
 195         super(configuration, configuration.docPaths.moduleSummary(mdle));
 196         this.prevModule = prevModule;
 197         this.nextModule = nextModule;
 198         this.mdle = mdle;
 199         this.moduleMode = configuration.docEnv.getModuleMode();
 200         computeModulesData();
 201     }
 202 
 203     /**
 204      * Get the module header.
 205      *
 206      * @param heading the heading for the section
 207      */
 208     @Override
 209     public Content getModuleHeader(String heading) {
 210         HtmlTree bodyTree = getBody(true, getWindowTitle(mdle.getQualifiedName().toString()));
 211         HtmlTree htmlTree = (configuration.allowTag(HtmlTag.HEADER))
 212                 ? HtmlTree.HEADER()
 213                 : bodyTree;
 214         addTop(htmlTree);
 215         addNavLinks(true, htmlTree);
 216         if (configuration.allowTag(HtmlTag.HEADER)) {
 217             bodyTree.addContent(htmlTree);
 218         }
 219         HtmlTree div = new HtmlTree(HtmlTag.DIV);
 220         div.setStyle(HtmlStyle.header);
 221         Content annotationContent = new HtmlTree(HtmlTag.P);
 222         addAnnotationInfo(mdle, annotationContent);
 223         div.addContent(annotationContent);
 224         Content label = mdle.isOpen() && (configuration.docEnv.getModuleMode() == ModuleMode.ALL)
 225                 ? contents.openModuleLabel : contents.moduleLabel;
 226         Content tHeading = HtmlTree.HEADING(HtmlConstants.TITLE_HEADING, true,
 227                 HtmlStyle.title, label);
 228         tHeading.addContent(Contents.SPACE);
 229         Content moduleHead = new RawHtml(heading);
 230         tHeading.addContent(moduleHead);
 231         div.addContent(tHeading);
 232         if (configuration.allowTag(HtmlTag.MAIN)) {
 233             mainTree.addContent(div);
 234         } else {
 235             bodyTree.addContent(div);
 236         }
 237         return bodyTree;
 238     }
 239 
 240     /**
 241      * Get the content header.
 242      */
 243     @Override
 244     public Content getContentHeader() {
 245         HtmlTree div = new HtmlTree(HtmlTag.DIV);
 246         div.setStyle(HtmlStyle.contentContainer);
 247         return div;
 248     }
 249 
 250     /**
 251      * Get the summary section header.
 252      */
 253     @Override
 254     public Content getSummaryHeader() {
 255         HtmlTree li = new HtmlTree(HtmlTag.LI);
 256         li.setStyle(HtmlStyle.blockList);
 257         return li;
 258     }
 259 
 260     /**
 261      * Get the summary tree.
 262      *
 263      * @param summaryContentTree the content tree to be added to the summary tree.
 264      */
 265     @Override
 266     public Content getSummaryTree(Content summaryContentTree) {
 267         HtmlTree ul = HtmlTree.UL(HtmlStyle.blockList, summaryContentTree);
 268         return ul;
 269     }
 270 
 271     /**
 272      * Compute the modules data that will be displayed in various tables on the module summary page.
 273      */
 274     public void computeModulesData() {
 275         CommentHelper ch = utils.getCommentHelper(mdle);
 276         // Get module dependencies using the module's transitive closure.
 277         Map<ModuleElement, String> dependentModules = utils.getDependentModules(mdle);
 278         // Add all dependent modules to indirect modules set. We will remove the modules,
 279         // listed using the requires directive, from this set to come up with the table of indirect
 280         // required modules.
 281         dependentModules.forEach((module, mod) -> {
 282             if (shouldDocument(module)) {
 283                 indirectModules.put(module, new StringContent(mod));
 284             }
 285         });
 286         (ElementFilter.requiresIn(mdle.getDirectives())).forEach((directive) -> {
 287             ModuleElement m = directive.getDependency();
 288             if (shouldDocument(m)) {
 289                 if (moduleMode == ModuleMode.ALL || directive.isTransitive()) {
 290                     requires.put(m, new StringContent(utils.getModifiers(directive)));
 291             } else {
 292                     // For api mode, just keep the public requires in dependentModules for display of
 293                     // indirect packages in the "Packages" section.
 294                     dependentModules.remove(m);
 295                 }
 296                 indirectModules.remove(m);
 297         }
 298         });
 299 
 300         // Get all packages if module is open or if displaying concealed modules
 301         for (PackageElement pkg : utils.getModulePackageMap().getOrDefault(mdle, Collections.emptySet())) {
 302             if (shouldDocument(pkg) && (mdle.isOpen() || moduleMode == ModuleMode.ALL)) {
 303                 PackageEntry e = new PackageEntry();
 304                 if (mdle.isOpen()) {
 305                     e.openedTo = Collections.emptySet();
 306                 }
 307                 packages.put(pkg, e);
 308             }
 309         };
 310 
 311         // Get all exported packages for the module, using the exports directive for the module.
 312         for (ModuleElement.ExportsDirective directive : ElementFilter.exportsIn(mdle.getDirectives())) {
 313             PackageElement p = directive.getPackage();
 314             if (shouldDocument(p)) {
 315                 List<? extends ModuleElement> targetMdles = directive.getTargetModules();
 316                 // Include package if in details mode, or exported to all (i.e. targetModules == null)
 317                 if (moduleMode == ModuleMode.ALL || targetMdles == null) {
 318                     PackageEntry packageEntry = packages.computeIfAbsent(p, pkg -> new PackageEntry());
 319                     SortedSet<ModuleElement> mdleList = new TreeSet<>(utils.makeModuleComparator());
 320                     if (targetMdles != null) {
 321                         mdleList.addAll(targetMdles);
 322                     }
 323                     packageEntry.exportedTo = mdleList;
 324                 }
 325             }
 326         }
 327 
 328         // Get all opened packages for the module, using the opens directive for the module.
 329         // If it is an open module, there will be no separate opens directives.
 330         for (ModuleElement.OpensDirective directive : ElementFilter.opensIn(mdle.getDirectives())) {
 331             PackageElement p = directive.getPackage();
 332             if (shouldDocument(p)) {
 333                 List<? extends ModuleElement> targetMdles = directive.getTargetModules();
 334                 // Include package if in details mode, or opened to all (i.e. targetModules == null)
 335                 if (moduleMode == ModuleMode.ALL || targetMdles == null) {
 336                     PackageEntry packageEntry = packages.computeIfAbsent(p, pkg -> new PackageEntry());
 337                     SortedSet<ModuleElement> mdleList = new TreeSet<>(utils.makeModuleComparator());
 338                     if (targetMdles != null) {
 339                         mdleList.addAll(targetMdles);
 340                     }
 341                     packageEntry.openedTo = mdleList;
 342                 }
 343             }
 344         }
 345 
 346         // Get all the exported and opened packages, for the transitive closure of the module, to be displayed in
 347         // the indirect packages tables.
 348         dependentModules.forEach((module, mod) -> {
 349             SortedSet<PackageElement> exportPkgList = new TreeSet<>(utils.makePackageComparator());
 350             (ElementFilter.exportsIn(module.getDirectives())).forEach((directive) -> {
 351                 PackageElement pkg = directive.getPackage();
 352                 if (shouldDocument(pkg)) {
 353                     // Qualified exports are not displayed in API mode
 354                     if (moduleMode == ModuleMode.ALL || directive.getTargetModules() == null) {
 355                         exportPkgList.add(pkg);
 356                     }
 357                 }
 358             });
 359             // If none of the indirect modules have exported packages to be displayed, we should not be
 360             // displaying the table and so it should not be added to the map.
 361             if (!exportPkgList.isEmpty()) {
 362                 indirectPackages.put(module, exportPkgList);
 363             }
 364             SortedSet<PackageElement> openPkgList = new TreeSet<>(utils.makePackageComparator());
 365             if (module.isOpen()) {
 366                 openPkgList.addAll(utils.getModulePackageMap().getOrDefault(module, Collections.emptySet()));
 367             } else {
 368                 (ElementFilter.opensIn(module.getDirectives())).forEach((directive) -> {
 369                     PackageElement pkg = directive.getPackage();
 370                     if (shouldDocument(pkg)) {
 371                         // Qualified opens are not displayed in API mode
 372                         if (moduleMode == ModuleMode.ALL || directive.getTargetModules() == null) {
 373                             openPkgList.add(pkg);
 374                         }
 375                     }
 376                 });
 377             }
 378             // If none of the indirect modules have opened packages to be displayed, we should not be
 379             // displaying the table and so it should not be added to the map.
 380             if (!openPkgList.isEmpty()) {
 381                 indirectOpenPackages.put(module, openPkgList);
 382             }
 383         });
 384         // Get all the services listed as uses directive.
 385         (ElementFilter.usesIn(mdle.getDirectives())).forEach((directive) -> {
 386             TypeElement u = directive.getService();
 387             if (shouldDocument(u)) {
 388                 uses.add(u);
 389             }
 390         });
 391         // Get all the services and implementations listed as provides directive.
 392         (ElementFilter.providesIn(mdle.getDirectives())).forEach((directive) -> {
 393             TypeElement u = directive.getService();
 394             if (shouldDocument(u)) {
 395                 List<? extends TypeElement> implList = directive.getImplementations();
 396                 SortedSet<TypeElement> implSet = new TreeSet<>(utils.makeAllClassesComparator());
 397                 implSet.addAll(implList);
 398                 provides.put(u, implSet);
 399             }
 400         });
 401         // Generate the map of all services listed using @provides, and the description.
 402         (utils.getBlockTags(mdle, DocTree.Kind.PROVIDES)).forEach((tree) -> {
 403             TypeElement t = ch.getServiceType(configuration, tree);
 404             if (t != null) {
 405                 providesTrees.put(t, commentTagsToContent(tree, mdle, ch.getDescription(configuration, tree), false));
 406             }
 407         });
 408         // Generate the map of all services listed using @uses, and the description.
 409         (utils.getBlockTags(mdle, DocTree.Kind.USES)).forEach((tree) -> {
 410             TypeElement t = ch.getServiceType(configuration, tree);
 411             if (t != null) {
 412                 usesTrees.put(t, commentTagsToContent(tree, mdle, ch.getDescription(configuration, tree), false));
 413             }
 414         });
 415     }
 416 
 417     /**
 418      * Returns true if the element should be documented on the module summary page.
 419      *
 420      * @param element the element to be checked
 421      * @return true if the element should be documented
 422      */
 423     public boolean shouldDocument(Element element) {
 424         return (moduleMode == ModuleMode.ALL || utils.isIncluded(element));
 425     }
 426 
 427     /**
 428      * Returns true if there are elements to be displayed.
 429      *
 430      * @param section set of elements
 431      * @return true if there are elements to be displayed
 432      */
 433     public boolean display(Set<? extends Element> section) {
 434         return section != null && !section.isEmpty();
 435     }
 436 
 437     /**
 438      * Returns true if there are elements to be displayed.
 439      *
 440      * @param section map of elements.
 441      * @return true if there are elements to be displayed
 442      */
 443     public boolean display(Map<? extends Element, ?> section) {
 444         return section != null && !section.isEmpty();
 445     }
 446 
 447     /*
 448      * Returns true, in API mode, if at least one type element in
 449      * the typeElements set is referenced by a javadoc tag in tagsMap.
 450      */
 451     private boolean displayServices(Set<TypeElement> typeElements,
 452                                     Map<TypeElement, Content> tagsMap) {
 453         return typeElements != null &&
 454                 typeElements.stream().anyMatch((v) -> displayServiceDirective(v, tagsMap));
 455     }
 456 
 457     /*
 458      * Returns true, in API mode, if the type element is referenced
 459      * from a javadoc tag in tagsMap.
 460      */
 461     private boolean displayServiceDirective(TypeElement typeElement,
 462                                             Map<TypeElement, Content> tagsMap) {
 463         return moduleMode == ModuleMode.ALL || tagsMap.containsKey(typeElement);
 464     }
 465 
 466     /**
 467      * Add the summary header.
 468      *
 469      * @param startMarker the marker comment
 470      * @param markerAnchor the marker anchor for the section
 471      * @param heading the heading for the section
 472      * @param htmltree the content tree to which the information is added
 473      */
 474     public void addSummaryHeader(Content startMarker, SectionName markerAnchor, Content heading,
 475             Content htmltree) {
 476         htmltree.addContent(startMarker);
 477         htmltree.addContent(links.createAnchor(markerAnchor));
 478         htmltree.addContent(HtmlTree.HEADING(HtmlTag.H3, heading));
 479     }
 480 
 481     /**
 482      * Get a table, with two columns.
 483      *
 484      * @param caption the table caption
 485      * @param tableSummary the summary for the table
 486      * @param tableStyle the table style
 487      * @param tableHeader the table header
 488      * @return a content object
 489      */
 490     private Table getTable2(Content caption, String tableSummary, HtmlStyle tableStyle,
 491             TableHeader tableHeader) {
 492         return new Table(configuration.htmlVersion, tableStyle)
 493                 .setSummary(tableSummary)
 494                 .setCaption(caption)
 495                 .setHeader(tableHeader)
 496                 .setColumnStyles(HtmlStyle.colFirst, HtmlStyle.colLast);
 497     }
 498 
 499     /**
 500      * Get a table, with three columns, with the second column being the defining column.
 501      *
 502      * @param caption the table caption
 503      * @param tableSummary the summary for the table
 504      * @param tableStyle the table style
 505      * @param tableHeader the table header
 506      * @return a content object
 507      */
 508     private Table getTable3(Content caption, String tableSummary, HtmlStyle tableStyle,
 509             TableHeader tableHeader) {
 510         return new Table(configuration.htmlVersion, tableStyle)
 511                 .setSummary(tableSummary)
 512                 .setCaption(caption)
 513                 .setHeader(tableHeader)
 514                 .setRowScopeColumn(1)
 515                 .setColumnStyles(HtmlStyle.colFirst, HtmlStyle.colSecond, HtmlStyle.colLast);
 516     }
 517 
 518     /**
 519      * {@inheritDoc}
 520      */
 521     @Override
 522     public void addModulesSummary(Content summaryContentTree) {
 523         if (display(requires) || display(indirectModules)) {
 524             TableHeader requiresTableHeader =
 525                     new TableHeader(contents.modifierLabel, contents.moduleLabel,
 526                             contents.descriptionLabel);
 527             HtmlTree li = new HtmlTree(HtmlTag.LI);
 528             li.setStyle(HtmlStyle.blockList);
 529             addSummaryHeader(HtmlConstants.START_OF_MODULES_SUMMARY, SectionName.MODULES,
 530                     contents.navModules, li);
 531             if (display(requires)) {
 532                 String text = resources.getText("doclet.Requires_Summary");
 533                 String tableSummary = resources.getText("doclet.Member_Table_Summary",
 534                         text,
 535                         resources.getText("doclet.modules"));
 536                 Content caption = getTableCaption(new StringContent(text));
 537                 Table table = getTable3(caption, tableSummary, HtmlStyle.requiresSummary,
 538                             requiresTableHeader);
 539                 addModulesList(requires, table);
 540                 li.addContent(table.toContent());
 541             }
 542             // Display indirect modules table in both "api" and "all" mode.
 543             if (display(indirectModules)) {
 544                 String amrText = resources.getText("doclet.Indirect_Requires_Summary");
 545                 String amrTableSummary = resources.getText("doclet.Member_Table_Summary",
 546                         amrText,
 547                         configuration.getText("doclet.modules"));
 548                 Content amrCaption = getTableCaption(new StringContent(amrText));
 549                 Table amrTable = getTable3(amrCaption, amrTableSummary, HtmlStyle.requiresSummary,
 550                             requiresTableHeader);
 551                 addModulesList(indirectModules, amrTable);
 552                 li.addContent(amrTable.toContent());
 553             }
 554             HtmlTree ul = HtmlTree.UL(HtmlStyle.blockList, li);
 555             summaryContentTree.addContent(ul);
 556         }
 557     }
 558 
 559     /**
 560      * Add the list of modules.
 561      *
 562      * @param mdleMap map of modules and modifiers
 563      * @param tbody the content tree to which the list will be added
 564      */
 565     private void addModulesList(Map<ModuleElement, Content> mdleMap, Table table) {
 566         for (ModuleElement m : mdleMap.keySet()) {
 567             Content modifiers = mdleMap.get(m);
 568             Content moduleLink = getModuleLink(m, new StringContent(m.getQualifiedName()));
 569             Content moduleSummary = new ContentBuilder();
 570             addSummaryComment(m, moduleSummary);
 571             table.addRow(modifiers, moduleLink, moduleSummary);
 572         }
 573     }
 574 
 575     @Override
 576     public void addPackagesSummary(Content summaryContentTree) {
 577         if (display(packages)
 578                 || display(indirectPackages) || display(indirectOpenPackages)) {
 579             HtmlTree li = new HtmlTree(HtmlTag.LI);
 580             li.setStyle(HtmlStyle.blockList);
 581             addSummaryHeader(HtmlConstants.START_OF_PACKAGES_SUMMARY, SectionName.PACKAGES,
 582                     contents.navPackages, li);
 583             if (display(packages)) {
 584                 String tableSummary = resources.getText("doclet.Member_Table_Summary",
 585                         resources.getText("doclet.Packages_Summary"),
 586                         resources.getText("doclet.packages"));
 587                 addPackageSummary(tableSummary, li);
 588             }
 589             TableHeader indirectPackagesHeader =
 590                     new TableHeader(contents.fromLabel, contents.packagesLabel);
 591             if (display(indirectPackages)) {
 592                 String aepText = resources.getText("doclet.Indirect_Exports_Summary");
 593                 String aepTableSummary = resources.getText("doclet.Indirect_Packages_Table_Summary",
 594                         aepText,
 595                         resources.getText("doclet.modules"),
 596                         resources.getText("doclet.packages"));
 597                 Table aepTable = getTable2(new StringContent(aepText), aepTableSummary,
 598                         HtmlStyle.packagesSummary, indirectPackagesHeader);
 599                 addIndirectPackages(aepTable, indirectPackages);
 600                 li.addContent(aepTable.toContent());
 601             }
 602             if (display(indirectOpenPackages)) {
 603                 String aopText = resources.getText("doclet.Indirect_Opens_Summary");
 604                 String aopTableSummary = resources.getText("doclet.Indirect_Packages_Table_Summary",
 605                         aopText,
 606                         resources.getText("doclet.modules"),
 607                         resources.getText("doclet.packages"));
 608                 Table aopTable = getTable2(new StringContent(aopText), aopTableSummary,
 609                         HtmlStyle.packagesSummary, indirectPackagesHeader);
 610                 addIndirectPackages(aopTable, indirectOpenPackages);
 611                 li.addContent(aopTable.toContent());
 612             }
 613             HtmlTree ul = HtmlTree.UL(HtmlStyle.blockList, li);
 614             summaryContentTree.addContent(ul);
 615         }
 616     }
 617 
 618     /**
 619      * Add the package summary for the module.
 620      *
 621      * @param tableSummary
 622      * @param li
 623      */
 624     public void addPackageSummary(String tableSummary, HtmlTree li) {
 625         Table table = new Table(configuration.htmlVersion, HtmlStyle.packagesSummary)
 626                 .setSummary(tableSummary)
 627                 .setDefaultTab(resources.getText("doclet.All_Packages"))
 628                 .addTab(resources.getText("doclet.Exported_Packages_Summary"), this::isExported)
 629                 .addTab(resources.getText("doclet.Opened_Packages_Summary"), this::isOpened)
 630                 .addTab(resources.getText("doclet.Concealed_Packages_Summary"), this::isConcealed)
 631                 .setTabScript(i -> String.format("showPkgs(%d);", i))
 632                 .setTabScriptVariable("packages");
 633 
 634         // Determine whether to show the "Exported To" and "Opened To" columns,
 635         // based on whether such columns would provide "useful" info.
 636         int numExports = 0;
 637         int numUnqualifiedExports = 0;
 638         int numOpens = 0;
 639         int numUnqualifiedOpens = 0;
 640 
 641         for (PackageEntry e : packages.values()) {
 642             if (e.exportedTo != null) {
 643                 numExports++;
 644                 if (e.exportedTo.isEmpty()) {
 645                     numUnqualifiedExports++;
 646                 }
 647             }
 648             if (e.openedTo != null) {
 649                 numOpens++;
 650                 if (e.openedTo.isEmpty()) {
 651                     numUnqualifiedOpens++;
 652                 }
 653             }
 654         }
 655 
 656         boolean showExportedTo = numExports > 0 && (numOpens > 0   || numUnqualifiedExports < packages.size());
 657         boolean showOpenedTo   = numOpens > 0   && (numExports > 0 || numUnqualifiedOpens < packages.size());
 658 
 659         // Create the table header and column styles.
 660         List<Content> colHeaders = new ArrayList<>();
 661         List<HtmlStyle> colStyles = new ArrayList<>();
 662         colHeaders.add(contents.packageLabel);
 663         colStyles.add(HtmlStyle.colFirst);
 664 
 665         if (showExportedTo) {
 666             colHeaders.add(contents.exportedTo);
 667             colStyles.add(HtmlStyle.colSecond);
 668         }
 669 
 670         if (showOpenedTo) {
 671             colHeaders.add(contents.openedTo);
 672             colStyles.add(HtmlStyle.colSecond);
 673         }
 674 
 675         colHeaders.add(contents.descriptionLabel);
 676         colStyles.add(HtmlStyle.colLast);
 677 
 678         table.setHeader(new TableHeader(colHeaders).styles(colStyles))
 679                 .setColumnStyles(colStyles);
 680 
 681         // Add the table rows, based on the "packages" map.
 682         for (Map.Entry<PackageElement, PackageEntry> e : packages.entrySet()) {
 683             PackageElement pkg = e.getKey();
 684             PackageEntry entry = e.getValue();
 685             List<Content> row = new ArrayList<>();
 686             Content pkgLinkContent = getPackageLink(pkg, new StringContent(utils.getPackageName(pkg)));
 687             row.add(pkgLinkContent);
 688 
 689             if (showExportedTo) {
 690                 row.add(getPackageExportOpensTo(entry.exportedTo));
 691             }
 692             if (showOpenedTo) {
 693                 row.add(getPackageExportOpensTo(entry.openedTo));
 694             }
 695             Content summary = new ContentBuilder();
 696             addSummaryComment(pkg, summary);
 697             row.add(summary);
 698 
 699             table.addRow(pkg, row);
 700         }
 701 
 702         li.addContent(table.toContent());
 703         if (table.needsScript()) {
 704             mainBodyScript.append(table.getScript());
 705         }
 706     }
 707 
 708     private boolean isExported(Element e) {
 709         PackageEntry entry = packages.get((PackageElement) e);
 710         return (entry != null) && (entry.exportedTo != null);
 711     }
 712 
 713     private boolean isOpened(Element e) {
 714         PackageEntry entry = packages.get((PackageElement) e);
 715         return (entry != null) && (entry.openedTo != null);
 716     }
 717 
 718     private boolean isConcealed(Element e) {
 719         PackageEntry entry = packages.get((PackageElement) e);
 720         return (entry != null) && (entry.exportedTo == null) && (entry.openedTo == null);
 721     }
 722 
 723     private Content getPackageExportOpensTo(Set<ModuleElement> modules) {
 724         if (modules == null) {
 725             return new StringContent(resources.getText("doclet.None"));
 726         } else if (modules.isEmpty()) {
 727             return new StringContent(resources.getText("doclet.All_Modules"));
 728         } else {
 729             Content list = new ContentBuilder();
 730             for (ModuleElement m : modules) {
 731                 if (!list.isEmpty()) {
 732                     list.addContent(new StringContent(", "));
 733                 }
 734                 list.addContent(getModuleLink(m, new StringContent(m.getQualifiedName())));
 735             }
 736             return list;
 737         }
 738     }
 739 
 740     /**
 741      * Add the indirect packages for the module being documented.
 742      *
 743      * @param table the table to which the content rows will be added
 744      * @param ip indirect packages to be added
 745      */
 746     public void addIndirectPackages(Table table, Map<ModuleElement, SortedSet<PackageElement>> ip) {
 747         for (Map.Entry<ModuleElement, SortedSet<PackageElement>> entry : ip.entrySet()) {
 748             ModuleElement m = entry.getKey();
 749             SortedSet<PackageElement> pkgList = entry.getValue();
 750             Content moduleLinkContent = getModuleLink(m, new StringContent(m.getQualifiedName()));
 751             Content list = new ContentBuilder();
 752             String sep = "";
 753             for (PackageElement pkg : pkgList) {
 754                 list.addContent(sep);
 755                 list.addContent(getPackageLink(pkg, new StringContent(utils.getPackageName(pkg))));
 756                 sep = " ";
 757             }
 758             table.addRow(moduleLinkContent, list);
 759         }
 760     }
 761 
 762     /**
 763      * {@inheritDoc}
 764      */
 765     @Override
 766     public void addServicesSummary(Content summaryContentTree) {
 767 
 768         boolean haveUses = displayServices(uses, usesTrees);
 769         boolean haveProvides = displayServices(provides.keySet(), providesTrees);
 770 
 771         if (haveProvides || haveUses) {
 772             HtmlTree li = new HtmlTree(HtmlTag.LI);
 773             li.setStyle(HtmlStyle.blockList);
 774             addSummaryHeader(HtmlConstants.START_OF_SERVICES_SUMMARY, SectionName.SERVICES,
 775                     contents.navServices, li);
 776             TableHeader usesProvidesTableHeader =
 777                     new TableHeader(contents.typeLabel, contents.descriptionLabel);
 778             if (haveProvides) {
 779                 String label = resources.getText("doclet.Provides_Summary");
 780                 String tableSummary = resources.getText("doclet.Member_Table_Summary",
 781                         label,
 782                         resources.getText("doclet.types"));
 783                 Table table = getTable2(new StringContent(label), tableSummary, HtmlStyle.providesSummary,
 784                         usesProvidesTableHeader);
 785                 addProvidesList(table);
 786                 if (!table.isEmpty()) {
 787                     li.addContent(table.toContent());
 788                 }
 789             }
 790             if (haveUses){
 791                 String label = resources.getText("doclet.Uses_Summary");
 792                 String tableSummary = resources.getText("doclet.Member_Table_Summary",
 793                         label,
 794                         resources.getText("doclet.types"));
 795                 Table table = getTable2(new StringContent(label), tableSummary, HtmlStyle.usesSummary,
 796                         usesProvidesTableHeader);
 797                 addUsesList(table);
 798                 if (!table.isEmpty()) {
 799                     li.addContent(table.toContent());
 800                 }
 801             }
 802             HtmlTree ul = HtmlTree.UL(HtmlStyle.blockList, li);
 803             summaryContentTree.addContent(ul);
 804         }
 805     }
 806 
 807     /**
 808      * Add the uses list for the module.
 809      *
 810      * @param table the table to which the services used will be added
 811      */
 812     public void addUsesList(Table table) {
 813         Content typeLinkContent;
 814         Content description;
 815         for (TypeElement t : uses) {
 816             if (!displayServiceDirective(t, usesTrees)) {
 817                 continue;
 818             }
 819             typeLinkContent = getLink(new LinkInfoImpl(configuration, LinkInfoImpl.Kind.PACKAGE, t));
 820             Content summary = new ContentBuilder();
 821             if (display(usesTrees)) {
 822                 description = usesTrees.get(t);
 823                 if (description != null && !description.isEmpty()) {
 824                     summary.addContent(HtmlTree.DIV(HtmlStyle.block, description));
 825                 } else {
 826                     addSummaryComment(t, summary);
 827                 }
 828             } else {
 829                 summary.addContent(Contents.SPACE);
 830             }
 831             table.addRow(typeLinkContent, summary);
 832         }
 833     }
 834 
 835     /**
 836      * Add the provides list for the module.
 837      *
 838      * @param table the table to which the services provided will be added
 839      */
 840     public void addProvidesList(Table table) {
 841         SortedSet<TypeElement> implSet;
 842         Content description;
 843         for (Map.Entry<TypeElement, SortedSet<TypeElement>> entry : provides.entrySet()) {
 844             TypeElement srv = entry.getKey();
 845             if (!displayServiceDirective(srv, providesTrees)) {
 846                 continue;
 847             }
 848             implSet = entry.getValue();
 849             Content srvLinkContent = getLink(new LinkInfoImpl(configuration, LinkInfoImpl.Kind.PACKAGE, srv));
 850             Content desc = new ContentBuilder();
 851             if (display(providesTrees)) {
 852                 description = providesTrees.get(srv);
 853                 desc.addContent((description != null && !description.isEmpty())
 854                         ? HtmlTree.DIV(HtmlStyle.block, description)
 855                         : Contents.SPACE);
 856             } else {
 857                 desc.addContent(Contents.SPACE);
 858                 }
 859             // Only display the implementation details in the "all" mode.
 860             if (moduleMode == ModuleMode.ALL && !implSet.isEmpty()) {
 861                 desc.addContent(new HtmlTree(HtmlTag.BR));
 862                 desc.addContent("(");
 863                 HtmlTree implSpan = HtmlTree.SPAN(HtmlStyle.implementationLabel, contents.implementation);
 864                 desc.addContent(implSpan);
 865                 desc.addContent(Contents.SPACE);
 866                 String sep = "";
 867                 for (TypeElement impl : implSet) {
 868                     desc.addContent(sep);
 869                     desc.addContent(getLink(new LinkInfoImpl(configuration, LinkInfoImpl.Kind.PACKAGE, impl)));
 870                     sep = ", ";
 871                 }
 872                 desc.addContent(")");
 873             }
 874             table.addRow(srvLinkContent, desc);
 875         }
 876     }
 877 
 878     /**
 879      * Add the module deprecation information to the documentation tree.
 880      *
 881      * @param div the content tree to which the deprecation information will be added
 882      */
 883     public void addDeprecationInfo(Content div) {
 884         List<? extends DocTree> deprs = utils.getBlockTags(mdle, DocTree.Kind.DEPRECATED);
 885         if (utils.isDeprecated(mdle)) {
 886             CommentHelper ch = utils.getCommentHelper(mdle);
 887             HtmlTree deprDiv = new HtmlTree(HtmlTag.DIV);
 888             deprDiv.setStyle(HtmlStyle.deprecationBlock);
 889             Content deprPhrase = HtmlTree.SPAN(HtmlStyle.deprecatedLabel, getDeprecatedPhrase(mdle));
 890             deprDiv.addContent(deprPhrase);
 891             if (!deprs.isEmpty()) {
 892                 List<? extends DocTree> commentTags = ch.getDescription(configuration, deprs.get(0));
 893                 if (!commentTags.isEmpty()) {
 894                     addInlineDeprecatedComment(mdle, deprs.get(0), deprDiv);
 895                 }
 896             }
 897             div.addContent(deprDiv);
 898         }
 899     }
 900 
 901     /**
 902      * {@inheritDoc}
 903      */
 904     @Override
 905     public void addModuleDescription(Content moduleContentTree) {
 906         if (!utils.getFullBody(mdle).isEmpty()) {
 907             Content tree = configuration.allowTag(HtmlTag.SECTION) ? HtmlTree.SECTION() : moduleContentTree;
 908             addDeprecationInfo(tree);
 909             tree.addContent(HtmlConstants.START_OF_MODULE_DESCRIPTION);
 910             tree.addContent(links.createAnchor(SectionName.MODULE_DESCRIPTION));
 911             addInlineComment(mdle, tree);
 912             if (configuration.allowTag(HtmlTag.SECTION)) {
 913                 moduleContentTree.addContent(tree);
 914             }
 915         }
 916     }
 917 
 918     /**
 919      * {@inheritDoc}
 920      */
 921     @Override
 922     public void addModuleTags(Content moduleContentTree) {
 923         Content tree = (configuration.allowTag(HtmlTag.SECTION))
 924                 ? HtmlTree.SECTION()
 925                 : moduleContentTree;
 926         addTagsInfo(mdle, tree);
 927         if (configuration.allowTag(HtmlTag.SECTION)) {
 928             moduleContentTree.addContent(tree);
 929         }
 930     }
 931 
 932     /**
 933      * Add summary details to the navigation bar.
 934      *
 935      * @param subDiv the content tree to which the summary detail links will be added
 936      */
 937     @Override
 938     protected void addSummaryDetailLinks(Content subDiv) {
 939         Content div = HtmlTree.DIV(getNavSummaryLinks());
 940         subDiv.addContent(div);
 941     }
 942 
 943     /**
 944      * Get summary links for navigation bar.
 945      *
 946      * @return the content tree for the navigation summary links
 947      */
 948     protected Content getNavSummaryLinks() {
 949         Content li = HtmlTree.LI(contents.moduleSubNavLabel);
 950         li.addContent(Contents.SPACE);
 951         Content ulNav = HtmlTree.UL(HtmlStyle.subNavList, li);
 952         Content liNav = new HtmlTree(HtmlTag.LI);
 953         liNav.addContent(!utils.getFullBody(mdle).isEmpty() && !configuration.nocomment
 954                 ? links.createLink(SectionName.MODULE_DESCRIPTION, contents.navModuleDescription)
 955                 : contents.navModuleDescription);
 956         addNavGap(liNav);
 957         liNav.addContent((display(requires) || display(indirectModules))
 958                 ? links.createLink(SectionName.MODULES, contents.navModules)
 959                 : contents.navModules);
 960         addNavGap(liNav);
 961         liNav.addContent((display(packages)
 962                 || display(indirectPackages) || display(indirectOpenPackages))
 963                 ? links.createLink(SectionName.PACKAGES, contents.navPackages)
 964                 : contents.navPackages);
 965         addNavGap(liNav);
 966         liNav.addContent((displayServices(uses, usesTrees) || displayServices(provides.keySet(), providesTrees))
 967                 ? links.createLink(SectionName.SERVICES, contents.navServices)
 968                 : contents.navServices);
 969         ulNav.addContent(liNav);
 970         return ulNav;
 971     }
 972 
 973     /**
 974      * {@inheritDoc}
 975      */
 976     @Override
 977     public void addModuleContent(Content contentTree, Content moduleContentTree) {
 978         if (configuration.allowTag(HtmlTag.MAIN)) {
 979             mainTree.addContent(moduleContentTree);
 980             contentTree.addContent(mainTree);
 981         } else {
 982             contentTree.addContent(moduleContentTree);
 983         }
 984     }
 985 
 986     /**
 987      * {@inheritDoc}
 988      */
 989     @Override
 990     public void addModuleFooter(Content contentTree) {
 991         Content htmlTree = (configuration.allowTag(HtmlTag.FOOTER))
 992                 ? HtmlTree.FOOTER()
 993                 : contentTree;
 994         addNavLinks(false, htmlTree);
 995         addBottom(htmlTree);
 996         if (configuration.allowTag(HtmlTag.FOOTER)) {
 997             contentTree.addContent(htmlTree);
 998         }
 999     }
1000 
1001     /**
1002      * {@inheritDoc}
1003      *
1004      * @throws jdk.javadoc.internal.doclets.toolkit.util.DocFileIOException
1005      */
1006     @Override
1007     public void printDocument(Content contentTree) throws DocFileIOException {
1008         printHtmlDocument(configuration.metakeywords.getMetaKeywordsForModule(mdle),
1009                 true, contentTree);
1010     }
1011 
1012     /**
1013      * Add the module package deprecation information to the documentation tree.
1014      *
1015      * @param li the content tree to which the deprecation information will be added
1016      * @param pkg the PackageDoc that is added
1017      */
1018     public void addPackageDeprecationInfo(Content li, PackageElement pkg) {
1019         List<? extends DocTree> deprs;
1020         if (utils.isDeprecated(pkg)) {
1021             deprs = utils.getDeprecatedTrees(pkg);
1022             HtmlTree deprDiv = new HtmlTree(HtmlTag.DIV);
1023             deprDiv.setStyle(HtmlStyle.deprecationBlock);
1024             Content deprPhrase = HtmlTree.SPAN(HtmlStyle.deprecatedLabel, getDeprecatedPhrase(pkg));
1025             deprDiv.addContent(deprPhrase);
1026             if (!deprs.isEmpty()) {
1027                 CommentHelper ch = utils.getCommentHelper(pkg);
1028                 List<? extends DocTree> commentTags = ch.getDescription(configuration, deprs.get(0));
1029                 if (!commentTags.isEmpty()) {
1030                     addInlineDeprecatedComment(pkg, deprs.get(0), deprDiv);
1031                 }
1032             }
1033             li.addContent(deprDiv);
1034         }
1035     }
1036 
1037     /**
1038      * Get this module link.
1039      *
1040      * @return a content tree for the module link
1041      */
1042     @Override
1043     protected Content getNavLinkModule() {
1044         Content li = HtmlTree.LI(HtmlStyle.navBarCell1Rev, contents.moduleLabel);
1045         return li;
1046     }
1047 
1048     /**
1049      * Get "PREV MODULE" link in the navigation bar.
1050      *
1051      * @return a content tree for the previous link
1052      */
1053     @Override
1054     public Content getNavLinkPrevious() {
1055         Content li;
1056         if (prevModule == null) {
1057             li = HtmlTree.LI(contents.prevModuleLabel);
1058         } else {
1059             li = HtmlTree.LI(links.createLink(pathToRoot.resolve(docPaths.moduleSummary(
1060                     prevModule)), contents.prevModuleLabel, "", ""));
1061         }
1062         return li;
1063     }
1064 
1065     /**
1066      * Get "NEXT MODULE" link in the navigation bar.
1067      *
1068      * @return a content tree for the next link
1069      */
1070     @Override
1071     public Content getNavLinkNext() {
1072         Content li;
1073         if (nextModule == null) {
1074             li = HtmlTree.LI(contents.nextModuleLabel);
1075         } else {
1076             li = HtmlTree.LI(links.createLink(pathToRoot.resolve(docPaths.moduleSummary(
1077                     nextModule)), contents.nextModuleLabel, "", ""));
1078         }
1079         return li;
1080     }
1081 }