Skip to content

Commit

Permalink
8297437: javadoc cannot link to old docs (with old style anchors)
Browse files Browse the repository at this point in the history
Reviewed-by: phh
Backport-of: 15a14884013a975707008f648b8e4864e16006ed
  • Loading branch information
Roman Marchenko authored and Paul Hohensee committed Jul 5, 2023
1 parent dbf8820 commit b9a29eb
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 61 deletions.
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2023, 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
Expand Down Expand Up @@ -29,6 +29,7 @@
import jdk.javadoc.internal.doclets.toolkit.Content;
import jdk.javadoc.internal.doclets.toolkit.util.DocLink;
import jdk.javadoc.internal.doclets.toolkit.util.DocPath;
import jdk.javadoc.internal.doclets.toolkit.util.Extern;

/**
* Factory for HTML A elements, both links (with a {@code href} attribute)
Expand Down Expand Up @@ -334,44 +335,7 @@ public String getName(String name) {
return name.replaceAll(" +", "");
}

StringBuilder sb = new StringBuilder();
for (int i = 0; i < name.length(); i++) {
char ch = name.charAt(i);
switch (ch) {
case '(':
case ')':
case '<':
case '>':
case ',':
sb.append('-');
break;
case ' ':
case '[':
break;
case ']':
sb.append(":A");
break;
// Any appearance of $ needs to be substituted with ":D" and not with hyphen
// since a field name "P$$ and a method P(), both valid member names, can end
// up as "P--". A member name beginning with $ needs to be substituted with
// "Z:Z:D".
case '$':
if (i == 0)
sb.append("Z:Z");
sb.append(":D");
break;
// A member name beginning with _ needs to be prefixed with "Z:Z" since valid anchor
// names can only begin with a letter.
case '_':
if (i == 0)
sb.append("Z:Z");
sb.append(ch);
break;
default:
sb.append(ch);
}
}
return sb.toString();
return Extern.getOldFormHtmlName(name);
}

}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1998, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2023, 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
Expand Down Expand Up @@ -98,6 +98,11 @@ private static class Item {
*/
final boolean relative;

/**
* Indicates that docs use old-form of anchors.
*/
final boolean useOldFormId;

/**
* Constructor to build a Extern Item object and map it with the element name.
* If the same element name is found in the map, then the first mapped
Expand All @@ -108,10 +113,11 @@ private static class Item {
* file is picked.
* @param relative True if path is URL, false if directory path.
*/
Item(String elementName, DocPath path, boolean relative) {
Item(String elementName, DocPath path, boolean relative, boolean useOldFormId) {
this.elementName = elementName;
this.path = path;
this.relative = relative;
this.useOldFormId = useOldFormId;
}

/**
Expand Down Expand Up @@ -179,7 +185,7 @@ public DocLink getExternalLink(Element element, DocPath relativepath, String fil
DocPath p = fnd.relative ?
relativepath.resolve(fnd.path).resolve(filename) :
fnd.path.resolve(filename);
return new DocLink(p, "is-external=true", memberName);
return new DocLink(p, "is-external=true", fnd.useOldFormId ? getOldFormHtmlName(memberName) : memberName);
}

/**
Expand Down Expand Up @@ -212,6 +218,18 @@ public boolean link(String url, String elemlisturl, Reporter reporter) throws Do
return link(url, elemlisturl, reporter, true);
}

/**
* Checks if platform docs for the specified version use old-form anchors.
* Old-form anchors are used by Oracle docs for JDKs 8 and 9.
* It can be checked on https://docs.oracle.com/javase/<version>/docs/api
*
* @param version
* @return True if docs use old-form anchors
*/
private boolean isOldFormPlatformDocs(int version) {
return 8 == version || 9 == version;
}

/*
* Build the extern element list from given URL or the directory path.
* Flag error if the "-link" or "-linkoffline" option is already used.
Expand Down Expand Up @@ -292,7 +310,7 @@ private String adjustEndFileSeparator(String url) {
private void readElementListFromURL(String urlpath, URL elemlisturlpath) throws Fault {
try {
URL link = elemlisturlpath.toURI().resolve(DocPaths.ELEMENT_LIST.getPath()).toURL();
readElementList(link.openStream(), urlpath, false);
readElementList(link.openStream(), urlpath, false, false);
} catch (URISyntaxException | MalformedURLException exc) {
throw new Fault(configuration.getText("doclet.MalformedURL", elemlisturlpath.toString()), exc);
} catch (IOException exc) {
Expand All @@ -309,7 +327,7 @@ private void readElementListFromURL(String urlpath, URL elemlisturlpath) throws
private void readAlternateURL(String urlpath, URL elemlisturlpath) throws Fault {
try {
URL link = elemlisturlpath.toURI().resolve(DocPaths.PACKAGE_LIST.getPath()).toURL();
readElementList(link.openStream(), urlpath, false);
readElementList(link.openStream(), urlpath, false, true);
} catch (URISyntaxException | MalformedURLException exc) {
throw new Fault(configuration.getText("doclet.MalformedURL", elemlisturlpath.toString()), exc);
} catch (IOException exc) {
Expand All @@ -332,27 +350,27 @@ private void readElementListFromFile(String path, DocFile elemListPath)
file = file.resolveAgainst(DocumentationTool.Location.DOCUMENTATION_OUTPUT);
}
if (file.exists()) {
readElementList(file, path);
readElementList(file, path, false);
} else {
DocFile file1 = elemListPath.resolve(DocPaths.PACKAGE_LIST);
if (!(file1.isAbsolute() || linkoffline)) {
file1 = file1.resolveAgainst(DocumentationTool.Location.DOCUMENTATION_OUTPUT);
}
if (file1.exists()) {
readElementList(file1, path);
readElementList(file1, path, true);
} else {
throw new Fault(configuration.getText("doclet.File_error", file.getPath()), null);
}
}
}

private void readElementList(DocFile file, String path) throws Fault, DocFileIOException {
private void readElementList(DocFile file, String path, boolean isOldFormDoc) throws Fault, DocFileIOException {
try {
if (file.canRead()) {
boolean pathIsRelative
= !isUrl(path)
&& !DocFile.createFileForInput(configuration, path).isAbsolute();
readElementList(file.openInputStream(), path, pathIsRelative);
readElementList(file.openInputStream(), path, pathIsRelative, isOldFormDoc);
} else {
throw new Fault(configuration.getText("doclet.File_error", file.getPath()), null);
}
Expand All @@ -370,7 +388,7 @@ private void readElementList(DocFile file, String path) throws Fault, DocFileIOE
* @param relative Is path relative?
* @throws IOException if there is a problem reading or closing the stream
*/
private void readElementList(InputStream input, String path, boolean relative)
private void readElementList(InputStream input, String path, boolean relative, boolean isOldFormDoc)
throws IOException {
try (BufferedReader in = new BufferedReader(new InputStreamReader(input))) {
String elemname = null;
Expand All @@ -382,7 +400,7 @@ private void readElementList(InputStream input, String path, boolean relative)
elempath = basePath;
if (elemname.startsWith(DocletConstants.MODULE_PREFIX)) {
moduleName = elemname.replace(DocletConstants.MODULE_PREFIX, "");
Item item = new Item(moduleName, elempath, relative);
Item item = new Item(moduleName, elempath, relative, isOldFormDoc);
moduleItems.put(moduleName, item);
} else {
DocPath pkgPath = DocPath.create(elemname.replace('.', '/'));
Expand All @@ -392,7 +410,7 @@ private void readElementList(InputStream input, String path, boolean relative)
elempath = elempath.resolve(pkgPath);
}
String actualModuleName = checkLinkCompatibility(elemname, moduleName, path);
Item item = new Item(elemname, elempath, relative);
Item item = new Item(elemname, elempath, relative, isOldFormDoc);
packageItems.computeIfAbsent(actualModuleName, k -> new TreeMap<>())
.put(elemname, item);
}
Expand Down Expand Up @@ -455,4 +473,65 @@ private String checkLinkCompatibility(String packageName, String moduleName, Str
}
return moduleName == null ? DocletConstants.DEFAULT_ELEMENT_NAME : moduleName;
}

/**
* Converts a name to an old-form HTML name (old-form id).
*
* @param name the string that needs to be converted to a valid HTML name
* @return old-form HTML name
*/
public static String getOldFormHtmlName(String name) {
/* The HTML 4 spec at http://www.w3.org/TR/html4/types.html#h-6.2 mentions
* that the name/id should begin with a letter followed by other valid characters.
* The HTML 5 spec (draft) is more permissive on names/ids where the only restriction
* is that it should be at least one character long and should not contain spaces.
* The spec draft is @ http://www.w3.org/html/wg/drafts/html/master/dom.html#the-id-attribute.
*
* For HTML 4, we need to check for non-characters at the beginning of the name and
* substitute it accordingly, "_" and "$" can appear at the beginning of a member name.
* The method substitutes "$" with "Z:Z:D" and will prefix "_" with "Z:Z".
*/

if (null == name)
return name;

StringBuilder sb = new StringBuilder();
for (int i = 0; i < name.length(); i++) {
char ch = name.charAt(i);
switch (ch) {
case '(':
case ')':
case '<':
case '>':
case ',':
sb.append('-');
break;
case ' ':
case '[':
break;
case ']':
sb.append(":A");
break;
// Any appearance of $ needs to be substituted with ":D" and not with hyphen
// since a field name "P$$ and a method P(), both valid member names, can end
// up as "P--". A member name beginning with $ needs to be substituted with
// "Z:Z:D".
case '$':
if (i == 0)
sb.append("Z:Z");
sb.append(":D");
break;
// A member name beginning with _ needs to be prefixed with "Z:Z" since valid anchor
// names can only begin with a letter.
case '_':
if (i == 0)
sb.append("Z:Z");
sb.append(ch);
break;
default:
sb.append(ch);
}
}
return sb.toString();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2002, 2023, 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
Expand Down Expand Up @@ -59,7 +59,7 @@ void test() {
+ "title=\"class or interface in javax.swing.text\" class=\"externalLink\"><code>Link to AttributeContext innerclass</code></a>",
"<a href=\"" + uri + "java/math/BigDecimal.html?is-external=true\" "
+ "title=\"class or interface in java.math\" class=\"externalLink\"><code>Link to external class BigDecimal</code></a>",
"<a href=\"" + uri + "java/math/BigInteger.html?is-external=true#gcd(java.math.BigInteger)\" "
"<a href=\"" + uri + "java/math/BigInteger.html?is-external=true#gcd-java.math.BigInteger-\" "
+ "title=\"class or interface in java.math\" class=\"externalLink\"><code>Link to external member gcd</code></a>",
"<a href=\"" + uri + "javax/tools/SimpleJavaFileObject.html?is-external=true#uri\" "
+ "title=\"class or interface in javax.tools\" class=\"externalLink\"><code>Link to external member URI</code></a>",
Expand Down Expand Up @@ -90,7 +90,7 @@ void test_warning() {
+ "title=\"class or interface in javax.swing.text\" class=\"externalLink\"><code>Link to AttributeContext innerclass</code></a>",
"<a href=\"" + uri + "java/math/BigDecimal.html?is-external=true\" "
+ "title=\"class or interface in java.math\" class=\"externalLink\"><code>Link to external class BigDecimal</code></a>",
"<a href=\"" + uri + "java/math/BigInteger.html?is-external=true#gcd(java.math.BigInteger)\" "
"<a href=\"" + uri + "java/math/BigInteger.html?is-external=true#gcd-java.math.BigInteger-\" "
+ "title=\"class or interface in java.math\" class=\"externalLink\"><code>Link to external member gcd</code></a>",
"<a href=\"" + uri + "javax/tools/SimpleJavaFileObject.html?is-external=true#uri\" "
+ "title=\"class or interface in javax.tools\" class=\"externalLink\"><code>Link to external member URI</code></a>",
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2023, 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
Expand Down Expand Up @@ -53,12 +53,12 @@ void test() {

checkOutput("pkg/XReader.html", true,
"<dt><span class=\"overrideSpecifyLabel\">Overrides:</span></dt>\n"
+ "<dd><code><a href=\"" + uri + "/java/io/FilterReader.html?is-external=true#read()\" "
+ "<dd><code><a href=\"" + uri + "/java/io/FilterReader.html?is-external=true#read--\" "
+ "title=\"class or interface in java.io\" class=\"externalLink\">read</a></code>&nbsp;in class&nbsp;<code>"
+ "<a href=\"" + uri + "/java/io/FilterReader.html?is-external=true\" "
+ "title=\"class or interface in java.io\" class=\"externalLink\">FilterReader</a></code></dd>",
"<dt><span class=\"overrideSpecifyLabel\">Specified by:</span></dt>\n"
+ "<dd><code><a href=\"" + uri + "/java/io/DataInput.html?is-external=true#readInt()\" "
+ "<dd><code><a href=\"" + uri + "/java/io/DataInput.html?is-external=true#readInt--\" "
+ "title=\"class or interface in java.io\" class=\"externalLink\">readInt</a></code>&nbsp;in interface&nbsp;<code>"
+ "<a href=\"" + uri + "/java/io/DataInput.html?is-external=true\" "
+ "title=\"class or interface in java.io\" class=\"externalLink\">DataInput</a></code></dd>"
Expand Down
4 changes: 2 additions & 2 deletions test/langtools/jdk/javadoc/doclet/testHref/TestHref.java
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2023, 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
Expand Down Expand Up @@ -51,7 +51,7 @@ void test() {

checkOutput("pkg/C1.html", true,
//External link.
"href=\"http://java.sun.com/j2se/1.4/docs/api/java/lang/Object.html?is-external=true#wait(long,int)\"",
"href=\"http://java.sun.com/j2se/1.4/docs/api/java/lang/Object.html?is-external=true#wait-long-int-\"",
//Member summary table link.
"href=\"#method(int,int,java.util.ArrayList)\"",
//Anchor test.
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2002, 2023, 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
Expand Down Expand Up @@ -85,7 +85,7 @@ void test() {

checkOutput("pkg/B.html", true,
"<div class=\"block\">A method with html tag the method "
+ "<a href=\"" + url + "java/lang/ClassLoader.html?is-external=true#getSystemClassLoader()\""
+ "<a href=\"" + url + "java/lang/ClassLoader.html?is-external=true#getSystemClassLoader--\""
+ " title=\"class or interface in java.lang\" class=\"externalLink\"><code><tt>getSystemClassLoader()</tt>"
+ "</code></a> as the parent class loader.</div>",
"<div class=\"block\">is equivalent to invoking <code>"
Expand Down

1 comment on commit b9a29eb

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.