1 /*
   2  * Copyright (c) 2016, 2019, 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 java.util.ArrayList;
  29 import java.util.Collections;
  30 import java.util.List;
  31 import java.util.Map;
  32 import java.util.Set;
  33 import java.util.SortedSet;
  34 import java.util.TreeMap;
  35 import java.util.TreeSet;
  36 
  37 import javax.lang.model.element.Element;
  38 import javax.lang.model.element.ModuleElement;
  39 import javax.lang.model.element.PackageElement;
  40 import javax.lang.model.element.TypeElement;
  41 import javax.lang.model.util.ElementFilter;
  42 
  43 import com.sun.source.doctree.DocTree;
  44 import jdk.javadoc.doclet.DocletEnvironment.ModuleMode;
  45 import jdk.javadoc.internal.doclets.formats.html.markup.BodyContents;
  46 import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder;
  47 import jdk.javadoc.internal.doclets.formats.html.markup.Entity;
  48 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle;
  49 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag;
  50 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree;
  51 import jdk.javadoc.internal.doclets.formats.html.markup.Navigation;
  52 import jdk.javadoc.internal.doclets.formats.html.markup.Navigation.PageMode;
  53 import jdk.javadoc.internal.doclets.formats.html.markup.RawHtml;
  54 import jdk.javadoc.internal.doclets.formats.html.markup.StringContent;
  55 import jdk.javadoc.internal.doclets.formats.html.markup.Table;
  56 import jdk.javadoc.internal.doclets.formats.html.markup.TableHeader;
  57 import jdk.javadoc.internal.doclets.toolkit.Content;
  58 import jdk.javadoc.internal.doclets.toolkit.ModuleSummaryWriter;
  59 import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper;
  60 import jdk.javadoc.internal.doclets.toolkit.util.DocFileIOException;
  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 public class ModuleWriterImpl extends HtmlDocletWriter implements ModuleSummaryWriter {
  73 
  74     /**
  75      * The module being documented.
  76      */
  77     protected ModuleElement mdle;
  78 
  79     /**
  80      * The module mode for this javadoc run. It can be set to "api" or "all".
  81      */
  82     private final ModuleMode moduleMode;
  83 
  84     /**
  85      * Map of module elements and modifiers required by this module.
  86      */
  87     private final Map<ModuleElement, Content> requires
  88             = new TreeMap<>(utils.makeModuleComparator());
  89 
  90     /**
  91      * Map of indirect modules and modifiers, transitive closure, required by this module.
  92      */
  93     private final Map<ModuleElement, Content> indirectModules
  94             = new TreeMap<>(utils.makeModuleComparator());
  95 
  96     /**
  97      * Details about a package in a module.
  98      * A package may be not exported, or exported to some modules, or exported to all modules.
  99      * A package may be not opened, or opened to some modules, or opened to all modules.
 100      * A package that is neither exported or opened to any modules is a concealed package.
 101      * An open module opens all its packages to all modules.
 102      */
 103     class PackageEntry {
 104         /**
 105          * Summary of package exports:
 106          * If null, the package is not exported to any modules;
 107          * if empty, the package is exported to all modules;
 108          * otherwise, the package is exported to these modules.
 109          */
 110         Set<ModuleElement> exportedTo;
 111 
 112         /**
 113          * Summary of package opens:
 114          * If null, the package is not opened to any modules;
 115          * if empty, the package is opened to all modules;
 116          * otherwise, the package is opened to these modules.
 117          */
 118         Set<ModuleElement> openedTo;
 119     }
 120 
 121     /**
 122      * Map of packages of this module, and details of whether they are exported or opened.
 123      */
 124     private final Map<PackageElement, PackageEntry> packages = new TreeMap<>(utils.makePackageComparator());
 125 
 126     /**
 127      * Map of indirect modules (transitive closure) and their exported packages.
 128      */
 129     private final Map<ModuleElement, SortedSet<PackageElement>> indirectPackages
 130             = new TreeMap<>(utils.makeModuleComparator());
 131 
 132     /**
 133      * Map of indirect modules (transitive closure) and their open packages.
 134      */
 135     private final Map<ModuleElement, SortedSet<PackageElement>> indirectOpenPackages
 136             = new TreeMap<>(utils.makeModuleComparator());
 137 
 138     /**
 139      * Set of services used by the module.
 140      */
 141     private final SortedSet<TypeElement> uses
 142             = new TreeSet<>(utils.makeAllClassesComparator());
 143 
 144     /**
 145      * Map of services used by the module and specified using @uses javadoc tag, and description.
 146      */
 147     private final Map<TypeElement, Content> usesTrees
 148             = new TreeMap<>(utils.makeAllClassesComparator());
 149 
 150     /**
 151      * Map of services provided by this module, and set of its implementations.
 152      */
 153     private final Map<TypeElement, SortedSet<TypeElement>> provides
 154             = new TreeMap<>(utils.makeAllClassesComparator());
 155 
 156     /**
 157      * Map of services provided by the module and specified using @provides javadoc tag, and
 158      * description.
 159      */
 160     private final Map<TypeElement, Content> providesTrees
 161             = new TreeMap<>(utils.makeAllClassesComparator());
 162 
 163     private final Navigation navBar;
 164 
 165     private final BodyContents bodyContents = new BodyContents();
 166 
 167     /**
 168      * Constructor to construct ModuleWriter object and to generate "moduleName-summary.html" file.
 169      *
 170      * @param configuration the configuration of the doclet.
 171      * @param mdle        Module under consideration.
 172      */
 173     public ModuleWriterImpl(HtmlConfiguration configuration, ModuleElement mdle) {
 174         super(configuration, configuration.docPaths.moduleSummary(mdle));
 175         this.mdle = mdle;
 176         this.moduleMode = configuration.docEnv.getModuleMode();
 177         this.navBar = new Navigation(mdle, configuration, PageMode.MODULE, path);
 178         computeModulesData();
 179     }
 180 
 181     /**
 182      * Get the module header.
 183      *
 184      * @param heading the heading for the section
 185      */
 186     @Override
 187     public Content getModuleHeader(String heading) {
 188         HtmlTree bodyTree = getBody(getWindowTitle(mdle.getQualifiedName().toString()));
 189         Content headerContent = new ContentBuilder();
 190         addTop(headerContent);
 191         navBar.setDisplaySummaryModuleDescLink(!utils.getFullBody(mdle).isEmpty() && !configuration.nocomment);
 192         navBar.setDisplaySummaryModulesLink(display(requires) || display(indirectModules));
 193         navBar.setDisplaySummaryPackagesLink(display(packages) || display(indirectPackages)
 194                 || display(indirectOpenPackages));
 195         navBar.setDisplaySummaryServicesLink(displayServices(uses, usesTrees) || displayServices(provides.keySet(), providesTrees));
 196         navBar.setUserHeader(getUserHeaderFooter(true));
 197         headerContent.add(navBar.getContent(true));
 198         HtmlTree div = new HtmlTree(HtmlTag.DIV);
 199         div.setStyle(HtmlStyle.header);
 200         Content annotationContent = new HtmlTree(HtmlTag.P);
 201         addAnnotationInfo(mdle, annotationContent);
 202         div.add(annotationContent);
 203         Content label = mdle.isOpen() && (configuration.docEnv.getModuleMode() == ModuleMode.ALL)
 204                 ? contents.openModuleLabel : contents.moduleLabel;
 205         Content tHeading = HtmlTree.HEADING(Headings.PAGE_TITLE_HEADING, true,
 206                 HtmlStyle.title, label);
 207         tHeading.add(Entity.NO_BREAK_SPACE);
 208         Content moduleHead = new RawHtml(heading);
 209         tHeading.add(moduleHead);
 210         div.add(tHeading);
 211         bodyContents.setHeader(headerContent)
 212                 .addMainContent(div);
 213         return bodyTree;
 214     }
 215 
 216     /**
 217      * Get the content header.
 218      */
 219     @Override
 220     public Content getContentHeader() {
 221         HtmlTree div = new HtmlTree(HtmlTag.DIV);
 222         div.setStyle(HtmlStyle.contentContainer);
 223         return div;
 224     }
 225 
 226     /**
 227      * Get the summary section header.
 228      */
 229     @Override
 230     public Content getSummaryHeader() {
 231         HtmlTree ul = new HtmlTree(HtmlTag.UL);
 232         ul.setStyle(HtmlStyle.blockList);
 233         return ul;
 234     }
 235 
 236     /**
 237      * Get the summary tree.
 238      *
 239      * @param summaryContentTree the content tree to be added to the summary tree.
 240      */
 241     @Override
 242     public Content getSummaryTree(Content summaryContentTree) {
 243         return HtmlTree.SECTION(HtmlStyle.summary, summaryContentTree);
 244     }
 245 
 246     /**
 247      * Compute the modules data that will be displayed in various tables on the module summary page.
 248      */
 249     public void computeModulesData() {
 250         CommentHelper ch = utils.getCommentHelper(mdle);
 251         // Get module dependencies using the module's transitive closure.
 252         Map<ModuleElement, String> dependentModules = utils.getDependentModules(mdle);
 253         // Add all dependent modules to indirect modules set. We will remove the modules,
 254         // listed using the requires directive, from this set to come up with the table of indirect
 255         // required modules.
 256         dependentModules.forEach((module, mod) -> {
 257             if (shouldDocument(module)) {
 258                 indirectModules.put(module, new StringContent(mod));
 259             }
 260         });
 261         (ElementFilter.requiresIn(mdle.getDirectives())).forEach((directive) -> {
 262             ModuleElement m = directive.getDependency();
 263             if (shouldDocument(m)) {
 264                 if (moduleMode == ModuleMode.ALL || directive.isTransitive()) {
 265                     requires.put(m, new StringContent(utils.getModifiers(directive)));
 266             } else {
 267                     // For api mode, just keep the public requires in dependentModules for display of
 268                     // indirect packages in the "Packages" section.
 269                     dependentModules.remove(m);
 270                 }
 271                 indirectModules.remove(m);
 272         }
 273         });
 274 
 275         // Get all packages if module is open or if displaying concealed modules
 276         for (PackageElement pkg : utils.getModulePackageMap().getOrDefault(mdle, Collections.emptySet())) {
 277             if (shouldDocument(pkg) && (mdle.isOpen() || moduleMode == ModuleMode.ALL)) {
 278                 PackageEntry e = new PackageEntry();
 279                 if (mdle.isOpen()) {
 280                     e.openedTo = Collections.emptySet();
 281                 }
 282                 packages.put(pkg, e);
 283             }
 284         }
 285 
 286         // Get all exported packages for the module, using the exports directive for the module.
 287         for (ModuleElement.ExportsDirective directive : ElementFilter.exportsIn(mdle.getDirectives())) {
 288             PackageElement p = directive.getPackage();
 289             if (shouldDocument(p)) {
 290                 List<? extends ModuleElement> targetMdles = directive.getTargetModules();
 291                 // Include package if in details mode, or exported to all (i.e. targetModules == null)
 292                 if (moduleMode == ModuleMode.ALL || targetMdles == null) {
 293                     PackageEntry packageEntry = packages.computeIfAbsent(p, pkg -> new PackageEntry());
 294                     SortedSet<ModuleElement> mdleList = new TreeSet<>(utils.makeModuleComparator());
 295                     if (targetMdles != null) {
 296                         mdleList.addAll(targetMdles);
 297                     }
 298                     packageEntry.exportedTo = mdleList;
 299                 }
 300             }
 301         }
 302 
 303         // Get all opened packages for the module, using the opens directive for the module.
 304         // If it is an open module, there will be no separate opens directives.
 305         for (ModuleElement.OpensDirective directive : ElementFilter.opensIn(mdle.getDirectives())) {
 306             PackageElement p = directive.getPackage();
 307             if (shouldDocument(p)) {
 308                 List<? extends ModuleElement> targetMdles = directive.getTargetModules();
 309                 // Include package if in details mode, or opened to all (i.e. targetModules == null)
 310                 if (moduleMode == ModuleMode.ALL || targetMdles == null) {
 311                     PackageEntry packageEntry = packages.computeIfAbsent(p, pkg -> new PackageEntry());
 312                     SortedSet<ModuleElement> mdleList = new TreeSet<>(utils.makeModuleComparator());
 313                     if (targetMdles != null) {
 314                         mdleList.addAll(targetMdles);
 315                     }
 316                     packageEntry.openedTo = mdleList;
 317                 }
 318             }
 319         }
 320 
 321         // Get all the exported and opened packages, for the transitive closure of the module, to be displayed in
 322         // the indirect packages tables.
 323         dependentModules.forEach((module, mod) -> {
 324             SortedSet<PackageElement> exportPkgList = new TreeSet<>(utils.makePackageComparator());
 325             (ElementFilter.exportsIn(module.getDirectives())).forEach((directive) -> {
 326                 PackageElement pkg = directive.getPackage();
 327                 if (shouldDocument(pkg)) {
 328                     // Qualified exports are not displayed in API mode
 329                     if (moduleMode == ModuleMode.ALL || directive.getTargetModules() == null) {
 330                         exportPkgList.add(pkg);
 331                     }
 332                 }
 333             });
 334             // If none of the indirect modules have exported packages to be displayed, we should not be
 335             // displaying the table and so it should not be added to the map.
 336             if (!exportPkgList.isEmpty()) {
 337                 indirectPackages.put(module, exportPkgList);
 338             }
 339             SortedSet<PackageElement> openPkgList = new TreeSet<>(utils.makePackageComparator());
 340             if (module.isOpen()) {
 341                 openPkgList.addAll(utils.getModulePackageMap().getOrDefault(module, Collections.emptySet()));
 342             } else {
 343                 (ElementFilter.opensIn(module.getDirectives())).forEach((directive) -> {
 344                     PackageElement pkg = directive.getPackage();
 345                     if (shouldDocument(pkg)) {
 346                         // Qualified opens are not displayed in API mode
 347                         if (moduleMode == ModuleMode.ALL || directive.getTargetModules() == null) {
 348                             openPkgList.add(pkg);
 349                         }
 350                     }
 351                 });
 352             }
 353             // If none of the indirect modules have opened packages to be displayed, we should not be
 354             // displaying the table and so it should not be added to the map.
 355             if (!openPkgList.isEmpty()) {
 356                 indirectOpenPackages.put(module, openPkgList);
 357             }
 358         });
 359         // Get all the services listed as uses directive.
 360         (ElementFilter.usesIn(mdle.getDirectives())).forEach((directive) -> {
 361             TypeElement u = directive.getService();
 362             if (shouldDocument(u)) {
 363                 uses.add(u);
 364             }
 365         });
 366         // Get all the services and implementations listed as provides directive.
 367         (ElementFilter.providesIn(mdle.getDirectives())).forEach((directive) -> {
 368             TypeElement u = directive.getService();
 369             if (shouldDocument(u)) {
 370                 List<? extends TypeElement> implList = directive.getImplementations();
 371                 SortedSet<TypeElement> implSet = new TreeSet<>(utils.makeAllClassesComparator());
 372                 implSet.addAll(implList);
 373                 provides.put(u, implSet);
 374             }
 375         });
 376         // Generate the map of all services listed using @provides, and the description.
 377         (utils.getBlockTags(mdle, DocTree.Kind.PROVIDES)).forEach((tree) -> {
 378             TypeElement t = ch.getServiceType(configuration, tree);
 379             if (t != null) {
 380                 providesTrees.put(t, commentTagsToContent(tree, mdle, ch.getDescription(configuration, tree), false, true));
 381             }
 382         });
 383         // Generate the map of all services listed using @uses, and the description.
 384         (utils.getBlockTags(mdle, DocTree.Kind.USES)).forEach((tree) -> {
 385             TypeElement t = ch.getServiceType(configuration, tree);
 386             if (t != null) {
 387                 usesTrees.put(t, commentTagsToContent(tree, mdle, ch.getDescription(configuration, tree), false, true));
 388             }
 389         });
 390     }
 391 
 392     /**
 393      * Returns true if the element should be documented on the module summary page.
 394      *
 395      * @param element the element to be checked
 396      * @return true if the element should be documented
 397      */
 398     public boolean shouldDocument(Element element) {
 399         return (moduleMode == ModuleMode.ALL || utils.isIncluded(element));
 400     }
 401 
 402     /**
 403      * Returns true if there are elements to be displayed.
 404      *
 405      * @param section set of elements
 406      * @return true if there are elements to be displayed
 407      */
 408     public boolean display(Set<? extends Element> section) {
 409         return section != null && !section.isEmpty();
 410     }
 411 
 412     /**
 413      * Returns true if there are elements to be displayed.
 414      *
 415      * @param section map of elements.
 416      * @return true if there are elements to be displayed
 417      */
 418     public boolean display(Map<? extends Element, ?> section) {
 419         return section != null && !section.isEmpty();
 420     }
 421 
 422     /*
 423      * Returns true, in API mode, if at least one type element in
 424      * the typeElements set is referenced by a javadoc tag in tagsMap.
 425      */
 426     private boolean displayServices(Set<TypeElement> typeElements,
 427                                     Map<TypeElement, Content> tagsMap) {
 428         return typeElements != null &&
 429                 typeElements.stream().anyMatch((v) -> displayServiceDirective(v, tagsMap));
 430     }
 431 
 432     /*
 433      * Returns true, in API mode, if the type element is referenced
 434      * from a javadoc tag in tagsMap.
 435      */
 436     private boolean displayServiceDirective(TypeElement typeElement,
 437                                             Map<TypeElement, Content> tagsMap) {
 438         return moduleMode == ModuleMode.ALL || tagsMap.containsKey(typeElement);
 439     }
 440 
 441     /**
 442      * Add the summary header.
 443      *
 444      * @param startMarker the marker comment
 445      * @param heading the heading for the section
 446      * @param htmltree the content tree to which the information is added
 447      */
 448     public void addSummaryHeader(Content startMarker, Content heading,
 449             Content htmltree) {
 450         htmltree.add(startMarker);
 451         htmltree.add(HtmlTree.HEADING(Headings.ModuleDeclaration.SUMMARY_HEADING, heading));
 452     }
 453 
 454     /**
 455      * Get a table, with two columns.
 456      *
 457      * @param caption the table caption
 458      * @param tableStyle the table style
 459      * @param tableHeader the table header
 460      * @return a content object
 461      */
 462     private Table getTable2(Content caption, HtmlStyle tableStyle,
 463             TableHeader tableHeader) {
 464         return new Table(tableStyle)
 465                 .setCaption(caption)
 466                 .setHeader(tableHeader)
 467                 .setColumnStyles(HtmlStyle.colFirst, HtmlStyle.colLast);
 468     }
 469 
 470     /**
 471      * Get a table, with three columns, with the second column being the defining column.
 472      *
 473      * @param caption the table caption
 474      * @param tableSummary the summary for the table
 475      * @param tableStyle the table style
 476      * @param tableHeader the table header
 477      * @return a content object
 478      */
 479     private Table getTable3(Content caption, String tableSummary, HtmlStyle tableStyle,
 480             TableHeader tableHeader) {
 481         return new Table(tableStyle)
 482                 .setCaption(caption)
 483                 .setHeader(tableHeader)
 484                 .setRowScopeColumn(1)
 485                 .setColumnStyles(HtmlStyle.colFirst, HtmlStyle.colSecond, HtmlStyle.colLast);
 486     }
 487 
 488     /**
 489      * {@inheritDoc}
 490      */
 491     @Override
 492     public void addModulesSummary(Content summaryContentTree) {
 493         if (display(requires) || display(indirectModules)) {
 494             TableHeader requiresTableHeader =
 495                     new TableHeader(contents.modifierLabel, contents.moduleLabel,
 496                             contents.descriptionLabel);
 497             HtmlTree section = HtmlTree.SECTION(HtmlStyle.modulesSummary)
 498                     .setId(SectionName.MODULES.getName());
 499             addSummaryHeader(MarkerComments.START_OF_MODULES_SUMMARY, contents.navModules, section);
 500             if (display(requires)) {
 501                 String text = resources.getText("doclet.Requires_Summary");
 502                 String tableSummary = resources.getText("doclet.Member_Table_Summary",
 503                         text,
 504                         resources.getText("doclet.modules"));
 505                 Content caption = getTableCaption(new StringContent(text));
 506                 Table table = getTable3(caption, tableSummary, HtmlStyle.requiresSummary,
 507                             requiresTableHeader);
 508                 addModulesList(requires, table);
 509                 section.add(table.toContent());
 510             }
 511             // Display indirect modules table in both "api" and "all" mode.
 512             if (display(indirectModules)) {
 513                 String amrText = resources.getText("doclet.Indirect_Requires_Summary");
 514                 String amrTableSummary = resources.getText("doclet.Member_Table_Summary",
 515                         amrText,
 516                         resources.getText("doclet.modules"));
 517                 Content amrCaption = getTableCaption(new StringContent(amrText));
 518                 Table amrTable = getTable3(amrCaption, amrTableSummary, HtmlStyle.requiresSummary,
 519                             requiresTableHeader);
 520                 addModulesList(indirectModules, amrTable);
 521                 section.add(amrTable.toContent());
 522             }
 523             summaryContentTree.add(HtmlTree.LI(HtmlStyle.blockList, section));
 524         }
 525     }
 526 
 527     /**
 528      * Add the list of modules.
 529      *
 530      * @param mdleMap map of modules and modifiers
 531      * @param table the table to which the list will be added
 532      */
 533     private void addModulesList(Map<ModuleElement, Content> mdleMap, Table table) {
 534         for (ModuleElement m : mdleMap.keySet()) {
 535             Content modifiers = mdleMap.get(m);
 536             Content moduleLink = getModuleLink(m, new StringContent(m.getQualifiedName()));
 537             Content moduleSummary = new ContentBuilder();
 538             addSummaryComment(m, moduleSummary);
 539             table.addRow(modifiers, moduleLink, moduleSummary);
 540         }
 541     }
 542 
 543     @Override
 544     public void addPackagesSummary(Content summaryContentTree) {
 545         if (display(packages)
 546                 || display(indirectPackages) || display(indirectOpenPackages)) {
 547             HtmlTree section = HtmlTree.SECTION(HtmlStyle.packagesSummary)
 548                     .setId(SectionName.PACKAGES.getName());;
 549             addSummaryHeader(MarkerComments.START_OF_PACKAGES_SUMMARY, contents.navPackages, section);
 550             if (display(packages)) {
 551                 addPackageSummary(section);
 552             }
 553             TableHeader indirectPackagesHeader =
 554                     new TableHeader(contents.fromLabel, contents.packagesLabel);
 555             if (display(indirectPackages)) {
 556                 String aepText = resources.getText("doclet.Indirect_Exports_Summary");
 557                 Table aepTable = getTable2(new StringContent(aepText),
 558                         HtmlStyle.packagesSummary, indirectPackagesHeader);
 559                 addIndirectPackages(aepTable, indirectPackages);
 560                 section.add(aepTable.toContent());
 561             }
 562             if (display(indirectOpenPackages)) {
 563                 String aopText = resources.getText("doclet.Indirect_Opens_Summary");
 564                 Table aopTable = getTable2(new StringContent(aopText), HtmlStyle.packagesSummary,
 565                         indirectPackagesHeader);
 566                 addIndirectPackages(aopTable, indirectOpenPackages);
 567                 section.add(aopTable.toContent());
 568             }
 569             summaryContentTree.add(HtmlTree.LI(HtmlStyle.blockList, section));
 570         }
 571     }
 572 
 573     /**
 574      * Add the package summary for the module.
 575      *
 576      * @param li
 577      */
 578     public void addPackageSummary(HtmlTree li) {
 579         Table table = new Table(HtmlStyle.packagesSummary)
 580                 .setDefaultTab(resources.getText("doclet.All_Packages"))
 581                 .addTab(resources.getText("doclet.Exported_Packages_Summary"), this::isExported)
 582                 .addTab(resources.getText("doclet.Opened_Packages_Summary"), this::isOpened)
 583                 .addTab(resources.getText("doclet.Concealed_Packages_Summary"), this::isConcealed)
 584                 .setTabScript(i -> String.format("show(%d);", i));
 585 
 586         // Determine whether to show the "Exported To" and "Opened To" columns,
 587         // based on whether such columns would provide "useful" info.
 588         int numExports = 0;
 589         int numUnqualifiedExports = 0;
 590         int numOpens = 0;
 591         int numUnqualifiedOpens = 0;
 592 
 593         for (PackageEntry e : packages.values()) {
 594             if (e.exportedTo != null) {
 595                 numExports++;
 596                 if (e.exportedTo.isEmpty()) {
 597                     numUnqualifiedExports++;
 598                 }
 599             }
 600             if (e.openedTo != null) {
 601                 numOpens++;
 602                 if (e.openedTo.isEmpty()) {
 603                     numUnqualifiedOpens++;
 604                 }
 605             }
 606         }
 607 
 608         boolean showExportedTo = numExports > 0 && (numOpens > 0   || numUnqualifiedExports < packages.size());
 609         boolean showOpenedTo   = numOpens > 0   && (numExports > 0 || numUnqualifiedOpens < packages.size());
 610 
 611         // Create the table header and column styles.
 612         List<Content> colHeaders = new ArrayList<>();
 613         List<HtmlStyle> colStyles = new ArrayList<>();
 614         colHeaders.add(contents.packageLabel);
 615         colStyles.add(HtmlStyle.colFirst);
 616 
 617         if (showExportedTo) {
 618             colHeaders.add(contents.exportedTo);
 619             colStyles.add(HtmlStyle.colSecond);
 620         }
 621 
 622         if (showOpenedTo) {
 623             colHeaders.add(contents.openedTo);
 624             colStyles.add(HtmlStyle.colSecond);
 625         }
 626 
 627         colHeaders.add(contents.descriptionLabel);
 628         colStyles.add(HtmlStyle.colLast);
 629 
 630         table.setHeader(new TableHeader(colHeaders).styles(colStyles))
 631                 .setColumnStyles(colStyles);
 632 
 633         // Add the table rows, based on the "packages" map.
 634         for (Map.Entry<PackageElement, PackageEntry> e : packages.entrySet()) {
 635             PackageElement pkg = e.getKey();
 636             PackageEntry entry = e.getValue();
 637             List<Content> row = new ArrayList<>();
 638             Content pkgLinkContent = getPackageLink(pkg, new StringContent(utils.getPackageName(pkg)));
 639             row.add(pkgLinkContent);
 640 
 641             if (showExportedTo) {
 642                 row.add(getPackageExportOpensTo(entry.exportedTo));
 643             }
 644             if (showOpenedTo) {
 645                 row.add(getPackageExportOpensTo(entry.openedTo));
 646             }
 647             Content summary = new ContentBuilder();
 648             addSummaryComment(pkg, summary);
 649             row.add(summary);
 650 
 651             table.addRow(pkg, row);
 652         }
 653 
 654         li.add(table.toContent());
 655         if (table.needsScript()) {
 656             mainBodyScript.append(table.getScript());
 657         }
 658     }
 659 
 660     private boolean isExported(Element e) {
 661         PackageEntry entry = packages.get((PackageElement) e);
 662         return (entry != null) && (entry.exportedTo != null);
 663     }
 664 
 665     private boolean isOpened(Element e) {
 666         PackageEntry entry = packages.get((PackageElement) e);
 667         return (entry != null) && (entry.openedTo != null);
 668     }
 669 
 670     private boolean isConcealed(Element e) {
 671         PackageEntry entry = packages.get((PackageElement) e);
 672         return (entry != null) && (entry.exportedTo == null) && (entry.openedTo == null);
 673     }
 674 
 675     private Content getPackageExportOpensTo(Set<ModuleElement> modules) {
 676         if (modules == null) {
 677             return new StringContent(resources.getText("doclet.None"));
 678         } else if (modules.isEmpty()) {
 679             return new StringContent(resources.getText("doclet.All_Modules"));
 680         } else {
 681             Content list = new ContentBuilder();
 682             for (ModuleElement m : modules) {
 683                 if (!list.isEmpty()) {
 684                     list.add(new StringContent(", "));
 685                 }
 686                 list.add(getModuleLink(m, new StringContent(m.getQualifiedName())));
 687             }
 688             return list;
 689         }
 690     }
 691 
 692     /**
 693      * Add the indirect packages for the module being documented.
 694      *
 695      * @param table the table to which the content rows will be added
 696      * @param ip indirect packages to be added
 697      */
 698     public void addIndirectPackages(Table table, Map<ModuleElement, SortedSet<PackageElement>> ip) {
 699         for (Map.Entry<ModuleElement, SortedSet<PackageElement>> entry : ip.entrySet()) {
 700             ModuleElement m = entry.getKey();
 701             SortedSet<PackageElement> pkgList = entry.getValue();
 702             Content moduleLinkContent = getModuleLink(m, new StringContent(m.getQualifiedName()));
 703             Content list = new ContentBuilder();
 704             String sep = "";
 705             for (PackageElement pkg : pkgList) {
 706                 list.add(sep);
 707                 list.add(getPackageLink(pkg, new StringContent(utils.getPackageName(pkg))));
 708                 sep = " ";
 709             }
 710             table.addRow(moduleLinkContent, list);
 711         }
 712     }
 713 
 714     /**
 715      * {@inheritDoc}
 716      */
 717     @Override
 718     public void addServicesSummary(Content summaryContentTree) {
 719 
 720         boolean haveUses = displayServices(uses, usesTrees);
 721         boolean haveProvides = displayServices(provides.keySet(), providesTrees);
 722 
 723         if (haveProvides || haveUses) {
 724             HtmlTree section = HtmlTree.SECTION(HtmlStyle.servicesSummary)
 725                     .setId(SectionName.SERVICES.getName());
 726             addSummaryHeader(MarkerComments.START_OF_SERVICES_SUMMARY, contents.navServices, section);
 727             TableHeader usesProvidesTableHeader =
 728                     new TableHeader(contents.typeLabel, contents.descriptionLabel);
 729             if (haveProvides) {
 730                 String label = resources.getText("doclet.Provides_Summary");
 731                 Table table = getTable2(new StringContent(label), HtmlStyle.providesSummary,
 732                         usesProvidesTableHeader);
 733                 addProvidesList(table);
 734                 if (!table.isEmpty()) {
 735                     section.add(table.toContent());
 736                 }
 737             }
 738             if (haveUses){
 739                 String label = resources.getText("doclet.Uses_Summary");
 740                 Table table = getTable2(new StringContent(label), HtmlStyle.usesSummary,
 741                         usesProvidesTableHeader);
 742                 addUsesList(table);
 743                 if (!table.isEmpty()) {
 744                     section.add(table.toContent());
 745                 }
 746             }
 747             summaryContentTree.add(HtmlTree.LI(HtmlStyle.blockList, section));
 748         }
 749     }
 750 
 751     /**
 752      * Add the uses list for the module.
 753      *
 754      * @param table the table to which the services used will be added
 755      */
 756     public void addUsesList(Table table) {
 757         Content typeLinkContent;
 758         Content description;
 759         for (TypeElement t : uses) {
 760             if (!displayServiceDirective(t, usesTrees)) {
 761                 continue;
 762             }
 763             typeLinkContent = getLink(new LinkInfoImpl(configuration, LinkInfoImpl.Kind.PACKAGE, t));
 764             Content summary = new ContentBuilder();
 765             if (display(usesTrees)) {
 766                 description = usesTrees.get(t);
 767                 if (description != null && !description.isEmpty()) {
 768                     summary.add(HtmlTree.DIV(HtmlStyle.block, description));
 769                 } else {
 770                     addSummaryComment(t, summary);
 771                 }
 772             } else {
 773                 summary.add(Entity.NO_BREAK_SPACE);
 774             }
 775             table.addRow(typeLinkContent, summary);
 776         }
 777     }
 778 
 779     /**
 780      * Add the provides list for the module.
 781      *
 782      * @param table the table to which the services provided will be added
 783      */
 784     public void addProvidesList(Table table) {
 785         SortedSet<TypeElement> implSet;
 786         Content description;
 787         for (Map.Entry<TypeElement, SortedSet<TypeElement>> entry : provides.entrySet()) {
 788             TypeElement srv = entry.getKey();
 789             if (!displayServiceDirective(srv, providesTrees)) {
 790                 continue;
 791             }
 792             implSet = entry.getValue();
 793             Content srvLinkContent = getLink(new LinkInfoImpl(configuration, LinkInfoImpl.Kind.PACKAGE, srv));
 794             Content desc = new ContentBuilder();
 795             if (display(providesTrees)) {
 796                 description = providesTrees.get(srv);
 797                 desc.add((description != null && !description.isEmpty())
 798                         ? HtmlTree.DIV(HtmlStyle.block, description)
 799                         : Entity.NO_BREAK_SPACE);
 800             } else {
 801                 desc.add(Entity.NO_BREAK_SPACE);
 802                 }
 803             // Only display the implementation details in the "all" mode.
 804             if (moduleMode == ModuleMode.ALL && !implSet.isEmpty()) {
 805                 desc.add(new HtmlTree(HtmlTag.BR));
 806                 desc.add("(");
 807                 HtmlTree implSpan = HtmlTree.SPAN(HtmlStyle.implementationLabel, contents.implementation);
 808                 desc.add(implSpan);
 809                 desc.add(Entity.NO_BREAK_SPACE);
 810                 String sep = "";
 811                 for (TypeElement impl : implSet) {
 812                     desc.add(sep);
 813                     desc.add(getLink(new LinkInfoImpl(configuration, LinkInfoImpl.Kind.PACKAGE, impl)));
 814                     sep = ", ";
 815                 }
 816                 desc.add(")");
 817             }
 818             table.addRow(srvLinkContent, desc);
 819         }
 820     }
 821 
 822     /**
 823      * Add the module deprecation information to the documentation tree.
 824      *
 825      * @param div the content tree to which the deprecation information will be added
 826      */
 827     public void addDeprecationInfo(Content div) {
 828         List<? extends DocTree> deprs = utils.getBlockTags(mdle, DocTree.Kind.DEPRECATED);
 829         if (utils.isDeprecated(mdle)) {
 830             CommentHelper ch = utils.getCommentHelper(mdle);
 831             HtmlTree deprDiv = new HtmlTree(HtmlTag.DIV);
 832             deprDiv.setStyle(HtmlStyle.deprecationBlock);
 833             Content deprPhrase = HtmlTree.SPAN(HtmlStyle.deprecatedLabel, getDeprecatedPhrase(mdle));
 834             deprDiv.add(deprPhrase);
 835             if (!deprs.isEmpty()) {
 836                 List<? extends DocTree> commentTags = ch.getDescription(configuration, deprs.get(0));
 837                 if (!commentTags.isEmpty()) {
 838                     addInlineDeprecatedComment(mdle, deprs.get(0), deprDiv);
 839                 }
 840             }
 841             div.add(deprDiv);
 842         }
 843     }
 844 
 845     /**
 846      * {@inheritDoc}
 847      */
 848     @Override
 849     public void addModuleDescription(Content moduleContentTree) {
 850         if (!utils.getFullBody(mdle).isEmpty()) {
 851             HtmlTree tree = HtmlTree.SECTION(HtmlStyle.moduleDescription);
 852             tree.setId(SectionName.MODULE_DESCRIPTION.getName());
 853             addDeprecationInfo(tree);
 854             tree.add(MarkerComments.START_OF_MODULE_DESCRIPTION);
 855             addInlineComment(mdle, tree);
 856             moduleContentTree.add(tree);
 857         }
 858     }
 859 
 860     /**
 861      * {@inheritDoc}
 862      */
 863     @Override
 864     public void addModuleTags(Content moduleContentTree) {
 865         Content tree = HtmlTree.SECTION(HtmlStyle.moduleTags);
 866         addTagsInfo(mdle, tree);
 867         moduleContentTree.add(tree);
 868     }
 869 
 870     /**
 871      * {@inheritDoc}
 872      */
 873     @Override
 874     public void addModuleContent(Content moduleContentTree) {
 875         bodyContents.addMainContent(moduleContentTree);
 876     }
 877 
 878     /**
 879      * {@inheritDoc}
 880      */
 881     @Override
 882     public void addModuleFooter() {
 883         Content htmlTree = HtmlTree.FOOTER();
 884         navBar.setUserFooter(getUserHeaderFooter(false));
 885         htmlTree.add(navBar.getContent(false));
 886         addBottom(htmlTree);
 887         bodyContents.setFooter(htmlTree);
 888     }
 889 
 890     /**
 891      * {@inheritDoc}
 892      *
 893      * @throws jdk.javadoc.internal.doclets.toolkit.util.DocFileIOException
 894      */
 895     @Override
 896     public void printDocument(Content contentTree) throws DocFileIOException {
 897         contentTree.add(bodyContents.toContent());
 898         printHtmlDocument(configuration.metakeywords.getMetaKeywordsForModule(mdle),
 899                 getDescription("declaration", mdle), getLocalStylesheets(mdle), contentTree);
 900     }
 901 
 902     /**
 903      * Add the module package deprecation information to the documentation tree.
 904      *
 905      * @param li the content tree to which the deprecation information will be added
 906      * @param pkg the PackageDoc that is added
 907      */
 908     public void addPackageDeprecationInfo(Content li, PackageElement pkg) {
 909         List<? extends DocTree> deprs;
 910         if (utils.isDeprecated(pkg)) {
 911             deprs = utils.getDeprecatedTrees(pkg);
 912             HtmlTree deprDiv = new HtmlTree(HtmlTag.DIV);
 913             deprDiv.setStyle(HtmlStyle.deprecationBlock);
 914             Content deprPhrase = HtmlTree.SPAN(HtmlStyle.deprecatedLabel, getDeprecatedPhrase(pkg));
 915             deprDiv.add(deprPhrase);
 916             if (!deprs.isEmpty()) {
 917                 CommentHelper ch = utils.getCommentHelper(pkg);
 918                 List<? extends DocTree> commentTags = ch.getDescription(configuration, deprs.get(0));
 919                 if (!commentTags.isEmpty()) {
 920                     addInlineDeprecatedComment(pkg, deprs.get(0), deprDiv);
 921                 }
 922             }
 923             li.add(deprDiv);
 924         }
 925     }
 926 }