--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ModuleWriterImpl.java Wed Dec 13 14:49:48 2017 -0800
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ModuleWriterImpl.java Wed Dec 13 17:27:43 2017 -0800
@@ -106,21 +106,34 @@
= new TreeMap<>(utils.makeModuleComparator());
/**
- * Map of packages exported by this module and the modules it has been exported to.
+ * Details about a package in a module.
+ * A package may be not exported, or exported to some modules, or exported to all modules.
+ * A package may be not opened, or opened to some modules, or opened to all modules.
+ * A package that is neither exported or opened to any modules is a concealed package.
+ * An open module opens all its packages to all modules.
*/
- private final Map<PackageElement, SortedSet<ModuleElement>> exportedPackages
- = new TreeMap<>(utils.makePackageComparator());
+ class PackageEntry {
+ /**
+ * Summary of package exports:
+ * If null, the package is not exported to any modules;
+ * if empty, the package is exported to all modules;
+ * otherwise, the package is exported to these modules.
+ */
+ Set<ModuleElement> exportedTo;
+
+ /**
+ * Summary of package opens:
+ * If null, the package is not opened to any modules;
+ * if empty, the package is opened to all modules;
+ * otherwise, the package is opened to these modules.
+ */
+ Set<ModuleElement> openedTo;
+ }
/**
- * Map of opened packages by this module and the modules it has been opened to.
+ * Map of packages of this module, and details of whether they are exported or opened.
*/
- private final Map<PackageElement, SortedSet<ModuleElement>> openedPackages
- = new TreeMap<>(utils.makePackageComparator());
-
- /**
- * Set of concealed packages of this module.
- */
- private final SortedSet<PackageElement> concealedPackages = new TreeSet<>(utils.makePackageComparator());
+ private final Map<PackageElement, PackageEntry> packages = new TreeMap<>(utils.makePackageComparator());
/**
* Map of indirect modules (transitive closure) and their exported packages.
@@ -284,51 +297,52 @@
}
});
- // Get all packages for the module and put it in the concealed packages set.
- utils.getModulePackageMap().getOrDefault(mdle, Collections.emptySet()).forEach((pkg) -> {
- if (shouldDocument(pkg) && moduleMode == ModuleMode.ALL) {
- concealedPackages.add(pkg);
+ // Get all packages if module is open or if displaying concealed modules
+ for (PackageElement pkg : utils.getModulePackageMap().getOrDefault(mdle, Collections.emptySet())) {
+ if (shouldDocument(pkg) && (mdle.isOpen() || moduleMode == ModuleMode.ALL)) {
+ PackageEntry e = new PackageEntry();
+ if (mdle.isOpen()) {
+ e.openedTo = Collections.emptySet();
+ }
+ packages.put(pkg, e);
}
- });
+ };
- // Get all exported packages for the module using the exports directive for the module.
- (ElementFilter.exportsIn(mdle.getDirectives())).forEach((directive) -> {
+ // Get all exported packages for the module, using the exports directive for the module.
+ for (ModuleElement.ExportsDirective directive : ElementFilter.exportsIn(mdle.getDirectives())) {
PackageElement p = directive.getPackage();
if (shouldDocument(p)) {
- SortedSet<ModuleElement> mdleList = new TreeSet<>(utils.makeModuleComparator());
List<? extends ModuleElement> targetMdles = directive.getTargetModules();
- if (targetMdles != null) {
- mdleList.addAll(targetMdles);
- }
- // Qualified exports should not be displayed in the api mode. So if mdleList is empty,
- // its exported to all modules and hence can be added.
- if (moduleMode == ModuleMode.ALL || mdleList.isEmpty()) {
- exportedPackages.put(p, mdleList);
- }
- if (moduleMode == ModuleMode.ALL) {
- concealedPackages.remove(p);
+ // Include package if in details mode, or exported to all (i.e. targetModules == null)
+ if (moduleMode == ModuleMode.ALL || targetMdles == null) {
+ PackageEntry packageEntry = packages.computeIfAbsent(p, pkg -> new PackageEntry());
+ SortedSet<ModuleElement> mdleList = new TreeSet<>(utils.makeModuleComparator());
+ if (targetMdles != null) {
+ mdleList.addAll(targetMdles);
+ }
+ packageEntry.exportedTo = mdleList;
}
}
- });
- // Get all opened packages for the module using the opens directive for the module.
- (ElementFilter.opensIn(mdle.getDirectives())).forEach((directive) -> {
+ }
+
+ // Get all opened packages for the module, using the opens directive for the module.
+ // If it is an open module, there will be no separate opens directives.
+ for (ModuleElement.OpensDirective directive : ElementFilter.opensIn(mdle.getDirectives())) {
PackageElement p = directive.getPackage();
if (shouldDocument(p)) {
- SortedSet<ModuleElement> mdleList = new TreeSet<>(utils.makeModuleComparator());
List<? extends ModuleElement> targetMdles = directive.getTargetModules();
- if (targetMdles != null) {
- mdleList.addAll(targetMdles);
- }
- // Qualified opens should not be displayed in the api mode. So if mdleList is empty,
- // it is opened to all modules and hence can be added.
- if (moduleMode == ModuleMode.ALL || mdleList.isEmpty()) {
- openedPackages.put(p, mdleList);
- }
- if (moduleMode == ModuleMode.ALL) {
- concealedPackages.remove(p);
+ // Include package if in details mode, or opened to all (i.e. targetModules == null)
+ if (moduleMode == ModuleMode.ALL || targetMdles == null) {
+ PackageEntry packageEntry = packages.computeIfAbsent(p, pkg -> new PackageEntry());
+ SortedSet<ModuleElement> mdleList = new TreeSet<>(utils.makeModuleComparator());
+ if (targetMdles != null) {
+ mdleList.addAll(targetMdles);
+ }
+ packageEntry.openedTo = mdleList;
}
}
- });
+ }
+
// Get all the exported and opened packages, for the transitive closure of the module, to be displayed in
// the indirect packages tables.
dependentModules.forEach((module, mod) -> {
@@ -348,15 +362,19 @@
indirectPackages.put(module, exportPkgList);
}
SortedSet<PackageElement> openPkgList = new TreeSet<>(utils.makePackageComparator());
- (ElementFilter.opensIn(module.getDirectives())).forEach((directive) -> {
- PackageElement pkg = directive.getPackage();
- if (shouldDocument(pkg)) {
- // Qualified opens are not displayed in API mode
- if (moduleMode == ModuleMode.ALL || directive.getTargetModules() == null) {
- openPkgList.add(pkg);
+ if (module.isOpen()) {
+ openPkgList.addAll(utils.getModulePackageMap().getOrDefault(module, Collections.emptySet()));
+ } else {
+ (ElementFilter.opensIn(module.getDirectives())).forEach((directive) -> {
+ PackageElement pkg = directive.getPackage();
+ if (shouldDocument(pkg)) {
+ // Qualified opens are not displayed in API mode
+ if (moduleMode == ModuleMode.ALL || directive.getTargetModules() == null) {
+ openPkgList.add(pkg);
+ }
}
- }
- });
+ });
+ }
// If none of the indirect modules have opened packages to be displayed, we should not be
// displaying the table and so it should not be added to the map.
if (!openPkgList.isEmpty()) {
@@ -556,13 +574,13 @@
@Override
public void addPackagesSummary(Content summaryContentTree) {
- if (display(exportedPackages) || display(openedPackages) || display(concealedPackages)
+ if (display(packages)
|| display(indirectPackages) || display(indirectOpenPackages)) {
HtmlTree li = new HtmlTree(HtmlTag.LI);
li.setStyle(HtmlStyle.blockList);
addSummaryHeader(HtmlConstants.START_OF_PACKAGES_SUMMARY, SectionName.PACKAGES,
contents.navPackages, li);
- if (display(exportedPackages) || display(openedPackages) || display(concealedPackages)) {
+ if (display(packages)) {
String tableSummary = resources.getText("doclet.Member_Table_Summary",
resources.getText("doclet.Packages_Summary"),
resources.getText("doclet.packages"));
@@ -607,77 +625,115 @@
Table table = new Table(configuration.htmlVersion, HtmlStyle.packagesSummary)
.setSummary(tableSummary)
.setDefaultTab(resources.getText("doclet.All_Packages"))
- .addTab(resources.getText("doclet.Exported_Packages_Summary"),
- e -> exportedPackages.containsKey((PackageElement) e))
- .addTab(resources.getText("doclet.Opened_Packages_Summary"),
- e -> openedPackages.containsKey((PackageElement) e))
- .addTab(resources.getText("doclet.Concealed_Packages_Summary"),
- e -> concealedPackages.contains((PackageElement) e))
+ .addTab(resources.getText("doclet.Exported_Packages_Summary"), this::isExported)
+ .addTab(resources.getText("doclet.Opened_Packages_Summary"), this::isOpened)
+ .addTab(resources.getText("doclet.Concealed_Packages_Summary"), this::isConcealed)
.setTabScript(i -> String.format("showPkgs(%d);", i))
.setTabScriptVariable("packages");
- if (configuration.docEnv.getModuleMode() == ModuleMode.API) {
- table.setHeader(new TableHeader(contents.packageLabel, contents.descriptionLabel))
- .setColumnStyles(HtmlStyle.colFirst, HtmlStyle.colLast);
- } else {
- table.setHeader(new TableHeader(contents.packageLabel, contents.moduleLabel, contents.descriptionLabel))
- .setColumnStyles(HtmlStyle.colFirst, HtmlStyle.colSecond, HtmlStyle.colLast);
+ // Determine whether to show the "Exported To" and "Opened To" columns,
+ // based on whether such columns would provide "useful" info.
+ int numExports = 0;
+ int numUnqualifiedExports = 0;
+ int numOpens = 0;
+ int numUnqualifiedOpens = 0;
+
+ for (PackageEntry e : packages.values()) {
+ if (e.exportedTo != null) {
+ numExports++;
+ if (e.exportedTo.isEmpty()) {
+ numUnqualifiedExports++;
+ }
+ }
+ if (e.openedTo != null) {
+ numOpens++;
+ if (e.openedTo.isEmpty()) {
+ numUnqualifiedOpens++;
+ }
+ }
}
- addPackageTableRows(table);
+ boolean showExportedTo = numExports > 0 && (numOpens > 0 || numUnqualifiedExports < packages.size());
+ boolean showOpenedTo = numOpens > 0 && (numExports > 0 || numUnqualifiedOpens < packages.size());
+
+ // Create the table header and column styles.
+ List<Content> colHeaders = new ArrayList<>();
+ List<HtmlStyle> colStyles = new ArrayList<>();
+ colHeaders.add(contents.packageLabel);
+ colStyles.add(HtmlStyle.colFirst);
+
+ if (showExportedTo) {
+ colHeaders.add(contents.exportedTo);
+ colStyles.add(HtmlStyle.colSecond);
+ }
+
+ if (showOpenedTo) {
+ colHeaders.add(contents.openedTo);
+ colStyles.add(HtmlStyle.colSecond);
+ }
+
+ colHeaders.add(contents.descriptionLabel);
+ colStyles.add(HtmlStyle.colLast);
+
+ table.setHeader(new TableHeader(colHeaders).styles(colStyles))
+ .setColumnStyles(colStyles);
+
+ // Add the table rows, based on the "packages" map.
+ for (Map.Entry<PackageElement, PackageEntry> e : packages.entrySet()) {
+ PackageElement pkg = e.getKey();
+ PackageEntry entry = e.getValue();
+ List<Content> row = new ArrayList<>();
+ Content pkgLinkContent = getPackageLink(pkg, new StringContent(utils.getPackageName(pkg)));
+ row.add(pkgLinkContent);
+
+ if (showExportedTo) {
+ row.add(getPackageExportOpensTo(entry.exportedTo));
+ }
+ if (showOpenedTo) {
+ row.add(getPackageExportOpensTo(entry.openedTo));
+ }
+ Content summary = new ContentBuilder();
+ addSummaryComment(pkg, summary);
+ row.add(summary);
+
+ table.addRow(pkg, row);
+ }
+
li.addContent(table.toContent());
if (table.needsScript()) {
mainBodyScript.append(table.getScript());
}
}
- /**
- * Get the package table rows.
- *
- * @return a content object
- */
- private void addPackageTableRows(Table table) {
- addPackageTableRows(table, exportedPackages);
- addPackageTableRows(table, openedPackages);
- // Show concealed packages only in "all" mode.
- if (moduleMode == ModuleMode.ALL) {
- for (PackageElement pkg : concealedPackages) {
- Content pkgLinkContent = getPackageLink(pkg, new StringContent(utils.getPackageName(pkg)));
- Content noModules = new StringContent(resources.getText("doclet.None"));
- Content summary = new ContentBuilder();
- addSummaryComment(pkg, summary);
- table.addRow(pkg, pkgLinkContent, noModules, summary);
- }
- }
+ private boolean isExported(Element e) {
+ PackageEntry entry = packages.get((PackageElement) e);
+ return (entry != null) && (entry.exportedTo != null);
+ }
+
+ private boolean isOpened(Element e) {
+ PackageEntry entry = packages.get((PackageElement) e);
+ return (entry != null) && (entry.openedTo != null);
+ }
+
+ private boolean isConcealed(Element e) {
+ PackageEntry entry = packages.get((PackageElement) e);
+ return (entry != null) && (entry.exportedTo == null) && (entry.openedTo == null);
}
- private void addPackageTableRows(Table table, Map<PackageElement,SortedSet<ModuleElement>> ap) {
- for (Map.Entry<PackageElement, SortedSet<ModuleElement>> entry : ap.entrySet()) {
- List<Content> row = new ArrayList<>();
- PackageElement pkg = entry.getKey();
- SortedSet<ModuleElement> mdleList = entry.getValue();
- Content pkgLinkContent = getPackageLink(pkg, new StringContent(utils.getPackageName(pkg)));
- row.add(pkgLinkContent);
-
- if (moduleMode == ModuleMode.ALL) {
- Content modules = new ContentBuilder();
- if (!mdleList.isEmpty()) {
- for (ModuleElement m : mdleList) {
- if (!modules.isEmpty()) {
- modules.addContent(new HtmlTree(HtmlTag.BR));
- }
- modules.addContent(getModuleLink(m, new StringContent(m.getQualifiedName())));
- }
- } else {
- Content allModules = new StringContent(resources.getText("doclet.All_Modules"));
- modules.addContent(allModules);
+ private Content getPackageExportOpensTo(Set<ModuleElement> modules) {
+ if (modules == null) {
+ return new StringContent(resources.getText("doclet.None"));
+ } else if (modules.isEmpty()) {
+ return new StringContent(resources.getText("doclet.All_Modules"));
+ } else {
+ Content list = new ContentBuilder();
+ for (ModuleElement m : modules) {
+ if (!list.isEmpty()) {
+ list.addContent(new StringContent(", "));
}
- row.add(modules);
+ list.addContent(getModuleLink(m, new StringContent(m.getQualifiedName())));
}
- Content summary = new ContentBuilder();
- addSummaryComment(pkg, summary);
- row.add(summary);
- table.addRow(pkg, row);
+ return list;
}
}
@@ -692,14 +748,14 @@
ModuleElement m = entry.getKey();
SortedSet<PackageElement> pkgList = entry.getValue();
Content moduleLinkContent = getModuleLink(m, new StringContent(m.getQualifiedName()));
- Content packages = new ContentBuilder();
+ Content list = new ContentBuilder();
String sep = "";
for (PackageElement pkg : pkgList) {
- packages.addContent(sep);
- packages.addContent(getPackageLink(pkg, new StringContent(utils.getPackageName(pkg))));
+ list.addContent(sep);
+ list.addContent(getPackageLink(pkg, new StringContent(utils.getPackageName(pkg))));
sep = " ";
}
- table.addRow(moduleLinkContent, packages);
+ table.addRow(moduleLinkContent, list);
}
}
@@ -898,7 +954,7 @@
? Links.createLink(SectionName.MODULES, contents.navModules)
: contents.navModules);
addNavGap(liNav);
- liNav.addContent((display(exportedPackages) || display(openedPackages) || display(concealedPackages)
+ liNav.addContent((display(packages)
|| display(indirectPackages) || display(indirectOpenPackages))
? Links.createLink(SectionName.PACKAGES, contents.navPackages)
: contents.navPackages);
--- a/test/langtools/jdk/javadoc/doclet/lib/JavadocTester.java Wed Dec 13 14:49:48 2017 -0800
+++ b/test/langtools/jdk/javadoc/doclet/lib/JavadocTester.java Wed Dec 13 17:27:43 2017 -0800
@@ -236,6 +236,9 @@
/** The current run of javadoc. Incremented when javadoc is called. */
private int javadocRunNum = 0;
+ /** The current subtest number for this run of javadoc. Incremented when checking(...) is called. */
+ private int javadocTestNum = 0;
+
/** Marker annotation for test methods to be invoked by runTests. */
@Retention(RetentionPolicy.RUNTIME)
@interface Test { }
@@ -295,11 +298,11 @@
fileContentCache.clear();
javadocRunNum++;
+ javadocTestNum = 0; // reset counter for this run of javadoc
if (javadocRunNum == 1) {
out.println("Running javadoc...");
} else {
- out.println("Running javadoc (run "
- + javadocRunNum + ")...");
+ out.println("Running javadoc (run "+ javadocRunNum + ")...");
}
outputDir = new File(".");
@@ -441,8 +444,7 @@
* Check for content in (or not in) the generated output.
* Within the search strings, the newline character \n
* will be translated to the platform newline character sequence.
- * @param path a path within the most recent output directory
- * or the name of one of the output buffers, identifying
+ * @param path a path within the most recent output directory, identifying
* where to look for the search strings.
* @param expectedFound true if all of the search strings are expected
* to be found, or false if all of the strings are expected to be
@@ -451,15 +453,13 @@
*/
public void checkOutput(String path, boolean expectedFound, String... strings) {
// Read contents of file
- String fileString;
try {
- fileString = readFile(outputDir, path);
+ String fileString = readFile(outputDir, path);
+ checkOutput(new File(outputDir, path).getPath(), fileString, expectedFound, strings);
} catch (Error e) {
checking("Read file");
failed("Error reading file: " + e);
- return;
}
- checkOutput(path, fileString, expectedFound, strings);
}
/**
@@ -476,6 +476,7 @@
checkOutput(output.toString(), outputMap.get(output), expectedFound, strings);
}
+ // NOTE: path may be the name of an Output stream as well as a file path
private void checkOutput(String path, String fileString, boolean expectedFound, String... strings) {
for (String stringToFind : strings) {
// log.logCheckOutput(path, expectedFound, stringToFind);
@@ -484,25 +485,27 @@
boolean isFound = findString(fileString, stringToFind);
if (isFound == expectedFound) {
passed(path + ": following text " + (isFound ? "found:" : "not found:") + "\n"
- + stringToFind + "\n");
+ + stringToFind);
} else {
failed(path + ": following text " + (isFound ? "found:" : "not found:") + "\n"
- + stringToFind + "\n");
+ + stringToFind);
}
}
}
/**
- * Get the content of the one of the output streams written by
- * javadoc.
+ * Get the content of the one of the output streams written by javadoc.
+ * @param output the name of the output stream
+ * @return the content of the output stream
*/
public String getOutput(Output output) {
return outputMap.get(output);
}
/**
- * Get the content of the one of the output streams written by
- * javadoc.
+ * Get the content of the one of the output streams written by javadoc.
+ * @param output the name of the output stream
+ * @return the content of the output stream, as a line of lines
*/
public List<String> getOutputLines(Output output) {
String text = outputMap.get(output);
@@ -534,9 +537,9 @@
File file = new File(outputDir, path);
boolean isFound = file.exists();
if (isFound == expectedFound) {
- passed(path + ": file " + (isFound ? "found:" : "not found:") + "\n");
+ passed(file, "file " + (isFound ? "found:" : "not found:") + "\n");
} else {
- failed(path + ": file " + (isFound ? "found:" : "not found:") + "\n");
+ failed(file, "file " + (isFound ? "found:" : "not found:") + "\n");
}
}
}
@@ -548,20 +551,21 @@
* @param strings the strings whose order to check
*/
public void checkOrder(String path, String... strings) {
+ File file = new File(outputDir, path);
String fileString = readOutputFile(path);
int prevIndex = -1;
for (String s : strings) {
s = s.replace("\n", NL); // normalize new lines
int currentIndex = fileString.indexOf(s, prevIndex + 1);
- checking(s + " at index " + currentIndex);
+ checking("file: " + file + ": " + s + " at index " + currentIndex);
if (currentIndex == -1) {
- failed(s + " not found.");
+ failed(file, s + " not found.");
continue;
}
if (currentIndex > prevIndex) {
- passed(s + " is in the correct order");
+ passed(file, s + " is in the correct order");
} else {
- failed("file: " + path + ": " + s + " is in the wrong order.");
+ failed(file, s + " is in the wrong order.");
}
prevIndex = currentIndex;
}
@@ -575,19 +579,20 @@
* @param strings ensure each are unique
*/
public void checkUnique(String path, String... strings) {
+ File file = new File(outputDir, path);
String fileString = readOutputFile(path);
for (String s : strings) {
int currentIndex = fileString.indexOf(s);
checking(s + " at index " + currentIndex);
if (currentIndex == -1) {
- failed(s + " not found.");
+ failed(file, s + " not found.");
continue;
}
int nextindex = fileString.indexOf(s, currentIndex + s.length());
if (nextindex == -1) {
- passed(s + " is unique");
+ passed(file, s + " is unique");
} else {
- failed(s + " is not unique, found at " + nextindex);
+ failed(file, s + " is not unique, found at " + nextindex);
}
}
}
@@ -702,16 +707,35 @@
protected void checking(String message) {
numTestsRun++;
- print("Starting subtest " + numTestsRun, message);
+ javadocTestNum++;
+ print("Starting subtest " + javadocRunNum + "." + javadocTestNum, message);
+ }
+
+ protected void passed(File file, String message) {
+ passed(file + ": " + message);
}
protected void passed(String message) {
numTestsPassed++;
print("Passed", message);
+ out.println();
+ }
+
+ protected void failed(File file, String message) {
+ failed(file + ": " + message);
}
protected void failed(String message) {
print("FAILED", message);
+ StackWalker.getInstance().walk(s -> {
+ s.dropWhile(f -> f.getMethodName().equals("failed"))
+ .takeWhile(f -> !f.getMethodName().equals("runTests"))
+ .forEach(f -> out.println(" at "
+ + f.getClassName() + "." + f.getMethodName()
+ + "(" + f.getFileName() + ":" + f.getLineNumber() + ")"));
+ return null;
+ });
+ out.println();
}
private void print(String prefix, String message) {
@@ -720,7 +744,10 @@
else {
out.print(prefix);
out.print(": ");
- out.println(message.replace("\n", NL));
+ out.print(message.replace("\n", NL));
+ if (!(message.endsWith("\n") || message.endsWith(NL))) {
+ out.println();
+ }
}
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/langtools/jdk/javadoc/doclet/testModules/TestModulePackages.java Wed Dec 13 17:27:43 2017 -0800
@@ -0,0 +1,486 @@
+/*
+ * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8178070
+ * @summary Test packages table in module summary pages
+ * @library /tools/lib ../lib
+ * @modules jdk.javadoc/jdk.javadoc.internal.tool
+ * @build toolbox.ModuleBuilder toolbox.ToolBox JavadocTester
+ * @run main TestModulePackages
+ */
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Set;
+
+import toolbox.ModuleBuilder;
+import toolbox.ToolBox;
+
+public class TestModulePackages extends JavadocTester {
+ enum TabKind { EXPORTS, OPENS, CONCEALED };
+ enum ColKind { EXPORTED_TO, OPENED_TO };
+
+ public static void main(String... args) throws Exception {
+ TestModulePackages tester = new TestModulePackages();
+ tester.runTests(m -> new Object[] { Paths.get(m.getName()) });
+ }
+
+ private final ToolBox tb;
+
+ public TestModulePackages() {
+ tb = new ToolBox();
+ }
+
+ // @Test: See: https://bugs.openjdk.java.net/browse/JDK-8193107
+ public void empty(Path base) throws Exception {
+ Path src = base.resolve("src");
+ new ModuleBuilder(tb, "m")
+ .comment("empty module")
+ .write(src);
+
+ javadoc("-d", base.resolve("out").toString(),
+ "-quiet",
+ "-noindex",
+ "--module-source-path", src.toString(),
+ "--module", "m");
+
+ checkExit(Exit.OK);
+ checkOutput("m-summary.html", false,
+ "<h3>Packages</h3>\n"
+ + "<table class=\"packagesSummary\" summary=\"Packages table, "
+ + "listing packages, and an explanation\">");
+ }
+
+ @Test
+ public void exportSingle(Path base) throws Exception {
+ Path src = base.resolve("src");
+ new ModuleBuilder(tb, "m")
+ .comment("exports single package to all")
+ .exports("p")
+ .classes("package p; public class C { }")
+ .write(src);
+
+ javadoc("-d", base.resolve("out").toString(),
+ "-quiet",
+ "-noindex",
+ "--module-source-path", src.toString(),
+ "--module", "m");
+
+ checkExit(Exit.OK);
+ checkCaption("m", TabKind.EXPORTS);
+ checkTableHead("m");
+ checkPackageRow("m", "p", "i0", null, null, " ");
+ }
+
+ @Test
+ public void exportMultiple(Path base) throws Exception {
+ Path src = base.resolve("src");
+ new ModuleBuilder(tb, "m")
+ .comment("exports multiple packages to all")
+ .exports("p")
+ .exports("q")
+ .classes("package p; public class C { }")
+ .classes("package q; public class D { }")
+ .write(src);
+
+ javadoc("-d", base.resolve("out").toString(),
+ "-quiet",
+ "-noindex",
+ "--module-source-path", src.toString(),
+ "--module", "m");
+
+ checkExit(Exit.OK);
+ checkCaption("m", TabKind.EXPORTS);
+ checkTableHead("m");
+ checkPackageRow("m", "p", "i0", null, null, " ");
+ checkPackageRow("m", "q", "i1", null, null, " ");
+ }
+
+ @Test
+ public void exportSomeQualified(Path base) throws Exception {
+ Path src = base.resolve("src");
+ new ModuleBuilder(tb, "m")
+ .comment("exports multiple packages, some qualified")
+ .exports("p")
+ .exportsTo("q", "other")
+ .classes("package p; public class C { }")
+ .classes("package q; public class D { }")
+ .write(src);
+
+ new ModuleBuilder(tb, "other")
+ .comment("dummy module for target of export")
+ .write(src);
+
+ javadoc("-d", base.resolve("out-api").toString(),
+ "-quiet",
+ "-noindex",
+ "--module-source-path", src.toString(),
+ "--module", "m,other");
+
+ checkExit(Exit.OK);
+ checkCaption("m", TabKind.EXPORTS);
+ checkTableHead("m");
+ checkPackageRow("m", "p", "i0", null, null, " ");
+
+ javadoc("-d", base.resolve("out-all").toString(),
+ "-quiet",
+ "-noindex",
+ "--show-module-contents", "all",
+ "--module-source-path", src.toString(),
+ "--module", "m,other");
+
+ checkExit(Exit.OK);
+ checkCaption("m", TabKind.EXPORTS);
+ checkTableHead("m", ColKind.EXPORTED_TO);
+ checkPackageRow("m", "p", "i0", "All Modules", null, " ");
+ checkPackageRow("m", "q", "i1",
+ "<a href=\"other-summary.html\">other</a>", null, " ");
+ }
+
+ @Test
+ public void exportWithConcealed(Path base) throws Exception {
+ Path src = base.resolve("src");
+ new ModuleBuilder(tb, "m")
+ .comment("exports package, has concealed package")
+ .exports("p")
+ .classes("package p; public class C { }")
+ .classes("package q; public class D { }")
+ .write(src);
+
+ javadoc("-d", base.resolve("out-api").toString(),
+ "-quiet",
+ "-noindex",
+ "--module-source-path", src.toString(),
+ "--module", "m");
+
+ checkExit(Exit.OK);
+ checkCaption("m", TabKind.EXPORTS);
+ checkTableHead("m");
+ checkPackageRow("m", "p", "i0", null, null, " ");
+
+ javadoc("-d", base.resolve("out-all").toString(),
+ "-quiet",
+ "-noindex",
+ "--show-module-contents", "all",
+ "--show-packages", "all",
+ "--module-source-path", src.toString(),
+ "--module", "m");
+
+ checkExit(Exit.OK);
+ checkCaption("m", TabKind.EXPORTS, TabKind.CONCEALED);
+ checkTableHead("m", ColKind.EXPORTED_TO);
+ checkPackageRow("m", "p", "i0", "All Modules", null, " ");
+ checkPackageRow("m", "q", "i1", "None", null, " ");
+ }
+
+ @Test
+ public void exportOpenWithConcealed(Path base) throws Exception {
+ Path src = base.resolve("src");
+ new ModuleBuilder(tb, "m")
+ .comment("exports and opens qual and unqual, with concealed")
+ .exports("e.all")
+ .exportsTo("e.other", "other")
+ .opens("o.all")
+ .opensTo("o.other", "other")
+ .exports("eo")
+ .opens("eo")
+ .classes("package e.all; public class CEAll { }")
+ .classes("package e.other; public class CEOther { }")
+ .classes("package o.all; public class COAll { }")
+ .classes("package o.other; public class COOther { }")
+ .classes("package eo; public class CEO { }")
+ .classes("package c; public class C { }")
+ .write(src);
+
+ new ModuleBuilder(tb, "other")
+ .comment("dummy module for target of export and open")
+ .write(src);
+
+ javadoc("-d", base.resolve("out-api").toString(),
+ "-quiet",
+ "-noindex",
+ "--module-source-path", src.toString(),
+ "--module", "m,other");
+
+ checkExit(Exit.OK);
+ checkCaption("m", TabKind.EXPORTS, TabKind.OPENS);
+ checkTableHead("m", ColKind.EXPORTED_TO, ColKind.OPENED_TO);
+ checkPackageRow("m", "e.all", "i0", "All Modules", "None", " ");
+ checkPackageRow("m", "eo", "i1", "All Modules", "All Modules", " ");
+
+ javadoc("-d", base.resolve("out-all").toString(),
+ "-quiet",
+ "-noindex",
+ "--show-module-contents", "all",
+ "--show-packages", "all",
+ "--module-source-path", src.toString(),
+ "--module", "m,other");
+
+ checkExit(Exit.OK);
+ checkCaption("m", TabKind.EXPORTS, TabKind.OPENS, TabKind.CONCEALED);
+ checkTableHead("m", ColKind.EXPORTED_TO, ColKind.OPENED_TO);
+ checkPackageRow("m", "c", "i0", "None", "None", " ");
+ checkPackageRow("m", "e.all", "i1", "All Modules", "None", " ");
+ checkPackageRow("m", "e.other", "i2",
+ "<a href=\"other-summary.html\">other</a>", "None", " ");
+ checkPackageRow("m", "eo", "i3", "All Modules", "All Modules", " ");
+ checkPackageRow("m", "o.all", "i4", "None", "All Modules", " ");
+ checkPackageRow("m", "o.other", "i5", "None",
+ "<a href=\"other-summary.html\">other</a>", " ");
+ }
+
+ @Test
+ public void openModule(Path base) throws Exception {
+ Path src = base.resolve("src");
+ new ModuleBuilder(tb, true, "m")
+ .comment("open module")
+ .classes("/** implicitly open package */ package p;")
+ .classes("package p; public class C { } ")
+ .classes("/** implicitly open package */ package q;")
+ .classes("package q; public class D { }")
+ .write(src);
+
+ javadoc("-d", base.resolve("out").toString(),
+ "-quiet",
+ "-noindex",
+ "--show-packages", "all", // required, to show open packages; see JDK-8193107
+ "--module-source-path", src.toString(),
+ "--module", "m");
+
+ checkExit(Exit.OK);
+ checkCaption("m", TabKind.OPENS);
+ checkTableHead("m");
+ checkPackageRow("m", "p", "i0", null, null,
+ "\n<div class=\"block\">implicitly open package</div>\n");
+ checkPackageRow("m", "q", "i1", null, null,
+ "\n<div class=\"block\">implicitly open package</div>\n");
+ }
+ @Test
+ public void openSingle(Path base) throws Exception {
+ Path src = base.resolve("src");
+ new ModuleBuilder(tb, "m")
+ .comment("opens single package to all")
+ .opens("p")
+ .classes("package p; public class C { }")
+ .write(src);
+
+ javadoc("-d", base.resolve("out").toString(),
+ "-quiet",
+ "-noindex",
+ "--show-packages", "all", // required, to show open packages; see JDK-8193107
+ "--module-source-path", src.toString(),
+ "--module", "m");
+
+ checkExit(Exit.OK);
+ checkCaption("m", TabKind.OPENS);
+ checkTableHead("m");
+ checkPackageRow("m", "p", "i0", null, null, " ");
+ }
+
+ @Test
+ public void openMultiple(Path base) throws Exception {
+ Path src = base.resolve("src");
+ new ModuleBuilder(tb, "m")
+ .comment("opens multiple packages to all")
+ .opens("p")
+ .opens("q")
+ .classes("package p; public class C { }")
+ .classes("package q; public class D { }")
+ .write(src);
+
+ javadoc("-d", base.resolve("out").toString(),
+ "-quiet",
+ "-noindex",
+ "--show-packages", "all", // required, to show open packages; see JDK-8193107
+ "--module-source-path", src.toString(),
+ "--module", "m");
+
+ checkExit(Exit.OK);
+ checkCaption("m", TabKind.OPENS);
+ checkTableHead("m");
+ checkPackageRow("m", "p", "i0", null, null, " ");
+ checkPackageRow("m", "q", "i1", null, null, " ");
+ }
+
+ @Test
+ public void openSomeQualified(Path base) throws Exception {
+ Path src = base.resolve("src");
+ new ModuleBuilder(tb, "m")
+ .comment("opens multiple packages, some qualified")
+ .opens("p")
+ .opensTo("q", "other")
+ .classes("package p; public class C { }")
+ .classes("package q; public class D { }")
+ .write(src);
+
+ new ModuleBuilder(tb, "other")
+ .comment("dummy module for target of export")
+ .write(src);
+
+ javadoc("-d", base.resolve("out-api").toString(),
+ "-quiet",
+ "-noindex",
+ "--show-packages", "all", // required, to show open packages; see JDK-8193107
+ "--module-source-path", src.toString(),
+ "--module", "m,other");
+
+ checkExit(Exit.OK);
+ checkCaption("m", TabKind.OPENS);
+ checkTableHead("m");
+ checkPackageRow("m", "p", "i0", null, null, " ");
+
+ javadoc("-d", base.resolve("out-all").toString(),
+ "-quiet",
+ "-noindex",
+ "--show-packages", "all", // required, to show open packages; see JDK-8193107
+ "--show-module-contents", "all",
+ "--module-source-path", src.toString(),
+ "--module", "m,other");
+
+ checkExit(Exit.OK);
+ checkCaption("m", TabKind.OPENS);
+ checkTableHead("m", ColKind.OPENED_TO);
+ checkPackageRow("m", "p", "i0", null, "All Modules", " ");
+ checkPackageRow("m", "q", "i1", null,
+ "<a href=\"other-summary.html\">other</a>", " ");
+ }
+
+ @Test
+ public void openWithConcealed(Path base) throws Exception {
+ Path src = base.resolve("src");
+ new ModuleBuilder(tb, "m")
+ .comment("opens package, has concealed package")
+ .opens("p")
+ .classes("package p; public class C { }")
+ .classes("package q; public class D { }")
+ .write(src);
+
+ javadoc("-d", base.resolve("out-api").toString(),
+ "-quiet",
+ "-noindex",
+ "--show-packages", "all", // required, to show open packages; see JDK-8193107
+ "--module-source-path", src.toString(),
+ "--module", "m");
+
+ checkExit(Exit.OK);
+ checkCaption("m", TabKind.OPENS);
+ checkTableHead("m");
+ checkPackageRow("m", "p", "i0", null, null, " ");
+
+ javadoc("-d", base.resolve("out-all").toString(),
+ "-quiet",
+ "-noindex",
+ "--show-module-contents", "all",
+ "--show-packages", "all",
+ "--module-source-path", src.toString(),
+ "--module", "m");
+
+ checkExit(Exit.OK);
+ checkCaption("m", TabKind.OPENS, TabKind.CONCEALED);
+ checkTableHead("m", ColKind.OPENED_TO);
+ checkPackageRow("m", "p", "i0", null, "All Modules", " ");
+ checkPackageRow("m", "q", "i1", null, "None", " ");
+ }
+
+
+ private void checkCaption(String moduleName, TabKind... kinds) {
+ String expect;
+ if (kinds.length > 1) {
+ Set<TabKind> kindSet = Set.of(kinds);
+ StringBuilder sb = new StringBuilder();
+ sb.append("<caption>"
+ + "<span id=\"t0\" class=\"activeTableTab\">"
+ + "<span>All Packages</span>"
+ + "<span class=\"tabEnd\"> </span></span>");
+ if (kindSet.contains(TabKind.EXPORTS)) {
+ sb.append("<span id=\"t1\" class=\"tableTab\">"
+ + "<span><a href=\"javascript:showPkgs(1);\">Exports</a></span>"
+ + "<span class=\"tabEnd\"> </span></span>");
+ }
+ if (kindSet.contains(TabKind.OPENS)) {
+ sb.append("<span id=\"t2\" class=\"tableTab\">"
+ + "<span><a href=\"javascript:showPkgs(2);\">Opens</a></span>"
+ + "<span class=\"tabEnd\"> </span></span>");
+ }
+ if (kindSet.contains(TabKind.CONCEALED)) {
+ sb.append("<span id=\"t3\" class=\"tableTab\"><span>"
+ + "<a href=\"javascript:showPkgs(4);\">Concealed</a></span>"
+ + "<span class=\"tabEnd\"> </span></span>");
+ }
+ sb.append("</caption>");
+ expect = sb.toString();
+ } else {
+ TabKind k = kinds[0];
+ String name = k.toString().charAt(0) + k.toString().substring(1).toLowerCase();
+ expect = "<caption>"
+ + "<span>" + name + "</span>"
+ + "<span class=\"tabEnd\"> </span>"
+ + "</caption>";
+ }
+
+ checkOutput(moduleName + "-summary.html", true, expect);
+ }
+
+
+ private void checkTableHead(String moduleName, ColKind... kinds) {
+ Set<ColKind> kindSet = Set.of(kinds);
+ StringBuilder sb = new StringBuilder();
+ sb.append("<tr>\n"
+ + "<th class=\"colFirst\" scope=\"col\">Package</th>\n");
+ if (kindSet.contains(ColKind.EXPORTED_TO)) {
+ sb.append("<th class=\"colSecond\" scope=\"col\">Exported To Modules</th>\n");
+ }
+ if (kindSet.contains(ColKind.OPENED_TO)) {
+ sb.append("<th class=\"colSecond\" scope=\"col\">Opened To Modules</th>\n");
+ }
+ sb.append("<th class=\"colLast\" scope=\"col\">Description</th>\n"
+ + "</tr>");
+
+ checkOutput(moduleName + "-summary.html", true, sb.toString());
+ }
+
+ private void checkPackageRow(String moduleName, String packageName,
+ String id, String exportedTo, String openedTo, String desc) {
+ StringBuilder sb = new StringBuilder();
+ int idNum = Integer.parseInt(id.substring(1));
+ String color = (idNum % 2 == 1 ? "rowColor" : "altColor");
+ sb.append("<tr class=\"" + color + "\" id=\"" + id + "\">\n"
+ + "<th class=\"colFirst\" scope=\"row\">"
+ + "<a href=\"" + packageName.replace('.', '/') + "/package-summary.html\">"
+ + packageName + "</a></th>\n");
+ if (exportedTo != null) {
+ sb.append("<td class=\"colSecond\">" + exportedTo + "</td>\n");
+ }
+ if (openedTo != null) {
+ sb.append("<td class=\"colSecond\">" + openedTo + "</td>\n");
+ }
+ sb.append("<td class=\"colLast\">" + desc + "</td>");
+
+ checkOutput(moduleName + "-summary.html", true, sb.toString());
+ }
+
+}
+