8178070: duplicate entries in package table
authorjjg
Wed, 13 Dec 2017 17:27:43 -0800
changeset 48325 e5cdedd37b78
parent 48323 23d427d8a1ff
child 48326 e1eb12343774
8178070: duplicate entries in package table Reviewed-by: bpatel, ksrini
src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/Contents.java
src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ModuleWriterImpl.java
src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/TableHeader.java
src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets.properties
test/langtools/jdk/javadoc/doclet/lib/JavadocTester.java
test/langtools/jdk/javadoc/doclet/testModules/TestModulePackages.java
test/langtools/jdk/javadoc/doclet/testModules/TestModules.java
test/langtools/tools/lib/toolbox/ModuleBuilder.java
test/langtools/tools/lib/toolbox/ToolBox.java
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/Contents.java	Wed Dec 13 14:49:48 2017 -0800
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/Contents.java	Wed Dec 13 17:27:43 2017 -0800
@@ -96,6 +96,7 @@
     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 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 @@
         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 @@
         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");
--- 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/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/TableHeader.java	Wed Dec 13 14:49:48 2017 -0800
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/TableHeader.java	Wed Dec 13 17:27:43 2017 -0800
@@ -77,6 +77,14 @@
     }
 
     /**
+     * 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.
      * @param styles the style class names
@@ -91,6 +99,20 @@
     }
 
     /**
+     * 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
      */
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets.properties	Wed Dec 13 14:49:48 2017 -0800
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets.properties	Wed Dec 13 17:27:43 2017 -0800
@@ -145,6 +145,8 @@
 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
--- 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, "&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());
+    }
+
+}
+
--- a/test/langtools/jdk/javadoc/doclet/testModules/TestModules.java	Wed Dec 13 14:49:48 2017 -0800
+++ b/test/langtools/jdk/javadoc/doclet/testModules/TestModules.java	Wed Dec 13 17:27:43 2017 -0800
@@ -994,7 +994,7 @@
                 + "</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 @@
                 "<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 @@
                 "<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>");
     }
 
--- a/test/langtools/tools/lib/toolbox/ModuleBuilder.java	Wed Dec 13 14:49:48 2017 -0800
+++ b/test/langtools/tools/lib/toolbox/ModuleBuilder.java	Wed Dec 13 17:27:43 2017 -0800
@@ -43,6 +43,7 @@
     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 @@
 
     /**
      * 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 @@
                     .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"));
--- a/test/langtools/tools/lib/toolbox/ToolBox.java	Wed Dec 13 14:49:48 2017 -0800
+++ b/test/langtools/tools/lib/toolbox/ToolBox.java	Wed Dec 13 17:27:43 2017 -0800
@@ -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 @@
             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 @@
 
         /**
          * 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";