8178070: duplicate entries in package table

Reviewed-by: bpatel, ksrini
This commit is contained in:
Jonathan Gibbons 2017-12-13 17:27:43 -08:00
parent be25eb7f0e
commit 5bcb718441
9 changed files with 773 additions and 148 deletions

View File

@ -96,6 +96,7 @@ public class Contents {
public final Content errors;
public final Content exception;
public final Content exceptions;
public final Content exportedTo;
public final Content fieldLabel;
public final Content fieldDetailsLabel;
public final Content fieldSummaryLabel;
@ -147,6 +148,7 @@ public class Contents {
public final Content noFramesLabel;
public final Content noScriptMessage;
public final Content openModuleLabel;
public final Content openedTo;
public final Content overridesLabel;
public final Content overviewLabel;
public final Content packageHierarchies;
@ -229,6 +231,7 @@ public class Contents {
errors = getContent("doclet.Errors");
exception = getContent("doclet.Exception");
exceptions = getContent("doclet.Exceptions");
exportedTo = getContent("doclet.ExportedTo");
fieldDetailsLabel = getContent("doclet.Field_Detail");
fieldSummaryLabel = getContent("doclet.Field_Summary");
fieldLabel = getContent("doclet.Field");
@ -279,6 +282,7 @@ public class Contents {
nextPackageLabel = getNonBreakContent("doclet.Next_Package");
noFramesLabel = getNonBreakContent("doclet.No_Frames");
noScriptMessage = getContent("doclet.No_Script_Message");
openedTo = getContent("doclet.OpenedTo");
openModuleLabel = getContent("doclet.Open_Module");
overridesLabel = getContent("doclet.Overrides");
overviewLabel = getContent("doclet.Overview");

View File

@ -106,21 +106,34 @@ public class ModuleWriterImpl extends HtmlDocletWriter implements ModuleSummaryW
= 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 @@ public class ModuleWriterImpl extends HtmlDocletWriter implements ModuleSummaryW
}
});
// 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 @@ public class ModuleWriterImpl extends HtmlDocletWriter implements ModuleSummaryW
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 @@ public class ModuleWriterImpl extends HtmlDocletWriter implements ModuleSummaryW
@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 @@ public class ModuleWriterImpl extends HtmlDocletWriter implements ModuleSummaryW
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++;
}
}
}
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);
}
addPackageTableRows(table);
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 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);
private boolean isOpened(Element e) {
PackageEntry entry = packages.get((PackageElement) e);
return (entry != null) && (entry.openedTo != null);
}
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 boolean isConcealed(Element e) {
PackageEntry entry = packages.get((PackageElement) e);
return (entry != null) && (entry.exportedTo == null) && (entry.openedTo == null);
}
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 @@ public class ModuleWriterImpl extends HtmlDocletWriter implements ModuleSummaryW
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 @@ public class ModuleWriterImpl extends HtmlDocletWriter implements ModuleSummaryW
? 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);

View File

@ -76,6 +76,14 @@ public class TableHeader {
this.cellContents = Arrays.asList(headerCellContents);
}
/**
* Creates a header row, with specified content for each cell.
* @param headerCellContents a content object for each header cell
*/
public TableHeader(List<Content> headerCellContents) {
this.cellContents = headerCellContents;
}
/**
* Set the style class names for each header cell.
* The number of names must match the number of cells given to the constructor.
@ -90,6 +98,20 @@ public class TableHeader {
return this;
}
/**
* Set the style class names for each header cell.
* The number of names must match the number of cells given to the constructor.
* @param styles the style class names
* @return this object
*/
public TableHeader styles(List<HtmlStyle> styles) {
if (styles.size() != cellContents.size()) {
throw new IllegalStateException();
}
this.styles = styles;
return this;
}
/**
* Converts this header to a {@link Content} object, for use in an {@link HtmlTree}.
* @return a Content object

View File

@ -145,6 +145,8 @@ doclet.errors=errors
doclet.Exception=Exception
doclet.exception=exception
doclet.exceptions=exceptions
doclet.ExportedTo=Exported To Modules
doclet.OpenedTo=Opened To Modules
doclet.Package_private=(package private)
doclet.Nested_Classes_Interfaces_Inherited_From_Class=Nested classes/interfaces inherited from class
doclet.Nested_Classes_Interfaces_Inherited_From_Interface=Nested classes/interfaces inherited from interface

View File

@ -236,6 +236,9 @@ public abstract class JavadocTester {
/** 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 @@ public abstract class JavadocTester {
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 @@ public abstract class JavadocTester {
* 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 abstract class JavadocTester {
*/
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 @@ public abstract class JavadocTester {
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 @@ public abstract class JavadocTester {
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 @@ public abstract class JavadocTester {
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 @@ public abstract class JavadocTester {
* @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 @@ public abstract class JavadocTester {
* @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 @@ public abstract class JavadocTester {
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 @@ public abstract class JavadocTester {
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();
}
}
}

View File

@ -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, "&nbsp;");
}
@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, "&nbsp;");
checkPackageRow("m", "q", "i1", null, null, "&nbsp;");
}
@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, "&nbsp;");
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, "&nbsp;");
checkPackageRow("m", "q", "i1",
"<a href=\"other-summary.html\">other</a>", null, "&nbsp;");
}
@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, "&nbsp;");
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, "&nbsp;");
checkPackageRow("m", "q", "i1", "None", null, "&nbsp;");
}
@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", "&nbsp;");
checkPackageRow("m", "eo", "i1", "All Modules", "All Modules", "&nbsp;");
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", "&nbsp;");
checkPackageRow("m", "e.all", "i1", "All Modules", "None", "&nbsp;");
checkPackageRow("m", "e.other", "i2",
"<a href=\"other-summary.html\">other</a>", "None", "&nbsp;");
checkPackageRow("m", "eo", "i3", "All Modules", "All Modules", "&nbsp;");
checkPackageRow("m", "o.all", "i4", "None", "All Modules", "&nbsp;");
checkPackageRow("m", "o.other", "i5", "None",
"<a href=\"other-summary.html\">other</a>", "&nbsp;");
}
@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, "&nbsp;");
}
@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, "&nbsp;");
checkPackageRow("m", "q", "i1", null, null, "&nbsp;");
}
@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, "&nbsp;");
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", "&nbsp;");
checkPackageRow("m", "q", "i1", null,
"<a href=\"other-summary.html\">other</a>", "&nbsp;");
}
@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, "&nbsp;");
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", "&nbsp;");
checkPackageRow("m", "q", "i1", null, "None", "&nbsp;");
}
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\">&nbsp;</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\">&nbsp;</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\">&nbsp;</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\">&nbsp;</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\">&nbsp;</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());
}
}

View File

@ -994,7 +994,7 @@ public class TestModules extends JavadocTester {
+ "</tr>\n"
+ "</tbody>\n"
+ "</table>");
checkOutput("moduletags-summary.html", found,
checkOutput("moduletags-summary.html", true,
"<th class=\"colFirst\" scope=\"row\"><a href=\"testpkgmdltags/package-summary.html\">testpkgmdltags</a></th>\n"
+ "<td class=\"colLast\">&nbsp;</td>");
}
@ -1025,6 +1025,7 @@ public class TestModules extends JavadocTester {
"<li><a href=\"#module.description\">Description</a>&nbsp;|&nbsp;<a href=\"#modules.summary\">"
+ "Modules</a>&nbsp;|&nbsp;<a href=\"#packages.summary\">Packages</a>&nbsp;|&nbsp;<a href=\"#services.summary\">Services</a></li>",
"<th class=\"colFirst\" scope=\"row\"><a href=\"testpkgmdlB/package-summary.html\">testpkgmdlB</a></th>\n"
+ "<td class=\"colSecond\">None</td>\n"
+ "<td class=\"colSecond\">All Modules</td>\n"
+ "<td class=\"colLast\">&nbsp;</td>",
"<td class=\"colFirst\"> </td>\n"
@ -1045,12 +1046,11 @@ public class TestModules extends JavadocTester {
"<caption><span>Exports</span><span class=\"tabEnd\">&nbsp;</span></caption>\n"
+ "<tr>\n"
+ "<th class=\"colFirst\" scope=\"col\">Package</th>\n"
+ "<th class=\"colSecond\" scope=\"col\">Module</th>\n"
+ "<th class=\"colSecond\" scope=\"col\">Exported To Modules</th>\n"
+ "<th class=\"colLast\" scope=\"col\">Description</th>\n"
+ "</tr>");
checkOutput("moduletags-summary.html", found,
checkOutput("moduletags-summary.html", true,
"<th class=\"colFirst\" scope=\"row\"><a href=\"testpkgmdltags/package-summary.html\">testpkgmdltags</a></th>\n"
+ "<td class=\"colSecond\">All Modules</td>\n"
+ "<td class=\"colLast\">&nbsp;</td>");
}

View File

@ -43,6 +43,7 @@ public class ModuleBuilder {
private final ToolBox tb;
private final String name;
private String comment = "";
private boolean open;
private List<String> requires = new ArrayList<>();
private List<String> exports = new ArrayList<>();
private List<String> opens = new ArrayList<>();
@ -53,11 +54,22 @@ public class ModuleBuilder {
/**
* Creates a builder for a module.
* @param tb a Toolbox that can be used to compile the module declaration.
* @param tb a Toolbox that can be used to compile the module declaration
* @param name the name of the module to be built
*/
public ModuleBuilder(ToolBox tb, String name) {
this(tb, false, name);
}
/**
* Creates a builder for a module.
* @param tb a Toolbox that can be used to compile the module declaration
* @param open whether or not this is an open module
* @param name the name of the module to be built
*/
public ModuleBuilder(ToolBox tb, boolean open, String name) {
this.tb = tb;
this.open = open;
this.name = name;
}
@ -214,6 +226,9 @@ public class ModuleBuilder {
.append(comment.replace("\n", "\n * "))
.append("\n */\n");
}
if (open) {
sb.append("open ");
}
sb.append("module ").append(name).append(" {\n");
requires.forEach(r -> sb.append(" " + r + "\n"));
exports.forEach(e -> sb.append(" " + e + "\n"));

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 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
@ -524,6 +524,8 @@ public class ToolBox {
return source;
}
private static Pattern commentPattern =
Pattern.compile("(?s)(\\s+//.*?\n|/\\*.*?\\*/)");
private static Pattern modulePattern =
Pattern.compile("module\\s+((?:\\w+\\.)*)");
private static Pattern packagePattern =
@ -533,13 +535,24 @@ public class ToolBox {
/**
* Extracts the Java file name from the class declaration.
* This method is intended for simple files and uses regular expressions,
* so comments matching the pattern can make the method fail.
* This method is intended for simple files and uses regular expressions.
* Comments in the source are stripped before looking for the
* declarations from which the name is derived.
*/
static String getJavaFileNameFromSource(String source) {
StringBuilder sb = new StringBuilder();
Matcher matcher = commentPattern.matcher(source);
int start = 0;
while (matcher.find()) {
sb.append(source.substring(start, matcher.start()));
start = matcher.end();
}
sb.append(source.substring(start));
source = sb.toString();
String packageName = null;
Matcher matcher = modulePattern.matcher(source);
matcher = modulePattern.matcher(source);
if (matcher.find())
return "module-info.java";