From c42b796f91eace68a08d7ef8d22552228d1711e1 Mon Sep 17 00:00:00 2001
From: Jonathan Gibbons <jjg@openjdk.org>
Date: Tue, 28 Jun 2022 15:58:12 +0000
Subject: [PATCH] 8288058: Broken links on constant-values page

Reviewed-by: prappo
---
 .../html/ConstantsSummaryWriterImpl.java      |  47 ++--
 .../doclets/formats/html/HtmlIds.java         |  15 +-
 .../toolkit/ConstantsSummaryWriter.java       |  20 +-
 .../internal/doclets/toolkit/WorkArounds.java |  16 --
 .../builders/ConstantsSummaryBuilder.java     | 108 ++++-----
 .../internal/doclets/toolkit/util/Utils.java  |  17 --
 .../TestConstantValuesPage.java               | 217 +++++++++++++++++-
 .../testHtmlVersion/TestHtmlVersion.java      |   4 +-
 8 files changed, 301 insertions(+), 143 deletions(-)

diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ConstantsSummaryWriterImpl.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ConstantsSummaryWriterImpl.java
index b37604550f3..5ad0589aeb5 100644
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ConstantsSummaryWriterImpl.java
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ConstantsSummaryWriterImpl.java
@@ -26,7 +26,6 @@
 package jdk.javadoc.internal.doclets.formats.html;
 
 import java.util.Collection;
-import java.util.Set;
 
 import javax.lang.model.element.Modifier;
 import javax.lang.model.element.PackageElement;
@@ -97,20 +96,16 @@ public class ConstantsSummaryWriterImpl extends HtmlDocletWriter implements Cons
     }
 
     @Override
-    public void addLinkToPackageContent(PackageElement pkg,
-            Set<PackageElement> printedPackageHeaders, Content content) {
+    public void addLinkToPackageContent(String abbrevPackageName, Content content) {
         //add link to summary
         Content link;
-        if (pkg.isUnnamed()) {
+        if (abbrevPackageName.isEmpty()) {
             link = links.createLink(HtmlIds.UNNAMED_PACKAGE_ANCHOR,
                     contents.defaultPackageLabel, "");
         } else {
-            String parsedPackageName = utils.parsePackageName(pkg);
-            Content packageNameContent = Text.of(parsedPackageName + ".*");
-            link = links.createLink(DocLink.fragment(parsedPackageName),
+            Content packageNameContent = Text.of(abbrevPackageName + ".*");
+            link = links.createLink(DocLink.fragment(abbrevPackageName),
                     packageNameContent, "");
-            PackageElement abbrevPkg = configuration.workArounds.getAbbreviatedPackageElement(pkg);
-            printedPackageHeaders.add(abbrevPkg);
         }
         content.add(HtmlTree.LI(link));
     }
@@ -136,26 +131,25 @@ public class ConstantsSummaryWriterImpl extends HtmlDocletWriter implements Cons
     }
 
     @Override
-    public void addPackageName(PackageElement pkg, Content toContent, boolean first) {
-        Content pkgNameContent;
+    public void addPackageGroup(String abbrevPackageName, Content toContent) {
+        Content headingContent;
         HtmlId anchorName;
-        if (!first) {
-            toContent.add(summarySection);
-        }
-        if (pkg.isUnnamed()) {
+        if (abbrevPackageName.isEmpty()) {
             anchorName = HtmlIds.UNNAMED_PACKAGE_ANCHOR;
-            pkgNameContent = contents.defaultPackageLabel;
+            headingContent = contents.defaultPackageLabel;
         } else {
-            String parsedPackageName = utils.parsePackageName(pkg);
-            anchorName = htmlIds.forPackage(pkg);
-            pkgNameContent = getPackageLabel(parsedPackageName);
+            anchorName = htmlIds.forPackageName(abbrevPackageName);
+            headingContent = new ContentBuilder(
+                    getPackageLabel(abbrevPackageName),
+                    Text.of(".*"));
         }
-        var headingContent = Text.of(".*");
-        var heading = HtmlTree.HEADING_TITLE(Headings.ConstantsSummary.PACKAGE_HEADING,
-                pkgNameContent);
-        heading.add(headingContent);
+        var heading = HtmlTree.HEADING_TITLE(
+                Headings.ConstantsSummary.PACKAGE_HEADING,
+                headingContent);
         summarySection = HtmlTree.SECTION(HtmlStyle.constantsSummary, heading)
                 .setId(anchorName);
+
+        toContent.add(summarySection);
     }
 
     @Override
@@ -175,7 +169,7 @@ public class ConstantsSummaryWriterImpl extends HtmlDocletWriter implements Cons
         currentTypeElement = typeElement;
 
         //generate links backward only to public classes.
-        Content classlink = (utils.isPublic(typeElement) || utils.isProtected(typeElement)) ?
+        Content classLink = (utils.isPublic(typeElement) || utils.isProtected(typeElement)) ?
             getLink(new HtmlLinkInfo(configuration,
                     HtmlLinkInfo.Kind.CONSTANT_SUMMARY, typeElement)) :
             Text.of(utils.getFullyQualifiedName(typeElement));
@@ -186,7 +180,7 @@ public class ConstantsSummaryWriterImpl extends HtmlDocletWriter implements Cons
             caption.add(enclosingPackage.getQualifiedName());
             caption.add(".");
         }
-        caption.add(classlink);
+        caption.add(classLink);
 
         Table table = new Table(HtmlStyle.summaryTable)
                 .setCaption(caption)
@@ -245,9 +239,6 @@ public class ConstantsSummaryWriterImpl extends HtmlDocletWriter implements Cons
 
     @Override
     public void addConstantSummaries(Content content) {
-        if (summarySection != null) {
-            content.add(summarySection);
-        }
         bodyContents.addMainContent(content);
     }
 
diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlIds.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlIds.java
index 18475b6c1d2..bc76f8b312f 100644
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlIds.java
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlIds.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021, 2022, 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
@@ -134,6 +134,19 @@ public class HtmlIds {
                 : HtmlId.of(element.getQualifiedName().toString());
     }
 
+    /**
+     * Returns an id for a package name.
+     *
+     * @param pkgName the package name
+     *
+     * @return the id
+     */
+    HtmlId forPackageName(String pkgName) {
+        return pkgName.isEmpty()
+                ? UNNAMED_PACKAGE_ANCHOR
+                : HtmlId.of(pkgName);
+    }
+
     /**
      * Returns an id for a class or interface.
      *
diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/ConstantsSummaryWriter.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/ConstantsSummaryWriter.java
index 0286b6e85c8..dbc04dbef55 100644
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/ConstantsSummaryWriter.java
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/ConstantsSummaryWriter.java
@@ -55,13 +55,10 @@ public interface ConstantsSummaryWriter {
     /**
      * Adds the given package name link to the constant content list.
      *
-     * @param pkg                    the {@link PackageElement} to index.
-     * @param writtenPackageHeaders  the set of package headers that have already
-     *                               been indexed, we want to index utmost once.
-     * @param content                the content to which the link will be added
+     * @param abbrevPackageName the abbreviated package name
+     * @param content       the content to which the link will be added
      */
-    void addLinkToPackageContent(PackageElement pkg, Set<PackageElement> writtenPackageHeaders,
-                                 Content content);
+    void addLinkToPackageContent(String abbrevPackageName, Content content);
 
     /**
      * Add the content list to the documentation.
@@ -78,17 +75,12 @@ public interface ConstantsSummaryWriter {
     Content getConstantSummaries();
 
     /**
-     * Adds the given package name.
+     * Adds a header for the given abbreviated package name.
      *
-     * @param pkg  the parsed package name.  We only Write the
-     *                          first 2 directory levels of the package
-     *                          name. For example, java.lang.ref would be
-     *                          indexed as java.lang.*.
+     * @param abbrevPackageName  the abbreviated package name
      * @param toContent the summaries documentation
-     * @param first true if the first package is listed
-     *                    be written
      */
-    void addPackageName(PackageElement pkg, Content toContent, boolean first);
+    void addPackageGroup(String abbrevPackageName, Content toContent);
 
     /**
      * Get the class summary header for the constants summary.
diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/WorkArounds.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/WorkArounds.java
index 8f8e204ea4b..209bd3605d7 100644
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/WorkArounds.java
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/WorkArounds.java
@@ -500,22 +500,6 @@ public class WorkArounds {
         }
     }
 
-    // TODO: we need to eliminate this, as it is hacky.
-    /**
-     * Returns a representation of the package truncated to two levels.
-     * For instance if the given package represents foo.bar.baz will return
-     * a representation of foo.bar
-     * @param pkg the PackageElement
-     * @return an abbreviated PackageElement
-     */
-    public PackageElement getAbbreviatedPackageElement(PackageElement pkg) {
-        String parsedPackageName = utils.parsePackageName(pkg);
-        ModuleElement encl = (ModuleElement) pkg.getEnclosingElement();
-        return encl == null
-                ? utils.elementUtils.getPackageElement(parsedPackageName)
-                : ((JavacElements) utils.elementUtils).getPackageElement(encl, parsedPackageName);
-    }
-
     public boolean isPreviewAPI(Element el) {
         Symbol sym = (Symbol) el;
         return (sym.flags() & Flags.PREVIEW_API) != 0;
diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/builders/ConstantsSummaryBuilder.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/builders/ConstantsSummaryBuilder.java
index 2feadbbc677..23b2c663999 100644
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/builders/ConstantsSummaryBuilder.java
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/builders/ConstantsSummaryBuilder.java
@@ -45,10 +45,10 @@ import static jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberTable.Kind.
 public class ConstantsSummaryBuilder extends AbstractBuilder {
 
     /**
-     * The maximum number of package directories shown in the constant
-     * value index.
+     * The maximum number of package directories shown in the headings of
+     * the constant values contents list and headings.
      */
-    public static final int MAX_CONSTANT_VALUE_INDEX_LENGTH = 2;
+    private static final int MAX_CONSTANT_VALUE_INDEX_LENGTH = 2;
 
     /**
      * The writer used to write the results.
@@ -56,14 +56,14 @@ public class ConstantsSummaryBuilder extends AbstractBuilder {
     protected ConstantsSummaryWriter writer;
 
     /**
-     * The set of TypeElements that have constant fields.
+     * The set of type elements that have constant fields.
      */
     protected final Set<TypeElement> typeElementsWithConstFields;
 
     /**
-     * The set of printed package headers.
+     * The set of package-group headings.
      */
-    protected final Set<PackageElement> printedPackageHeaders;
+    protected final Set<String> packageGroupHeadings;
 
     /**
      * The current package being documented.
@@ -76,25 +76,20 @@ public class ConstantsSummaryBuilder extends AbstractBuilder {
     private TypeElement currentClass;
 
     /**
-     * True if first package is listed.
-     */
-    private boolean first = true;
-
-    /**
-     * Construct a new ConstantsSummaryBuilder.
+     * Constructs a new {@code ConstantsSummaryBuilder}.
      *
-     * @param context       the build context.
+     * @param context       the build context
      */
     private ConstantsSummaryBuilder(Context context) {
         super(context);
         this.typeElementsWithConstFields = new HashSet<>();
-        this.printedPackageHeaders = new TreeSet<>(utils.comparators.makePackageComparator());
+        this.packageGroupHeadings = new TreeSet<>(utils::compareStrings);
     }
 
     /**
-     * Construct a ConstantsSummaryBuilder.
+     * Constructs a {@code ConstantsSummaryBuilder}.
      *
-     * @param context       the build context.
+     * @param context       the build context
      * @return the new ConstantsSummaryBuilder
      */
     public static ConstantsSummaryBuilder getInstance(Context context) {
@@ -117,7 +112,7 @@ public class ConstantsSummaryBuilder extends AbstractBuilder {
     }
 
     /**
-     * Build the constant summary.
+     * Builds the constant summary page.
      *
      * @throws DocletException if there is a problem while building the documentation
      */
@@ -132,56 +127,55 @@ public class ConstantsSummaryBuilder extends AbstractBuilder {
     }
 
     /**
-     * Build the list of packages.
+     * Builds the list of contents for the groups of packages appearing in the constants summary page.
      */
     protected void buildContents() {
         Content contentList = writer.getContentsHeader();
-        printedPackageHeaders.clear();
+        packageGroupHeadings.clear();
         for (PackageElement pkg : configuration.packages) {
-            if (hasConstantField(pkg) && !hasPrintedPackageIndex(pkg)) {
-                writer.addLinkToPackageContent(pkg, printedPackageHeaders, contentList);
+            String abbrevPackageName = getAbbrevPackageName(pkg);
+            if (hasConstantField(pkg) && !packageGroupHeadings.contains(abbrevPackageName)) {
+                writer.addLinkToPackageContent(abbrevPackageName, contentList);
+                packageGroupHeadings.add(abbrevPackageName);
             }
         }
         writer.addContentsList(contentList);
     }
 
     /**
-     * Build the summary for each documented package.
+     * Builds the summary for each documented package.
      *
      * @throws DocletException if there is a problem while building the documentation
      */
     protected void buildConstantSummaries() throws DocletException {
-        printedPackageHeaders.clear();
+        packageGroupHeadings.clear();
         Content summaries = writer.getConstantSummaries();
         for (PackageElement aPackage : configuration.packages) {
             if (hasConstantField(aPackage)) {
                 currentPackage = aPackage;
                 //Build the documentation for the current package.
-
                 buildPackageHeader(summaries);
                 buildClassConstantSummary();
-
-                first = false;
             }
         }
         writer.addConstantSummaries(summaries);
     }
 
     /**
-     * Build the header for the given package.
+     * Builds the header for the given package.
      *
      * @param target the content to which the package header will be added
      */
     protected void buildPackageHeader(Content target) {
-        PackageElement abbrevPkg = configuration.workArounds.getAbbreviatedPackageElement(currentPackage);
-        if (!printedPackageHeaders.contains(abbrevPkg)) {
-            writer.addPackageName(currentPackage, target, first);
-            printedPackageHeaders.add(abbrevPkg);
+        String abbrevPkgName = getAbbrevPackageName(currentPackage);
+        if (!packageGroupHeadings.contains(abbrevPkgName)) {
+            writer.addPackageGroup(abbrevPkgName, target);
+            packageGroupHeadings.add(abbrevPkgName);
         }
     }
 
     /**
-     * Build the summary for the current class.
+     * Builds the summary for the current class.
      *
      * @throws DocletException if there is a problem while building the documentation
      */
@@ -206,20 +200,18 @@ public class ConstantsSummaryBuilder extends AbstractBuilder {
     }
 
     /**
-     * Build the summary of constant members in the class.
+     * Builds the summary of constant members in the class.
      *
-     * @param target the content to which the constant members table
-     *               will be added
+     * @param target the content to which the table of constant members will be added
      */
     protected void buildConstantMembers(Content target) {
         new ConstantFieldBuilder(currentClass).buildMembersSummary(target);
     }
 
     /**
-     * Return true if the given package has constant fields to document.
+     * {@return true if the given package has constant fields to document}
      *
-     * @param pkg   the package being checked.
-     * @return true if the given package has constant fields to document.
+     * @param pkg   the package to be checked
      */
     private boolean hasConstantField(PackageElement pkg) {
         SortedSet<TypeElement> classes = !pkg.isUnnamed()
@@ -235,10 +227,9 @@ public class ConstantsSummaryBuilder extends AbstractBuilder {
     }
 
     /**
-     * Return true if the given class has constant fields to document.
+     * {@return true if the given class has constant fields to document}
      *
-     * @param typeElement the class being checked.
-     * @return true if the given package has constant fields to document.
+     * @param typeElement the class to be checked
      */
     private boolean hasConstantField (TypeElement typeElement) {
         VisibleMemberTable vmt = configuration.getVisibleMemberTable(typeElement);
@@ -254,33 +245,36 @@ public class ConstantsSummaryBuilder extends AbstractBuilder {
     }
 
     /**
-     * Return true if the given package name has been printed.  Also
-     * return true if the root of this package has been printed.
+     * {@return the abbreviated name for a package, containing the leading segments of the name}
      *
-     * @param pkg the name of the package to check.
+     * @param pkg the package
      */
-    private boolean hasPrintedPackageIndex(PackageElement pkg) {
-        for (PackageElement printedPkg : printedPackageHeaders) {
-            if (utils.getPackageName(pkg).startsWith(utils.parsePackageName(printedPkg))) {
-                return true;
-            }
+    public String getAbbrevPackageName(PackageElement pkg) {
+        if (pkg.isUnnamed()) {
+            return "";
         }
-        return false;
+
+        String packageName = utils.getPackageName(pkg);
+        int index = -1;
+        for (int j = 0; j < MAX_CONSTANT_VALUE_INDEX_LENGTH; j++) {
+            index = packageName.indexOf(".", index + 1);
+        }
+        return index == -1 ? packageName : packageName.substring(0, index);
     }
 
     /**
-     * Print the table of constants.
+     * Builder for the table of fields with constant values.
      */
     private class ConstantFieldBuilder {
 
         /**
-         * The typeElement that we are examining constants for.
+         * The type element that we are examining constants for.
          */
         protected TypeElement typeElement;
 
         /**
-         * Construct a ConstantFieldSubWriter.
-         * @param typeElement the typeElement that we are examining constants for.
+         * Constructs a {@code ConstantFieldBuilder}.
+         * @param typeElement the type element that we are examining constants for
          */
         public ConstantFieldBuilder(TypeElement typeElement) {
             this.typeElement = typeElement;
@@ -289,8 +283,7 @@ public class ConstantsSummaryBuilder extends AbstractBuilder {
         /**
          * Builds the table of constants for a given class.
          *
-         * @param target the content to which the class constants table
-         *               will be added
+         * @param target the content to which the table of class constants will be added
          */
         protected void buildMembersSummary(Content target) {
             SortedSet<VariableElement> members = members();
@@ -300,8 +293,7 @@ public class ConstantsSummaryBuilder extends AbstractBuilder {
         }
 
         /**
-         * Returns a set of visible constant fields for the given type.
-         * @return the set of visible constant fields for the given type.
+         * {@return a set of visible constant fields for the given type}
          */
         protected SortedSet<VariableElement> members() {
             VisibleMemberTable vmt = configuration.getVisibleMemberTable(typeElement);
diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java
index 567d6ff4841..6d44628e4c6 100644
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java
+++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java
@@ -124,7 +124,6 @@ import static javax.lang.model.element.ElementKind.*;
 import static javax.lang.model.type.TypeKind.*;
 
 import static com.sun.source.doctree.DocTree.Kind.*;
-import static jdk.javadoc.internal.doclets.toolkit.builders.ConstantsSummaryBuilder.MAX_CONSTANT_VALUE_INDEX_LENGTH;
 
 /**
  * Utilities Class for Doclets.
@@ -905,22 +904,6 @@ public class Utils {
         return searchResult;
     }
 
-    /**
-     * Parse the package name.  We only want to display package name up to
-     * 2 levels.
-     */
-    public String parsePackageName(PackageElement p) {
-        String pkgname = p.isUnnamed() ? "" : getPackageName(p);
-        int index = -1;
-        for (int j = 0; j < MAX_CONSTANT_VALUE_INDEX_LENGTH; j++) {
-            index = pkgname.indexOf(".", index + 1);
-        }
-        if (index != -1) {
-            pkgname = pkgname.substring(0, index);
-        }
-        return pkgname;
-    }
-
     /**
      * Given an annotation, return true if it should be documented and false
      * otherwise.
diff --git a/test/langtools/jdk/javadoc/doclet/testConstantValuesPage/TestConstantValuesPage.java b/test/langtools/jdk/javadoc/doclet/testConstantValuesPage/TestConstantValuesPage.java
index 107776f6a57..afd35c814b3 100644
--- a/test/langtools/jdk/javadoc/doclet/testConstantValuesPage/TestConstantValuesPage.java
+++ b/test/langtools/jdk/javadoc/doclet/testConstantValuesPage/TestConstantValuesPage.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2002, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2002, 2022, 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
@@ -23,16 +23,18 @@
 
 /*
  * @test
- * @bug 4681599
- * @summary Test to make sure that constant values page does not get
- * generated when doclet has nothing to document.
- * @library ../../lib
+ * @bug 4681599 8288058
+ * @summary Tests for the Constant Values page.
+ * @library /tools/lib ../../lib
  * @modules jdk.javadoc/jdk.javadoc.internal.tool
- * @build javadoc.tester.*
+ * @build toolbox.ToolBox javadoc.tester.*
  * @run main TestConstantValuesPage
  */
 
+import java.nio.file.Path;
+
 import javadoc.tester.JavadocTester;
+import toolbox.ToolBox;
 
 public class TestConstantValuesPage extends JavadocTester {
 
@@ -41,8 +43,14 @@ public class TestConstantValuesPage extends JavadocTester {
         tester.runTests();
     }
 
+    ToolBox tb = new ToolBox();
+
+    /**
+     * Test to make sure that constant values page does not get
+     * generated when doclet has nothing to document.
+     */
     @Test
-    public void test() {
+    public void testNoPage() {
         javadoc("-d", "out",
                 "-sourcepath", testSrc,
                 "foo");
@@ -50,5 +58,200 @@ public class TestConstantValuesPage extends JavadocTester {
 
         checkOutput(Output.OUT, false,
                 "constant-values.html...");
+        checkFiles(false, "constant-values.html");
+    }
+
+    /**
+     * Tests the "contents" list for a group of named packages in the unnamed module.
+     */
+    @Test
+    public void testIndexNamed(Path base) throws Exception {
+        Path src = base.resolve("src");
+        tb.writeJavaFiles(src,
+                """
+                    package p1.p2a.p3a;
+                    public class CA {
+                        public static final int ia = 1;
+                        public static final String sa = "string";
+                    }
+                    """,
+                """
+                    package p1.p2a.p3b;
+                    public class CB {
+                        public static final int ib = 1;
+                        public static final String sb = "string";
+                    }
+                    """,
+                """
+                    package p1.p2b.p3c;
+                    public class CC {
+                        public static final int ic = 1;
+                        public static final String sc = "string";
+                    }
+                    """,
+                """
+                    package p2;
+                    public class CD {
+                        public static final int id = 1;
+                        public static final String sd = "string";
+                    }
+                    """);
+
+        setAutomaticCheckLinks(true); // ensure link-checking enabled for this test
+
+        javadoc("-d", base.resolve("api").toString(),
+                "-Xdoclint:none",
+                "-sourcepath", src.toString(),
+                "p1.p2a.p3a", "p1.p2a.p3b", "p1.p2b.p3c");
+        checkExit(Exit.OK);
+
+        checkOutput("constant-values.html", true,
+                """
+                    <section class="packages">
+                    <h2 title="Contents">Contents</h2>
+                    <ul>
+                    <li><a href="#p1.p2a">p1.p2a.*</a></li>
+                    <li><a href="#p1.p2b">p1.p2b.*</a></li>
+                    </ul>
+                    </section>""");
+    }
+
+    /**
+     * Tests the "contents" list for the unnamed package in the unnamed module.
+     */
+    @Test
+    public void testIndexUnnamed(Path base) throws Exception {
+        Path src = base.resolve("src");
+        tb.writeJavaFiles(src,
+                """
+                    public class C {
+                        public static final int ia = 1;
+                        public static final String sa = "string";
+                    }
+                    """);
+
+        setAutomaticCheckLinks(true); // ensure link-checking enabled for this test
+
+        javadoc("-d", base.resolve("api").toString(),
+                "-Xdoclint:none",
+                "-sourcepath", src.toString(),
+                src.resolve("C.java").toString());
+        checkExit(Exit.OK);
+
+        checkOutput("constant-values.html", true,
+                """
+                    <section class="packages">
+                    <h2 title="Contents">Contents</h2>
+                    <ul>
+                    <li><a href="#unnamed-package">Unnamed Package</a></li>
+                    </ul>
+                    </section>""");
+    }
+
+    /**
+     * Tests the "contents" list for a group of named and unnamed packages in the unnamed module.
+     */
+    @Test
+    public void testMixed(Path base) throws Exception {
+        Path src = base.resolve("src");
+        tb.writeJavaFiles(src,
+                """
+                    package p1.p2a.p3a;
+                    public class CA {
+                        public static final int ia = 1;
+                        public static final String sa = "string";
+                    }
+                    """,
+                """
+                    public class C {
+                        public static final int ia = 1;
+                        public static final String sa = "string";
+                    }
+                    """);
+
+        setAutomaticCheckLinks(true); // ensure link-checking enabled for this test
+
+        javadoc("-d", base.resolve("api").toString(),
+                "-Xdoclint:none",
+                "-sourcepath", src.toString(),
+                "p1.p2a.p3a", src.resolve("C.java").toString());
+        checkExit(Exit.OK);
+
+        checkOutput("constant-values.html", true,
+                """
+                    <section class="packages">
+                    <h2 title="Contents">Contents</h2>
+                    <ul>
+                    <li><a href="#unnamed-package">Unnamed Package</a></li>
+                    <li><a href="#p1.p2a">p1.p2a.*</a></li>
+                    </ul>
+                    </section>""");
+    }
+
+    /**
+     * Tests the "contents" list for a group of named packages in named modules.
+     */
+    @Test
+    public void testModules(Path base) throws Exception {
+        Path src = base.resolve("src");
+        Path src_mA = src.resolve("mA");
+        tb.writeJavaFiles(src_mA,
+                """
+                    module mA {
+                        exports p.a;
+                        exports p.q.r1;
+                    }
+                    """,
+                """
+                    package p.a;
+                    public class CA {
+                        public static final int iA = 1;
+                    }
+                    """,
+                """
+                    package p.q.r1;
+                    public class C1 {
+                        public static final int i1 = 1;
+                    }
+                    """);
+        Path src_mB = src.resolve("mB");
+        tb.writeJavaFiles(src_mB,
+                """
+                    module mB {
+                        exports p.b;
+                        exports p.q.r2;
+                    }
+                    """,
+                """
+                    package p.b;
+                    public class CB {
+                        public static final int iB = 1;
+                    }
+                    """,
+                """
+                    package p.q.r2;
+                    public class C2 {
+                        public static final int i2 = 1;
+                    }
+                    """);
+
+        setAutomaticCheckLinks(true); // ensure link-checking enabled for this test
+
+        javadoc("-d", base.resolve("api").toString(),
+                "-Xdoclint:none",
+                "--module-source-path", src.toString(),
+                "--module", "mA,mB");
+        checkExit(Exit.OK);
+
+        checkOutput("constant-values.html", true,
+                """
+                    <section class="packages">
+                    <h2 title="Contents">Contents</h2>
+                    <ul>
+                    <li><a href="#p.a">p.a.*</a></li>
+                    <li><a href="#p.b">p.b.*</a></li>
+                    <li><a href="#p.q">p.q.*</a></li>
+                    </ul>
+                    </section>""");
     }
 }
diff --git a/test/langtools/jdk/javadoc/doclet/testHtmlVersion/TestHtmlVersion.java b/test/langtools/jdk/javadoc/doclet/testHtmlVersion/TestHtmlVersion.java
index 9637c589a06..68393890fa3 100644
--- a/test/langtools/jdk/javadoc/doclet/testHtmlVersion/TestHtmlVersion.java
+++ b/test/langtools/jdk/javadoc/doclet/testHtmlVersion/TestHtmlVersion.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2022, 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
@@ -197,7 +197,7 @@ public class TestHtmlVersion extends JavadocTester {
                     """,
                 """
                     <section class="constants-summary" id="pkg">
-                    <h2 title="pkg">pkg.*</h2>
+                    <h2 title="pkg.*">pkg.*</h2>
                     """,
                 """
                     <footer role="contentinfo">""",