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