001/*
002 * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.  Oracle designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Oracle in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
022 * or visit www.oracle.com if you need additional information or have any
023 * questions.
024 */
025
026package org.jdrupes.mdoclet.internal.doclets.formats.html;
027
028import java.util.ArrayList;
029import java.util.List;
030import java.util.Map;
031import java.util.Set;
032import java.util.SortedSet;
033import java.util.TreeMap;
034import java.util.TreeSet;
035
036import javax.lang.model.element.Element;
037import javax.lang.model.element.ModuleElement;
038import javax.lang.model.element.PackageElement;
039import javax.lang.model.element.TypeElement;
040import javax.lang.model.util.ElementFilter;
041
042import org.jdrupes.mdoclet.internal.doclets.formats.html.Navigation.PageMode;
043import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.BodyContents;
044import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.ContentBuilder;
045import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.Entity;
046import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.HtmlStyle;
047import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.HtmlTree;
048import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.TagName;
049import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.Text;
050import org.jdrupes.mdoclet.internal.doclets.toolkit.Content;
051import org.jdrupes.mdoclet.internal.doclets.toolkit.ModuleSummaryWriter;
052import org.jdrupes.mdoclet.internal.doclets.toolkit.util.CommentHelper;
053import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocFileIOException;
054
055import com.sun.source.doctree.DeprecatedTree;
056import com.sun.source.doctree.DocTree;
057import jdk.javadoc.doclet.DocletEnvironment.ModuleMode;
058
059/**
060 * Class to generate file for each module contents in the right-hand frame. This will list all the
061 * required modules, packages and service types for the module. A click on any of the links will update
062 * the frame with the clicked element page.
063 */
064public class ModuleWriterImpl extends HtmlDocletWriter
065        implements ModuleSummaryWriter {
066
067    /**
068     * The module being documented.
069     */
070    protected ModuleElement mdle;
071
072    /**
073     * The module mode for this javadoc run. It can be set to "api" or "all".
074     */
075    private final ModuleMode moduleMode;
076
077    /**
078     * Map of module elements and modifiers required by this module.
079     */
080    private final Map<ModuleElement, Content> requires
081        = new TreeMap<>(comparators.makeModuleComparator());
082
083    /**
084     * Map of indirect modules and modifiers, transitive closure, required by this module.
085     */
086    private final Map<ModuleElement, Content> indirectModules
087        = new TreeMap<>(comparators.makeModuleComparator());
088
089    /**
090     * Details about a package in a module.
091     * A package may be not exported, or exported to some modules, or exported to all modules.
092     * A package may be not opened, or opened to some modules, or opened to all modules.
093     * A package that is neither exported or opened to any modules is a concealed package.
094     * An open module opens all its packages to all modules.
095     */
096    class PackageEntry {
097        /**
098         * Summary of package exports:
099         * If null, the package is not exported to any modules;
100         * if empty, the package is exported to all modules;
101         * otherwise, the package is exported to these modules.
102         */
103        Set<ModuleElement> exportedTo;
104
105        /**
106         * Summary of package opens:
107         * If null, the package is not opened to any modules;
108         * if empty, the package is opened to all modules;
109         * otherwise, the package is opened to these modules.
110         */
111        Set<ModuleElement> openedTo;
112    }
113
114    /**
115     * Map of packages of this module, and details of whether they are exported or opened.
116     */
117    private final Map<PackageElement, PackageEntry> packages
118        = new TreeMap<>(utils.comparators.makePackageComparator());
119
120    /**
121     * Map of indirect modules (transitive closure) and their exported packages.
122     */
123    private final Map<ModuleElement, SortedSet<PackageElement>> indirectPackages
124        = new TreeMap<>(comparators.makeModuleComparator());
125
126    /**
127     * Map of indirect modules (transitive closure) and their open packages.
128     */
129    private final Map<ModuleElement,
130            SortedSet<PackageElement>> indirectOpenPackages
131                = new TreeMap<>(comparators.makeModuleComparator());
132
133    /**
134     * Set of services used by the module.
135     */
136    private final SortedSet<TypeElement> uses
137        = new TreeSet<>(comparators.makeAllClassesComparator());
138
139    /**
140     * Map of services used by the module and specified using @uses javadoc tag, and description.
141     */
142    private final Map<TypeElement, Content> usesTrees
143        = new TreeMap<>(comparators.makeAllClassesComparator());
144
145    /**
146     * Map of services provided by this module, and set of its implementations.
147     */
148    private final Map<TypeElement, SortedSet<TypeElement>> provides
149        = new TreeMap<>(comparators.makeAllClassesComparator());
150
151    /**
152     * Map of services provided by the module and specified using @provides javadoc tag, and
153     * description.
154     */
155    private final Map<TypeElement, Content> providesTrees
156        = new TreeMap<>(comparators.makeAllClassesComparator());
157
158    private final BodyContents bodyContents = new BodyContents();
159
160    /**
161     * Constructor to construct ModuleWriter object and to generate "moduleName-summary.html" file.
162     *
163     * @param configuration the configuration of the doclet.
164     * @param mdle        Module under consideration.
165     */
166    public ModuleWriterImpl(HtmlConfiguration configuration,
167            ModuleElement mdle) {
168        super(configuration, configuration.docPaths.moduleSummary(mdle));
169        this.mdle = mdle;
170        this.moduleMode = configuration.docEnv.getModuleMode();
171        computeModulesData();
172    }
173
174    @Override
175    public Content getModuleHeader(String heading) {
176        HtmlTree body
177            = getBody(getWindowTitle(mdle.getQualifiedName().toString()));
178        var div = HtmlTree.DIV(HtmlStyle.header);
179        Content moduleHead = new ContentBuilder();
180        moduleHead.add(mdle.isOpen()
181            && (configuration.docEnv.getModuleMode() == ModuleMode.ALL)
182                ? contents.openModuleLabel
183                : contents.moduleLabel);
184        moduleHead.add(" ").add(heading);
185        var tHeading = HtmlTree.HEADING_TITLE(Headings.PAGE_TITLE_HEADING,
186            HtmlStyle.title, moduleHead);
187        div.add(tHeading);
188        bodyContents.setHeader(getHeader(PageMode.MODULE, mdle))
189            .addMainContent(div);
190        return body;
191    }
192
193    @Override
194    protected Navigation getNavBar(PageMode pageMode, Element element) {
195        return super.getNavBar(pageMode, element)
196            .setSubNavLinks(() -> List.of(
197                links.createLink(HtmlIds.MODULE_DESCRIPTION,
198                    contents.navDescription,
199                    !utils.getFullBody(mdle).isEmpty() && !options.noComment()),
200                links.createLink(HtmlIds.MODULES, contents.navModules,
201                    display(requires) || display(indirectModules)),
202                links.createLink(HtmlIds.PACKAGES, contents.navPackages,
203                    display(packages) || display(indirectPackages)
204                        || display(indirectOpenPackages)),
205                links.createLink(HtmlIds.SERVICES, contents.navServices,
206                    displayServices(uses, usesTrees)
207                        || displayServices(provides.keySet(), providesTrees))));
208    }
209
210    @Override
211    public Content getContentHeader() {
212        return new ContentBuilder();
213    }
214
215    @Override
216    public Content getSummariesList() {
217        return HtmlTree.UL(HtmlStyle.summaryList);
218    }
219
220    @Override
221    public Content getSummary(Content source) {
222        return HtmlTree.SECTION(HtmlStyle.summary, source);
223    }
224
225    /**
226     * Compute the modules data that will be displayed in various tables on the module summary page.
227     */
228    public void computeModulesData() {
229        CommentHelper ch = utils.getCommentHelper(mdle);
230        // Get module dependencies using the module's transitive closure.
231        Map<ModuleElement, String> dependentModules
232            = utils.getDependentModules(mdle);
233        // Add all dependent modules to indirect modules set. We will remove the
234        // modules,
235        // listed using the requires directive, from this set to come up with
236        // the table of indirect
237        // required modules.
238        dependentModules.forEach((module, mod) -> {
239            if (shouldDocument(module)) {
240                indirectModules.put(module, Text.of(mod));
241            }
242        });
243        ElementFilter.requiresIn(mdle.getDirectives()).forEach(directive -> {
244            ModuleElement m = directive.getDependency();
245            if (shouldDocument(m)) {
246                if (moduleMode == ModuleMode.ALL || directive.isTransitive()) {
247                    requires.put(m, Text.of(utils.getModifiers(directive)));
248                } else {
249                    // For api mode, just keep the public requires in
250                    // dependentModules for display of
251                    // indirect packages in the "Packages" section.
252                    dependentModules.remove(m);
253                }
254                indirectModules.remove(m);
255            }
256        });
257
258        // Get all packages if module is open or if displaying concealed modules
259        for (PackageElement pkg : utils.getModulePackageMap().getOrDefault(mdle,
260            Set.of())) {
261            if (shouldDocument(pkg)
262                && (mdle.isOpen() || moduleMode == ModuleMode.ALL)) {
263                PackageEntry e = new PackageEntry();
264                if (mdle.isOpen()) {
265                    e.openedTo = Set.of();
266                }
267                packages.put(pkg, e);
268            }
269        }
270
271        // Get all exported packages for the module, using the exports directive
272        // for the module.
273        for (ModuleElement.ExportsDirective directive : ElementFilter
274            .exportsIn(mdle.getDirectives())) {
275            PackageElement p = directive.getPackage();
276            if (shouldDocument(p)) {
277                List<? extends ModuleElement> targetMdles
278                    = directive.getTargetModules();
279                // Include package if in details mode, or exported to all (i.e.
280                // targetModules == null)
281                if (moduleMode == ModuleMode.ALL || targetMdles == null) {
282                    PackageEntry packageEntry = packages.computeIfAbsent(p,
283                        pkg -> new PackageEntry());
284                    SortedSet<ModuleElement> mdleList = new TreeSet<>(
285                        utils.comparators.makeModuleComparator());
286                    if (targetMdles != null) {
287                        mdleList.addAll(targetMdles);
288                    }
289                    packageEntry.exportedTo = mdleList;
290                }
291            }
292        }
293
294        // Get all opened packages for the module, using the opens directive for
295        // the module.
296        // If it is an open module, there will be no separate opens directives.
297        for (ModuleElement.OpensDirective directive : ElementFilter
298            .opensIn(mdle.getDirectives())) {
299            PackageElement p = directive.getPackage();
300            if (shouldDocument(p)) {
301                List<? extends ModuleElement> targetMdles
302                    = directive.getTargetModules();
303                // Include package if in details mode, or opened to all (i.e.
304                // targetModules == null)
305                if (moduleMode == ModuleMode.ALL || targetMdles == null) {
306                    PackageEntry packageEntry = packages.computeIfAbsent(p,
307                        pkg -> new PackageEntry());
308                    SortedSet<ModuleElement> mdleList = new TreeSet<>(
309                        utils.comparators.makeModuleComparator());
310                    if (targetMdles != null) {
311                        mdleList.addAll(targetMdles);
312                    }
313                    packageEntry.openedTo = mdleList;
314                }
315            }
316        }
317
318        // Get all the exported and opened packages, for the transitive closure
319        // of the module, to be displayed in
320        // the indirect packages tables.
321        dependentModules.forEach((module, mod) -> {
322            SortedSet<PackageElement> exportedPackages
323                = new TreeSet<>(utils.comparators.makePackageComparator());
324            ElementFilter.exportsIn(module.getDirectives())
325                .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
330                            || directive.getTargetModules() == null) {
331                            exportedPackages.add(pkg);
332                        }
333                    }
334                });
335            // If none of the indirect modules have exported packages to be
336            // displayed, we should not be
337            // displaying the table and so it should not be added to the map.
338            if (!exportedPackages.isEmpty()) {
339                indirectPackages.put(module, exportedPackages);
340            }
341            SortedSet<PackageElement> openPackages
342                = new TreeSet<>(utils.comparators.makePackageComparator());
343            if (module.isOpen()) {
344                openPackages.addAll(
345                    utils.getModulePackageMap().getOrDefault(module, Set.of()));
346            } else {
347                ElementFilter.opensIn(module.getDirectives())
348                    .forEach(directive -> {
349                        PackageElement pkg = directive.getPackage();
350                        if (shouldDocument(pkg)) {
351                            // Qualified opens are not displayed in API mode
352                            if (moduleMode == ModuleMode.ALL
353                                || directive.getTargetModules() == null) {
354                                openPackages.add(pkg);
355                            }
356                        }
357                    });
358            }
359            // If none of the indirect modules have opened packages to be
360            // displayed, we should not be
361            // displaying the table and so it should not be added to the map.
362            if (!openPackages.isEmpty()) {
363                indirectOpenPackages.put(module, openPackages);
364            }
365        });
366        // Get all the services listed as uses directive.
367        ElementFilter.usesIn(mdle.getDirectives()).forEach(directive -> {
368            TypeElement u = directive.getService();
369            if (shouldDocument(u)) {
370                uses.add(u);
371            }
372        });
373        // Get all the services and implementations listed as provides
374        // directive.
375        ElementFilter.providesIn(mdle.getDirectives()).forEach(directive -> {
376            TypeElement u = directive.getService();
377            if (shouldDocument(u)) {
378                List<? extends TypeElement> implList
379                    = directive.getImplementations();
380                SortedSet<TypeElement> implSet = new TreeSet<>(
381                    utils.comparators.makeAllClassesComparator());
382                implSet.addAll(implList);
383                provides.put(u, implSet);
384            }
385        });
386        // Generate the map of all services listed using @provides, and the
387        // description.
388        utils.getProvidesTrees(mdle).forEach(tree -> {
389            TypeElement t = ch.getServiceType(tree);
390            if (t != null) {
391                providesTrees.put(t, commentTagsToContent(mdle,
392                    ch.getDescription(tree), false, true));
393            }
394        });
395        // Generate the map of all services listed using @uses, and the
396        // description.
397        utils.getUsesTrees(mdle).forEach(tree -> {
398            TypeElement t = ch.getServiceType(tree);
399            if (t != null) {
400                usesTrees.put(t, commentTagsToContent(mdle,
401                    ch.getDescription(tree), false, true));
402            }
403        });
404    }
405
406    /**
407     * Returns true if the element should be documented on the module summary page.
408     *
409     * @param element the element to be checked
410     * @return true if the element should be documented
411     */
412    public boolean shouldDocument(Element element) {
413        return (moduleMode == ModuleMode.ALL || utils.isIncluded(element));
414    }
415
416    /**
417     * Returns true if there are elements to be displayed.
418     *
419     * @param section set of elements
420     * @return true if there are elements to be displayed
421     */
422    public boolean display(Set<? extends Element> section) {
423        return section != null && !section.isEmpty();
424    }
425
426    /**
427     * Returns true if there are elements to be displayed.
428     *
429     * @param section map of elements.
430     * @return true if there are elements to be displayed
431     */
432    public boolean display(Map<? extends Element, ?> section) {
433        return section != null && !section.isEmpty();
434    }
435
436    /*
437     * Returns true, in API mode, if at least one type element in
438     * the typeElements set is referenced by a javadoc tag in tagsMap.
439     */
440    private boolean displayServices(Set<TypeElement> typeElements,
441            Map<TypeElement, Content> tagsMap) {
442        return typeElements != null &&
443            typeElements.stream()
444                .anyMatch(v -> displayServiceDirective(v, tagsMap));
445    }
446
447    /*
448     * Returns true, in API mode, if the type element is referenced
449     * from a javadoc tag in tagsMap.
450     */
451    private boolean displayServiceDirective(TypeElement typeElement,
452            Map<TypeElement, Content> tagsMap) {
453        return moduleMode == ModuleMode.ALL || tagsMap.containsKey(typeElement);
454    }
455
456    /**
457     * Add the summary header.
458     *
459     * @param startMarker the marker comment
460     * @param heading the heading for the section
461     * @param target the content to which the information is added
462     */
463    public void addSummaryHeader(Content startMarker, Content heading,
464            Content target) {
465        target.add(startMarker);
466        target.add(HtmlTree.HEADING(Headings.ModuleDeclaration.SUMMARY_HEADING,
467            heading));
468    }
469
470    /**
471     * Get a table, with two columns.
472     *
473     * @param caption the table caption
474     * @param tableHeader the table header
475     * @return a content object
476     */
477    private Table<?> getTable2(Content caption, TableHeader tableHeader) {
478        return new Table<Void>(HtmlStyle.detailsTable)
479            .setCaption(caption)
480            .setHeader(tableHeader)
481            .setColumnStyles(HtmlStyle.colFirst, HtmlStyle.colLast);
482    }
483
484    /**
485     * Get a table, with three columns, with the second column being the defining column.
486     *
487     * @param caption the table caption
488     * @param tableHeader the table header
489     * @return a content object
490     */
491    private Table<?> getTable3(Content caption, TableHeader tableHeader) {
492        return new Table<Void>(HtmlStyle.detailsTable)
493            .setCaption(caption)
494            .setHeader(tableHeader)
495            .setColumnStyles(HtmlStyle.colFirst, HtmlStyle.colSecond,
496                HtmlStyle.colLast);
497    }
498
499    @Override
500    public void addModulesSummary(Content summariesList) {
501        if (display(requires) || display(indirectModules)) {
502            TableHeader requiresTableHeader
503                = new TableHeader(contents.modifierLabel, contents.moduleLabel,
504                    contents.descriptionLabel);
505            var section = HtmlTree.SECTION(HtmlStyle.modulesSummary)
506                .setId(HtmlIds.MODULES);
507            addSummaryHeader(MarkerComments.START_OF_MODULES_SUMMARY,
508                contents.navModules, section);
509            if (display(requires)) {
510                String text = resources.getText("doclet.Requires_Summary");
511                Content caption = Text.of(text);
512                var table = getTable3(caption, requiresTableHeader);
513                addModulesList(requires, table);
514                section.add(table);
515            }
516            // Display indirect modules table in both "api" and "all" mode.
517            if (display(indirectModules)) {
518                String amrText
519                    = resources.getText("doclet.Indirect_Requires_Summary");
520                Content amrCaption = Text.of(amrText);
521                var amrTable = getTable3(amrCaption, requiresTableHeader);
522                addModulesList(indirectModules, amrTable);
523                section.add(amrTable);
524            }
525            summariesList.add(HtmlTree.LI(section));
526        }
527    }
528
529    /**
530     * Add the list of modules.
531     *
532     * @param mdleMap map of modules and modifiers
533     * @param table the table to which the list will be added
534     */
535    private void addModulesList(Map<ModuleElement, Content> mdleMap,
536            Table<?> table) {
537        for (ModuleElement m : mdleMap.keySet()) {
538            Content modifiers = mdleMap.get(m);
539            Content moduleLink
540                = getModuleLink(m, Text.of(m.getQualifiedName()));
541            Content moduleSummary = new ContentBuilder();
542            addSummaryComment(m, moduleSummary);
543            table.addRow(modifiers, moduleLink, moduleSummary);
544        }
545    }
546
547    @Override
548    public void addPackagesSummary(Content summariesList) {
549        if (display(packages)
550            || display(indirectPackages) || display(indirectOpenPackages)) {
551            var section = HtmlTree.SECTION(HtmlStyle.packagesSummary)
552                .setId(HtmlIds.PACKAGES);
553            addSummaryHeader(MarkerComments.START_OF_PACKAGES_SUMMARY,
554                contents.navPackages, section);
555            if (display(packages)) {
556                addPackageSummary(section);
557            }
558            TableHeader indirectPackagesHeader
559                = new TableHeader(contents.fromLabel, contents.packagesLabel);
560            if (display(indirectPackages)) {
561                String aepText
562                    = resources.getText("doclet.Indirect_Exports_Summary");
563                var aepTable
564                    = getTable2(Text.of(aepText), indirectPackagesHeader);
565                addIndirectPackages(aepTable, indirectPackages);
566                section.add(aepTable);
567            }
568            if (display(indirectOpenPackages)) {
569                String aopText
570                    = resources.getText("doclet.Indirect_Opens_Summary");
571                var aopTable
572                    = getTable2(Text.of(aopText), indirectPackagesHeader);
573                addIndirectPackages(aopTable, indirectOpenPackages);
574                section.add(aopTable);
575            }
576            summariesList.add(HtmlTree.LI(section));
577        }
578    }
579
580    /**
581     * Add the package summary for the module.
582     *
583     * @param li
584     */
585    public void addPackageSummary(HtmlTree li) {
586        var table = new Table<PackageElement>(HtmlStyle.summaryTable)
587            .setId(HtmlIds.PACKAGE_SUMMARY_TABLE)
588            .setDefaultTab(contents.getContent("doclet.All_Packages"))
589            .addTab(contents.getContent("doclet.Exported_Packages_Summary"),
590                this::isExported)
591            .addTab(contents.getContent("doclet.Opened_Packages_Summary"),
592                this::isOpened)
593            .addTab(contents.getContent("doclet.Concealed_Packages_Summary"),
594                this::isConcealed);
595
596        // Determine whether to show the "Exported To" and "Opened To" columns,
597        // based on whether such columns would provide "useful" info.
598        int numExports = 0;
599        int numUnqualifiedExports = 0;
600        int numOpens = 0;
601        int numUnqualifiedOpens = 0;
602
603        for (PackageEntry e : packages.values()) {
604            if (e.exportedTo != null) {
605                numExports++;
606                if (e.exportedTo.isEmpty()) {
607                    numUnqualifiedExports++;
608                }
609            }
610            if (e.openedTo != null) {
611                numOpens++;
612                if (e.openedTo.isEmpty()) {
613                    numUnqualifiedOpens++;
614                }
615            }
616        }
617
618        boolean showExportedTo = numExports > 0
619            && (numOpens > 0 || numUnqualifiedExports < packages.size());
620        boolean showOpenedTo = numOpens > 0
621            && (numExports > 0 || numUnqualifiedOpens < packages.size());
622
623        // Create the table header and column styles.
624        List<Content> colHeaders = new ArrayList<>();
625        List<HtmlStyle> colStyles = new ArrayList<>();
626        colHeaders.add(contents.packageLabel);
627        colStyles.add(HtmlStyle.colFirst);
628
629        if (showExportedTo) {
630            colHeaders.add(contents.exportedTo);
631            colStyles.add(HtmlStyle.colSecond);
632        }
633
634        if (showOpenedTo) {
635            colHeaders.add(contents.openedTo);
636            colStyles.add(HtmlStyle.colSecond);
637        }
638
639        colHeaders.add(contents.descriptionLabel);
640        colStyles.add(HtmlStyle.colLast);
641
642        table.setHeader(new TableHeader(colHeaders).styles(colStyles))
643            .setColumnStyles(colStyles);
644
645        // Add the table rows, based on the "packages" map.
646        for (Map.Entry<PackageElement, PackageEntry> e : packages.entrySet()) {
647            PackageElement pkg = e.getKey();
648            PackageEntry entry = e.getValue();
649            List<Content> row = new ArrayList<>();
650            Content pkgLinkContent
651                = getPackageLink(pkg, getLocalizedPackageName(pkg));
652            row.add(pkgLinkContent);
653
654            if (showExportedTo) {
655                row.add(getPackageExportOpensTo(entry.exportedTo));
656            }
657            if (showOpenedTo) {
658                row.add(getPackageExportOpensTo(entry.openedTo));
659            }
660            Content summary = new ContentBuilder();
661            addPreviewSummary(pkg, summary);
662            addSummaryComment(pkg, summary);
663            row.add(summary);
664
665            table.addRow(pkg, row);
666        }
667
668        li.add(table);
669    }
670
671    private boolean isExported(Element e) {
672        PackageEntry entry = packages.get((PackageElement) e);
673        return (entry != null) && (entry.exportedTo != null);
674    }
675
676    private boolean isOpened(Element e) {
677        PackageEntry entry = packages.get((PackageElement) e);
678        return (entry != null) && (entry.openedTo != null);
679    }
680
681    private boolean isConcealed(Element e) {
682        PackageEntry entry = packages.get((PackageElement) e);
683        return (entry != null) && (entry.exportedTo == null)
684            && (entry.openedTo == null);
685    }
686
687    private Content getPackageExportOpensTo(Set<ModuleElement> modules) {
688        if (modules == null) {
689            return contents.getContent("doclet.None");
690        } else if (modules.isEmpty()) {
691            return contents.getContent("doclet.All_Modules");
692        } else {
693            Content list = new ContentBuilder();
694            for (ModuleElement m : modules) {
695                if (!list.isEmpty()) {
696                    list.add(Text.of(", "));
697                }
698                list.add(getModuleLink(m, Text.of(m.getQualifiedName())));
699            }
700            return list;
701        }
702    }
703
704    /**
705     * Add the indirect packages for the module being documented.
706     *
707     * @param table the table to which the content rows will be added
708     * @param ip indirect packages to be added
709     */
710    public void addIndirectPackages(Table<?> table,
711            Map<ModuleElement, SortedSet<PackageElement>> ip) {
712        for (Map.Entry<ModuleElement, SortedSet<PackageElement>> entry : ip
713            .entrySet()) {
714            ModuleElement m = entry.getKey();
715            SortedSet<PackageElement> pkgList = entry.getValue();
716            Content moduleLinkContent
717                = getModuleLink(m, Text.of(m.getQualifiedName()));
718            Content list = new ContentBuilder();
719            String sep = "";
720            for (PackageElement pkg : pkgList) {
721                list.add(sep);
722                list.add(getPackageLink(pkg, getLocalizedPackageName(pkg)));
723                sep = " ";
724            }
725            table.addRow(moduleLinkContent, list);
726        }
727    }
728
729    @Override
730    public void addServicesSummary(Content summariesList) {
731
732        boolean haveUses = displayServices(uses, usesTrees);
733        boolean haveProvides
734            = displayServices(provides.keySet(), providesTrees);
735
736        if (haveProvides || haveUses) {
737            var section = HtmlTree.SECTION(HtmlStyle.servicesSummary)
738                .setId(HtmlIds.SERVICES);
739            addSummaryHeader(MarkerComments.START_OF_SERVICES_SUMMARY,
740                contents.navServices, section);
741            TableHeader usesProvidesTableHeader = new TableHeader(
742                contents.typeLabel, contents.descriptionLabel);
743            if (haveProvides) {
744                String label = resources.getText("doclet.Provides_Summary");
745                var table = getTable2(Text.of(label), usesProvidesTableHeader);
746                addProvidesList(table);
747                if (!table.isEmpty()) {
748                    section.add(table);
749                }
750            }
751            if (haveUses) {
752                String label = resources.getText("doclet.Uses_Summary");
753                var table = getTable2(Text.of(label), usesProvidesTableHeader);
754                addUsesList(table);
755                if (!table.isEmpty()) {
756                    section.add(table);
757                }
758            }
759            summariesList.add(HtmlTree.LI(section));
760        }
761    }
762
763    /**
764     * Add the uses list for the module.
765     *
766     * @param table the table to which the services used will be added
767     */
768    public void addUsesList(Table<?> table) {
769        Content typeLinkContent;
770        Content description;
771        for (TypeElement t : uses) {
772            if (!displayServiceDirective(t, usesTrees)) {
773                continue;
774            }
775            typeLinkContent = getLink(new HtmlLinkInfo(configuration,
776                HtmlLinkInfo.Kind.SHOW_TYPE_PARAMS_AND_BOUNDS, t));
777            Content summary = new ContentBuilder();
778            if (display(usesTrees)) {
779                description = usesTrees.get(t);
780                if (description != null && !description.isEmpty()) {
781                    summary.add(HtmlTree.DIV(HtmlStyle.block, description));
782                } else {
783                    addSummaryComment(t, summary);
784                }
785            } else {
786                summary.add(Entity.NO_BREAK_SPACE);
787            }
788            table.addRow(typeLinkContent, summary);
789        }
790    }
791
792    /**
793     * Add the provides list for the module.
794     *
795     * @param table the table to which the services provided will be added
796     */
797    public void addProvidesList(Table<?> table) {
798        SortedSet<TypeElement> implSet;
799        Content description;
800        for (Map.Entry<TypeElement, SortedSet<TypeElement>> entry : provides
801            .entrySet()) {
802            TypeElement srv = entry.getKey();
803            if (!displayServiceDirective(srv, providesTrees)) {
804                continue;
805            }
806            implSet = entry.getValue();
807            Content srvLinkContent = getLink(new HtmlLinkInfo(configuration,
808                HtmlLinkInfo.Kind.SHOW_TYPE_PARAMS_AND_BOUNDS, srv));
809            Content desc = new ContentBuilder();
810            if (display(providesTrees)) {
811                description = providesTrees.get(srv);
812                if (description != null && !description.isEmpty()) {
813                    desc.add(HtmlTree.DIV(HtmlStyle.block, description));
814                } else {
815                    addSummaryComment(srv, desc);
816                }
817            } else {
818                desc.add(Entity.NO_BREAK_SPACE);
819            }
820            // Only display the implementation details in the "all" mode.
821            if (moduleMode == ModuleMode.ALL && !implSet.isEmpty()) {
822                desc.add(new HtmlTree(TagName.BR));
823                desc.add("(");
824                var implSpan = HtmlTree.SPAN(HtmlStyle.implementationLabel,
825                    contents.implementation);
826                desc.add(implSpan);
827                desc.add(Entity.NO_BREAK_SPACE);
828                String sep = "";
829                for (TypeElement impl : implSet) {
830                    desc.add(sep);
831                    desc.add(getLink(new HtmlLinkInfo(configuration,
832                        HtmlLinkInfo.Kind.SHOW_TYPE_PARAMS_AND_BOUNDS, impl)));
833                    sep = ", ";
834                }
835                desc.add(")");
836            }
837            table.addRow(srvLinkContent, desc);
838        }
839    }
840
841    /**
842     * Add the module deprecation information to the documentation tree.
843     *
844     * @param div the content to which the deprecation information will be added
845     */
846    public void addDeprecationInfo(Content div) {
847        List<? extends DeprecatedTree> deprs = utils.getDeprecatedTrees(mdle);
848        if (utils.isDeprecated(mdle)) {
849            CommentHelper ch = utils.getCommentHelper(mdle);
850            var deprDiv = HtmlTree.DIV(HtmlStyle.deprecationBlock);
851            var deprPhrase = HtmlTree.SPAN(HtmlStyle.deprecatedLabel,
852                getDeprecatedPhrase(mdle));
853            deprDiv.add(deprPhrase);
854            if (!deprs.isEmpty()) {
855                List<? extends DocTree> commentTags
856                    = ch.getDescription(deprs.get(0));
857                if (!commentTags.isEmpty()) {
858                    addInlineDeprecatedComment(mdle, deprs.get(0), deprDiv);
859                }
860            }
861            div.add(deprDiv);
862        }
863    }
864
865    @Override
866    public void addModuleDescription(Content moduleContent) {
867        addPreviewInfo(mdle, moduleContent);
868        if (!utils.getFullBody(mdle).isEmpty()) {
869            var tree = HtmlTree.SECTION(HtmlStyle.moduleDescription)
870                .setId(HtmlIds.MODULE_DESCRIPTION);
871            addDeprecationInfo(tree);
872            tree.add(MarkerComments.START_OF_MODULE_DESCRIPTION);
873            addInlineComment(mdle, tree);
874            addTagsInfo(mdle, tree);
875            moduleContent.add(tree);
876        }
877    }
878
879    @Override
880    public void addModuleSignature(Content moduleContent) {
881        moduleContent.add(new HtmlTree(TagName.HR));
882        moduleContent.add(Signatures.getModuleSignature(mdle, this));
883    }
884
885    @Override
886    public void addModuleContent(Content source) {
887        bodyContents.addMainContent(source);
888    }
889
890    @Override
891    public void addModuleFooter() {
892        bodyContents.setFooter(getFooter());
893    }
894
895    @Override
896    public void printDocument(Content content) throws DocFileIOException {
897        content.add(bodyContents);
898        printHtmlDocument(
899            configuration.metakeywords.getMetaKeywordsForModule(mdle),
900            getDescription("declaration", mdle), getLocalStylesheets(mdle),
901            content);
902    }
903
904    /**
905     * Add the module package deprecation information to the documentation tree.
906     *
907     * @param li the content to which the deprecation information will be added
908     * @param pkg the PackageDoc that is added
909     */
910    public void addPackageDeprecationInfo(Content li, PackageElement pkg) {
911        if (utils.isDeprecated(pkg)) {
912            List<? extends DeprecatedTree> deprs
913                = utils.getDeprecatedTrees(pkg);
914            var deprDiv = HtmlTree.DIV(HtmlStyle.deprecationBlock);
915            var deprPhrase = HtmlTree.SPAN(HtmlStyle.deprecatedLabel,
916                getDeprecatedPhrase(pkg));
917            deprDiv.add(deprPhrase);
918            if (!deprs.isEmpty()) {
919                CommentHelper ch = utils.getCommentHelper(pkg);
920                List<? extends DocTree> commentTags
921                    = ch.getDescription(deprs.get(0));
922                if (!commentTags.isEmpty()) {
923                    addInlineDeprecatedComment(pkg, deprs.get(0), deprDiv);
924                }
925            }
926            li.add(deprDiv);
927        }
928    }
929}