8187521: In some corner cases the javadoc tool can reuse id attribute

Reviewed-by: bpatel, ksrini
This commit is contained in:
Jonathan Gibbons 2017-10-10 17:02:52 -07:00
parent b890c3ce83
commit f386e419c3
8 changed files with 326 additions and 66 deletions

View file

@ -313,7 +313,8 @@ public abstract class AbstractExecutableMemberWriter extends AbstractMemberWrite
* @return the 1.4.x style anchor for the executable element.
*/
protected String getErasureAnchor(ExecutableElement executableElement) {
final StringBuilder buf = new StringBuilder(name(executableElement) + "(");
final StringBuilder buf = new StringBuilder(writer.anchorName(executableElement));
buf.append("(");
List<? extends VariableElement> parameters = executableElement.getParameters();
boolean foundTypeVariable = false;
for (int i = 0; i < parameters.size(); i++) {

View file

@ -33,6 +33,7 @@ import java.util.regex.Pattern;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.ModuleElement;
import javax.lang.model.element.Name;
@ -74,6 +75,7 @@ import jdk.javadoc.internal.doclets.formats.html.markup.HtmlDocument;
import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle;
import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTag;
import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree;
import jdk.javadoc.internal.doclets.formats.html.markup.HtmlVersion;
import jdk.javadoc.internal.doclets.formats.html.markup.RawHtml;
import jdk.javadoc.internal.doclets.formats.html.markup.StringContent;
import jdk.javadoc.internal.doclets.toolkit.AnnotationTypeWriter;
@ -1468,20 +1470,18 @@ public class HtmlDocletWriter extends HtmlDocWriter {
if (isProperty) {
return executableElement.getSimpleName().toString();
}
String signature = utils.signature(executableElement);
StringBuilder signatureParsed = new StringBuilder();
int counter = 0;
for (int i = 0; i < signature.length(); i++) {
char c = signature.charAt(i);
if (c == '<') {
counter++;
} else if (c == '>') {
counter--;
} else if (counter == 0) {
signatureParsed.append(c);
}
String member = anchorName(executableElement);
String erasedSignature = utils.makeSignature(executableElement, true, true);
return member + erasedSignature;
}
public String anchorName(Element member) {
if (member.getKind() == ElementKind.CONSTRUCTOR
&& configuration.isOutputHtml5()) {
return "<init>";
} else {
return utils.getSimpleName(member);
}
return utils.getSimpleName(executableElement) + signatureParsed.toString();
}
public Content seeTagToContent(Element element, DocTree see) {

View file

@ -59,7 +59,8 @@ public abstract class HtmlDocWriter extends HtmlWriter {
public static final String CONTENT_TYPE = "text/html";
DocPath pathToRoot;
private final HtmlConfiguration configuration;
private final DocPath pathToRoot;
/**
* Constructor. Initializes the destination file name through the super
@ -68,8 +69,9 @@ public abstract class HtmlDocWriter extends HtmlWriter {
* @param configuration the configuration for this doclet
* @param filename String file name.
*/
public HtmlDocWriter(BaseConfiguration configuration, DocPath filename) {
public HtmlDocWriter(HtmlConfiguration configuration, DocPath filename) {
super(configuration, filename);
this.configuration = configuration;
this.pathToRoot = filename.parent().invert();
Messages messages = configuration.getMessages();
messages.notice("doclet.Generating_0",
@ -80,7 +82,9 @@ public abstract class HtmlDocWriter extends HtmlWriter {
* Accessor for configuration.
* @return the configuration for this doclet
*/
public abstract BaseConfiguration configuration();
public BaseConfiguration configuration() {
return configuration;
}
public Content getHyperLink(DocPath link, String label) {
return getHyperLink(link, new StringContent(label), false, "", "", "");
@ -166,8 +170,6 @@ public abstract class HtmlDocWriter extends HtmlWriter {
* @return a valid HTML name string.
*/
public String getName(String name) {
StringBuilder sb = new StringBuilder();
char ch;
/* 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
@ -178,8 +180,14 @@ public abstract class HtmlDocWriter extends HtmlWriter {
* 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 (configuration.isOutputHtml5()) {
return name.replaceAll(" +", "");
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < name.length(); i++) {
ch = name.charAt(i);
char ch = name.charAt(i);
switch (ch) {
case '(':
case ')':

View file

@ -181,36 +181,63 @@ public class HtmlTree extends Content {
return s;
}
/**
* A set of ASCII URI characters to be left unencoded.
/*
* The sets of ASCII URI characters to be left unencoded.
* See "Uniform Resource Identifier (URI): Generic Syntax"
* IETF RFC 3986. https://tools.ietf.org/html/rfc3986
*/
public static final BitSet NONENCODING_CHARS = new BitSet(256);
public static final BitSet MAIN_CHARS;
public static final BitSet QUERY_FRAGMENT_CHARS;
static {
// alphabetic characters
for (int i = 'a'; i <= 'z'; i++) {
NONENCODING_CHARS.set(i);
}
for (int i = 'A'; i <= 'Z'; i++) {
NONENCODING_CHARS.set(i);
}
// numeric characters
for (int i = '0'; i <= '9'; i++) {
NONENCODING_CHARS.set(i);
}
// Reserved characters as per RFC 3986. These are set of delimiting characters.
String noEnc = ":/?#[]@!$&'()*+,;=";
// Unreserved characters as per RFC 3986 which should not be percent encoded.
noEnc += "-._~";
for (int i = 0; i < noEnc.length(); i++) {
NONENCODING_CHARS.set(noEnc.charAt(i));
}
BitSet alphaDigit = bitSet(bitSet('A', 'Z'), bitSet('a', 'z'), bitSet('0', '9'));
BitSet unreserved = bitSet(alphaDigit, bitSet("-._~"));
BitSet genDelims = bitSet(":/?#[]@");
BitSet subDelims = bitSet("!$&'()*+,;=");
MAIN_CHARS = bitSet(unreserved, genDelims, subDelims);
BitSet pchar = bitSet(unreserved, subDelims, bitSet(":@"));
QUERY_FRAGMENT_CHARS = bitSet(pchar, bitSet("/?"));
}
private static BitSet bitSet(String s) {
BitSet result = new BitSet();
for (int i = 0; i < s.length(); i++) {
result.set(s.charAt(i));
}
return result;
}
private static BitSet bitSet(char from, char to) {
BitSet result = new BitSet();
result.set(from, to + 1);
return result;
}
private static BitSet bitSet(BitSet... sets) {
BitSet result = new BitSet();
for (BitSet set : sets) {
result.or(set);
}
return result;
}
/**
* Apply percent-encoding to a URL.
* This is similar to {@link java.net.URLEncoder} but
* is less aggressive about encoding some characters,
* like '(', ')', ',' which are used in the anchor
* names for Java methods in HTML5 mode.
*/
private static String encodeURL(String url) {
BitSet nonEncodingChars = MAIN_CHARS;
StringBuilder sb = new StringBuilder();
for (byte c : url.getBytes(Charset.forName("UTF-8"))) {
if (NONENCODING_CHARS.get(c & 0xFF)) {
if (c == '?' || c == '#') {
sb.append((char) c);
// switch to the more restrictive set inside
// the query and/or fragment
nonEncodingChars = QUERY_FRAGMENT_CHARS;
} else if (nonEncodingChars.get(c & 0xFF)) {
sb.append((char) c);
} else {
sb.append(String.format("%%%02X", c & 0xFF));