8288058: Broken links on constant-values page

Reviewed-by: prappo
This commit is contained in:
Jonathan Gibbons 2022-06-28 15:58:12 +00:00
parent a814293e1f
commit c42b796f91
8 changed files with 301 additions and 143 deletions
src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets
test/langtools/jdk/javadoc/doclet
testConstantValuesPage
testHtmlVersion

@ -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);
}

@ -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.
*

@ -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.

@ -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;

@ -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);

@ -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.

@ -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>""");
}
}

@ -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">""",