8325088: Overloads that differ in type parameters may be lost

Reviewed-by: jjg
This commit is contained in:
Pavel Rappo 2024-04-08 09:50:35 +00:00
parent 6f087cbcd5
commit 7c66465763
8 changed files with 516 additions and 43 deletions

View File

@ -100,7 +100,7 @@ public class AnnotationTypeMemberWriter extends AbstractMemberWriter {
buildAnnotationTypeMemberChildren(div);
annotationContent.add(div);
memberList.add(writer.getMemberListItem(annotationContent));
writer.tableOfContents.addLink(htmlIds.forMember(typeElement, (ExecutableElement) member),
writer.tableOfContents.addLink(htmlIds.forMember((ExecutableElement) member).getFirst(),
Text.of(name(member)));
}
Content annotationDetails = getAnnotationDetails(annotationDetailsHeader, memberList);
@ -210,7 +210,7 @@ public class AnnotationTypeMemberWriter extends AbstractMemberWriter {
Text.of(name(member)));
content.add(heading);
return HtmlTree.SECTION(HtmlStyle.detail, content)
.setId(htmlIds.forMember(typeElement, (ExecutableElement) member));
.setId(htmlIds.forMember((ExecutableElement) member).getFirst());
}
protected Content getSignature(Element member) {

View File

@ -116,7 +116,7 @@ public class ConstructorWriter extends AbstractExecutableMemberWriter {
buildTagInfo(div);
constructorContent.add(div);
memberList.add(getMemberListItem(constructorContent));
writer.tableOfContents.addLink(htmlIds.forMember(currentConstructor),
writer.tableOfContents.addLink(htmlIds.forMember(currentConstructor).getFirst(),
Text.of(utils.getSimpleName(constructor)
+ utils.makeSignature(currentConstructor, typeElement, false, true)));
}
@ -189,13 +189,14 @@ public class ConstructorWriter extends AbstractExecutableMemberWriter {
Content content = new ContentBuilder();
var heading = HtmlTree.HEADING(Headings.TypeDeclaration.MEMBER_HEADING,
Text.of(name(constructor)));
HtmlId erasureAnchor = htmlIds.forErasure(constructor);
if (erasureAnchor != null) {
heading.setId(erasureAnchor);
var anchors = htmlIds.forMember(constructor);
if (anchors.size() > 1) {
heading.setId(anchors.getLast());
}
content.add(heading);
return HtmlTree.SECTION(HtmlStyle.detail, content)
.setId(htmlIds.forMember(constructor));
.setId(anchors.getFirst());
}
protected Content getSignature(ExecutableElement constructor) {

View File

@ -1037,7 +1037,7 @@ public abstract class HtmlDocletWriter {
if (utils.isExecutableElement(element)) {
ExecutableElement ee = (ExecutableElement)element;
HtmlId id = isProperty ? htmlIds.forProperty(ee) : htmlIds.forMember(ee);
HtmlId id = isProperty ? htmlIds.forProperty(ee) : htmlIds.forMember(ee).getFirst();
return getLink(new HtmlLinkInfo(configuration, context, typeElement)
.label(label)
.fragment(id.name())

View File

@ -25,11 +25,16 @@
package jdk.javadoc.internal.doclets.formats.html;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
@ -159,34 +164,89 @@ public class HtmlIds {
}
/**
* Returns an id for an executable element, suitable for use when the
* simple name and argument list will be unique within the page, such as
* in the page for the declaration of the enclosing class or interface.
* {@return a non-empty list of ids to a constructor or a method}
* The ids from the returned list are alternative: the given constructor
* or method can be equally referred to by any of those ids.
*
* @param element the element
*
* @return the id
* @param executable a constructor or method
*/
HtmlId forMember(ExecutableElement element) {
List<HtmlId> forMember(ExecutableElement executable) {
var htmlId = ids.get(executable);
if (htmlId != null)
return htmlId;
if (executable.getKind() != ElementKind.CONSTRUCTOR
&& executable.getKind() != ElementKind.METHOD)
throw new IllegalArgumentException(String.valueOf(executable.getKind()));
var vmt = configuration.getVisibleMemberTable((TypeElement) executable.getEnclosingElement());
var ctors = vmt.getVisibleMembers(VisibleMemberTable.Kind.CONSTRUCTORS);
var methods = vmt.getVisibleMembers(VisibleMemberTable.Kind.METHODS);
record Erased(ExecutableElement element, HtmlId id) { }
// split elements into two buckets:
// - elements whose erased id is present
// - elements whose erased id is absent (i.e. is null)
enum ErasedId { PRESENT, ABSENT }
var buckets = Stream.concat(ctors.stream(), methods.stream())
.map(e -> (ExecutableElement) e)
.map(e -> new Erased(e, forErasure(e)))
.collect(Collectors.groupingBy(erased -> erased.id == null ?
ErasedId.ABSENT : ErasedId.PRESENT));
var dups = new HashSet<String>();
// the order of elements in each bucket is important for reproducibility
// of ids: the same executable element must have the same id in any
// javadoc run
// Use simple id, unless we have to use erased id; for that, do the
// following _in order_:
// 1. Map all elements that can _only_ be addressed by the simple id
for (var e : buckets.getOrDefault(ErasedId.ABSENT, List.of())) {
var simpleId = forMember0(e.element);
ids.put(e.element, List.of(simpleId));
boolean added = dups.add(simpleId.name());
// we assume that the simple id for an executable member that
// does not use type parameters is unique
assert added;
}
// 2. Map all elements that can be addressed by simple id or erased id;
// if the simple id is not yet used, use it, otherwise use the erased id
for (var e : buckets.getOrDefault(ErasedId.PRESENT, List.of())) {
var simpleId = forMember0(e.element);
if (dups.add(simpleId.name())) {
ids.put(e.element, List.of(simpleId, e.id));
} else {
ids.put(e.element, List.of(e.id));
boolean added = dups.add(e.id.name());
// Not only must an erased id not clash with any simple id,
// but it must also not clash with any other erased id.
// The latter is because JLS 8.4.2. Method Signature:
// it is a compile-time error to declare two methods
// with override-equivalent signatures in a class
assert added;
}
}
// Safety net: if for whatever reason we cannot find the element
// among those we just expanded, return the simple id. It might
// not be always right, but at least it won't fail.
//
// - one example where it might happen is linking to an inherited
// undocumented method (see test case T5093723)
// TODO the above will need to be revisited if and when we redesign
// VisibleMemberTable, which currently cannot correctly return the
// owner of such a method
//
// - another example is annotation interface methods: they are not
// included in VisibleMemberTable.Kind.METHODS and so cannot be
// found among them
return ids.computeIfAbsent(executable, e -> List.of(forMember0(e)));
}
private final Map<ExecutableElement, List<HtmlId>> ids = new HashMap<>();
private HtmlId forMember0(ExecutableElement element) {
String a = element.getSimpleName()
+ utils.makeSignature(element, null, true, true);
// utils.makeSignature includes spaces
return HtmlId.of(a.replaceAll("\\s", ""));
}
/**
* Returns an id for an executable element, including the context
* of its documented enclosing class or interface.
*
* @param typeElement the enclosing class or interface
* @param member the element
*
* @return the id
*/
HtmlId forMember(TypeElement typeElement, ExecutableElement member) {
return HtmlId.of(utils.getSimpleName(member) + utils.signature(member, typeElement));
}
/**
* Returns an id for a field, suitable for use when the simple name
* will be unique within the page, such as in the page for the
@ -229,7 +289,7 @@ public class HtmlIds {
* @param executableElement the element to anchor to
* @return the 1.4.x style anchor for the executable element
*/
protected HtmlId forErasure(ExecutableElement executableElement) {
private HtmlId forErasure(ExecutableElement executableElement) {
final StringBuilder buf = new StringBuilder(executableElement.getSimpleName().toString());
buf.append("(");
List<? extends VariableElement> parameters = executableElement.getParameters();
@ -483,7 +543,7 @@ public class HtmlIds {
*/
public HtmlId forPreviewSection(Element el) {
return HtmlId.of("preview-" + switch (el.getKind()) {
case CONSTRUCTOR, METHOD -> forMember((ExecutableElement) el).name();
case CONSTRUCTOR, METHOD -> forMember((ExecutableElement) el).getFirst().name();
case PACKAGE -> forPackage((PackageElement) el).name();
default -> utils.getFullyQualifiedName(el, false);
});
@ -497,7 +557,7 @@ public class HtmlIds {
* @return the id
*/
public HtmlId forRestrictedSection(ExecutableElement el) {
return HtmlId.of("restricted-" + forMember(el).name());
return HtmlId.of("restricted-" + forMember(el).getFirst().name());
}
/**

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1998, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2024, 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
@ -128,7 +128,7 @@ public class HtmlIndexBuilder extends IndexBuilder {
item.setContainingModule(utils.getFullyQualifiedName(utils.containingModule(element)));
}
if (utils.isExecutableElement(element)) {
String url = HtmlTree.encodeURL(htmlIds.forMember((ExecutableElement) element).name());
String url = HtmlTree.encodeURL(htmlIds.forMember((ExecutableElement) element).getFirst().name());
if (!url.equals(item.getLabel())) {
item.setUrl(url);
}

View File

@ -116,7 +116,7 @@ public class MethodWriter extends AbstractExecutableMemberWriter {
buildTagInfo(div);
methodContent.add(div);
memberList.add(writer.getMemberListItem(methodContent));
writer.tableOfContents.addLink(htmlIds.forMember(currentMethod),
writer.tableOfContents.addLink(htmlIds.forMember(currentMethod).getFirst(),
Text.of(utils.getSimpleName(method)
+ utils.makeSignature(currentMethod, typeElement, false, true)));
}
@ -204,13 +204,13 @@ public class MethodWriter extends AbstractExecutableMemberWriter {
Content content = new ContentBuilder();
var heading = HtmlTree.HEADING(Headings.TypeDeclaration.MEMBER_HEADING,
Text.of(name(method)));
HtmlId erasureAnchor;
if ((erasureAnchor = htmlIds.forErasure(method)) != null) {
heading.setId(erasureAnchor);
var anchors = htmlIds.forMember(method);
if (anchors.size() > 1) {
heading.setId(anchors.getLast());
}
content.add(heading);
return HtmlTree.SECTION(HtmlStyle.detail, content)
.setId(htmlIds.forMember(method));
.setId(anchors.getFirst());
}
protected Content getSignature(ExecutableElement method) {
@ -375,7 +375,7 @@ public class MethodWriter extends AbstractExecutableMemberWriter {
var codeOverriddenTypeLink = HtmlTree.CODE(overriddenTypeLink);
Content methlink = writer.getLink(
new HtmlLinkInfo(writer.configuration, HtmlLinkInfo.Kind.PLAIN, holder)
.fragment(writer.htmlIds.forMember(method).name())
.fragment(writer.htmlIds.forMember(method).getFirst().name())
.label(method.getSimpleName()));
var codeMethLink = HtmlTree.CODE(methlink);
var dd = HtmlTree.DD(codeMethLink);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 2024, 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
@ -32,12 +32,14 @@ import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.ModuleElement;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.SimpleElementVisitor14;
import javax.lang.model.util.SimpleTypeVisitor9;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
@ -143,7 +145,11 @@ public class Comparators {
if (result != 0) {
return result;
}
return compareModuleNames(e1, e2);
result = compareModuleNames(e1, e2);
if (result != 0) {
return result;
}
return compareTypeParameters(e1, e2);
}
};
}
@ -264,7 +270,10 @@ public class Comparators {
result = compareFullyQualifiedNames(e1, e2);
if (result != 0)
return result;
return compareModuleNames(e1, e2);
result = compareModuleNames(e1, e2);
if (result != 0)
return result;
return compareTypeParameters(e1, e2);
}
};
}
@ -364,7 +373,14 @@ public class Comparators {
if (result != 0) {
return result;
}
return compareModuleNames(e1, e2);
result = compareModuleNames(e1, e2);
if (result != 0) {
return result;
}
// this might not be needed: if e1 != e2 and both are
// executables they must differ in FQN and thus we
// shouldn't reach here
return compareTypeParameters(e1, e2);
}
};
}
@ -423,6 +439,21 @@ public class Comparators {
}.visit(t);
}
protected final int compareTypeParameters(Element e1, Element e2) {
if (!e1.getKind().isExecutable() || !e2.getKind().isExecutable())
return 0;
var typeParameters1 = ((ExecutableElement) e1).getTypeParameters();
var typeParameters2 = ((ExecutableElement) e2).getTypeParameters();
var parameters1 = typeParameters1.toArray(new TypeParameterElement[0]);
var parameters2 = typeParameters2.toArray(new TypeParameterElement[0]);
return Arrays.compare(parameters1, parameters2, (p1, p2) -> {
var bounds1 = p1.getBounds().toArray(new TypeMirror[0]);
var bounds2 = p2.getBounds().toArray(new TypeMirror[0]);
return Arrays.compare(bounds1, bounds2, (b1, b2) ->
utils.compareStrings(true, b1.toString(), b2.toString()));
});
}
/**
* Compares two Elements, typically the name of a method,
* field or constructor.

View File

@ -0,0 +1,381 @@
/*
* Copyright (c) 2024, 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 8297879
* @library /tools/lib ../../lib
* @modules jdk.javadoc/jdk.javadoc.internal.tool
* @build toolbox.ToolBox javadoc.tester.*
* @run main TestErasure
*/
import java.io.IOException;
import java.nio.file.Path;
import javadoc.tester.JavadocTester;
import toolbox.ToolBox;
public class TestErasure extends JavadocTester {
private final ToolBox tb = new ToolBox();
public static void main(String... args) throws Exception {
new TestErasure().runTests();
}
/*
* Create confusion between:
* - a constructor/method type parameter and a like-named class
* - similarly named but differently bounded constructor type parameters
* - similarly named but differently bounded type parameter in methods
*/
@Test
public void test1(Path base) throws IOException {
Path src = base.resolve("src");
// - put public class first so that writeJavaFiles is not confused
// on the name of the file it should create
//
// - an _abstract_ class is used only for convenience: like an interface,
// it allows to keep the test minimal, but unlike an interface, it
// allows to test constructors
tb.writeJavaFiles(src, """
public abstract class Foo {
public Foo(T arg) { }
public <T extends X> Foo(T arg) { }
public <T extends Y> Foo(T arg) { }
public abstract T m(T arg);
public abstract <T extends X> T m(T arg);
public abstract <T extends Y> T m(T arg);
}
class T { }
class X { }
class Y { }
""");
javadoc("-d", base.resolve("out").toString(),
src.resolve("Foo.java").toString());
checkExit(Exit.OK);
// constructors
checkOutput("Foo.html", true, """
<section class="constructor-summary" id="constructor-summary">
<h2>Constructor Summary</h2>
<div class="caption"><span>Constructors</span></div>
<div class="summary-table two-column-summary">
<div class="table-header col-first">Constructor</div>
<div class="table-header col-last">Description</div>
<div class="col-constructor-name even-row-color"><code>\
<a href="#%3Cinit%3E(T)" class="member-name-link">Foo</a><wbr>(T&nbsp;arg)</code></div>
<div class="col-last even-row-color">&nbsp;</div>
<div class="col-constructor-name odd-row-color"><code>\
<a href="#%3Cinit%3E(X)" class="member-name-link">Foo</a><wbr>(T&nbsp;arg)</code></div>
<div class="col-last odd-row-color">&nbsp;</div>
<div class="col-constructor-name even-row-color"><code>\
<a href="#%3Cinit%3E(Y)" class="member-name-link">Foo</a><wbr>(T&nbsp;arg)</code></div>
<div class="col-last even-row-color">&nbsp;</div>
</div>
</section>""");
checkOutput("Foo.html", true, """
<li><a href="#constructor-detail" tabindex="0">Constructor Details</a>
<ol class="toc-list">
<li><a href="#%3Cinit%3E(T)" tabindex="0">Foo(T)</a></li>
<li><a href="#%3Cinit%3E(X)" tabindex="0">Foo(T)</a></li>
<li><a href="#%3Cinit%3E(Y)" tabindex="0">Foo(T)</a></li>
</ol>
</li>""");
checkOutput("index-all.html", true, """
<dt><a href="Foo.html#%3Cinit%3E(T)" class="member-name-link">Foo(T)</a>\
- Constructor for class <a href="Foo.html" title="class in Unnamed Package">Foo</a></dt>
<dd>&nbsp;</dd>
<dt><a href="Foo.html#%3Cinit%3E(X)" class="member-name-link">Foo(T)</a>\
- Constructor for class <a href="Foo.html" title="class in Unnamed Package">Foo</a></dt>
<dd>&nbsp;</dd>
<dt><a href="Foo.html#%3Cinit%3E(Y)" class="member-name-link">Foo(T)</a>\
- Constructor for class <a href="Foo.html" title="class in Unnamed Package">Foo</a></dt>
<dd>&nbsp;</dd>""");
checkOutput("member-search-index.js", true, """
{"p":"<Unnamed>","c":"Foo","l":"Foo(T)","u":"%3Cinit%3E(T)"},\
{"p":"<Unnamed>","c":"Foo","l":"Foo(T)","u":"%3Cinit%3E(X)"},\
{"p":"<Unnamed>","c":"Foo","l":"Foo(T)","u":"%3Cinit%3E(Y)"}""");
// methods
checkOutput("Foo.html", true, """
<div class="col-first even-row-color method-summary-table method-summary-table-tab2 \
method-summary-table-tab3"><code>abstract T</code></div>
<div class="col-second even-row-color method-summary-table method-summary-table-tab2 \
method-summary-table-tab3"><code><a href="#m(T)" class="member-name-link">m</a><wbr>(T&nbsp;arg)</code></div>
<div class="col-last even-row-color method-summary-table method-summary-table-tab2 \
method-summary-table-tab3">&nbsp;</div>
<div class="col-first odd-row-color method-summary-table method-summary-table-tab2 \
method-summary-table-tab3"><code>abstract &lt;T extends X&gt;<br>T</code></div>
<div class="col-second odd-row-color method-summary-table method-summary-table-tab2 \
method-summary-table-tab3"><code><a href="#m(X)" class="member-name-link">m</a><wbr>(T&nbsp;arg)</code></div>
<div class="col-last odd-row-color method-summary-table method-summary-table-tab2 \
method-summary-table-tab3">&nbsp;</div>
<div class="col-first even-row-color method-summary-table method-summary-table-tab2 \
method-summary-table-tab3"><code>abstract &lt;T extends Y&gt;<br>T</code></div>
<div class="col-second even-row-color method-summary-table method-summary-table-tab2 \
method-summary-table-tab3"><code><a href="#m(Y)" class="member-name-link">m</a><wbr>(T&nbsp;arg)</code></div>
<div class="col-last even-row-color method-summary-table method-summary-table-tab2 \
method-summary-table-tab3">&nbsp;</div>""");
checkOutput("Foo.html", true, """
<li><a href="#method-detail" tabindex="0">Method Details</a>
<ol class="toc-list">
<li><a href="#m(T)" tabindex="0">m(T)</a></li>
<li><a href="#m(X)" tabindex="0">m(T)</a></li>
<li><a href="#m(Y)" tabindex="0">m(T)</a></li>
</ol>
</li>""");
checkOutput("index-all.html", true, """
<dt><a href="Foo.html#m(T)" class="member-name-link">m(T)</a>\
- Method in class <a href="Foo.html" title="class in Unnamed Package">Foo</a></dt>
<dd>&nbsp;</dd>
<dt><a href="Foo.html#m(X)" class="member-name-link">m(T)</a>\
- Method in class <a href="Foo.html" title="class in Unnamed Package">Foo</a></dt>
<dd>&nbsp;</dd>
<dt><a href="Foo.html#m(Y)" class="member-name-link">m(T)</a>\
- Method in class <a href="Foo.html" title="class in Unnamed Package">Foo</a></dt>
<dd>&nbsp;</dd>""");
checkOutput("member-search-index.js", true, """
{"p":"<Unnamed>","c":"Foo","l":"m(T)"},\
{"p":"<Unnamed>","c":"Foo","l":"m(T)","u":"m(X)"},\
{"p":"<Unnamed>","c":"Foo","l":"m(T)","u":"m(Y)"}""");
}
/*
* Create confusion between the class type parameter
* and a like-named constructor/method type parameter.
*/
@Test
public void test2(Path base) throws IOException {
Path src = base.resolve("src");
tb.writeJavaFiles(src, """
public abstract class Foo<T> {
public Foo(T arg) { }
public <T extends X> Foo(T arg) { }
public abstract T m(T arg);
public abstract <T extends X> T m(T arg);
}
class X { }
""");
javadoc("-d", base.resolve("out").toString(),
src.resolve("Foo.java").toString());
checkExit(Exit.OK);
// constructors
checkOutput("Foo.html", true, """
<section class="constructor-summary" id="constructor-summary">
<h2>Constructor Summary</h2>
<div class="caption"><span>Constructors</span></div>
<div class="summary-table two-column-summary">
<div class="table-header col-first">Constructor</div>
<div class="table-header col-last">Description</div>
<div class="col-constructor-name even-row-color"><code>\
<a href="#%3Cinit%3E(T)" class="member-name-link">Foo</a>\
<wbr>(<a href="Foo.html" title="type parameter in Foo">T</a>&nbsp;arg)</code></div>
<div class="col-last even-row-color">&nbsp;</div>
<div class="col-constructor-name odd-row-color"><code>\
<a href="#%3Cinit%3E(X)" class="member-name-link">Foo</a><wbr>(T&nbsp;arg)</code></div>
<div class="col-last odd-row-color">&nbsp;</div>
</div>
</section>""");
checkOutput("Foo.html", true, """
<li><a href="#constructor-detail" tabindex="0">Constructor Details</a>
<ol class="toc-list">
<li><a href="#%3Cinit%3E(T)" tabindex="0">Foo(T)</a></li>
<li><a href="#%3Cinit%3E(X)" tabindex="0">Foo(T)</a></li>
</ol>
</li>""");
checkOutput("index-all.html", true, """
<dt><a href="Foo.html#%3Cinit%3E(T)" class="member-name-link">Foo(T)</a>\
- Constructor for class <a href="Foo.html" title="class in Unnamed Package">Foo</a></dt>
<dd>&nbsp;</dd>
<dt><a href="Foo.html#%3Cinit%3E(X)" class="member-name-link">Foo(T)</a>\
- Constructor for class <a href="Foo.html" title="class in Unnamed Package">Foo</a></dt>
<dd>&nbsp;</dd>""");
checkOutput("member-search-index.js", true, """
{"p":"<Unnamed>","c":"Foo","l":"Foo(T)","u":"%3Cinit%3E(T)"},\
{"p":"<Unnamed>","c":"Foo","l":"Foo(T)","u":"%3Cinit%3E(X)"}""");
// methods
checkOutput("Foo.html", true, """
<div class="col-first even-row-color method-summary-table method-summary-table-tab2 \
method-summary-table-tab3"><code>abstract <a href="Foo.html" title="type parameter in Foo">T</a></code></div>
<div class="col-second even-row-color method-summary-table method-summary-table-tab2 \
method-summary-table-tab3"><code><a href="#m(T)" class="member-name-link">m</a>\
<wbr>(<a href="Foo.html" title="type parameter in Foo">T</a>&nbsp;arg)</code></div>
<div class="col-last even-row-color method-summary-table method-summary-table-tab2 \
method-summary-table-tab3">&nbsp;</div>
<div class="col-first odd-row-color method-summary-table method-summary-table-tab2 \
method-summary-table-tab3"><code>abstract &lt;T extends X&gt;<br>T</code></div>
<div class="col-second odd-row-color method-summary-table method-summary-table-tab2 \
method-summary-table-tab3"><code><a href="#m(X)" class="member-name-link">m</a><wbr>(T&nbsp;arg)</code></div>
<div class="col-last odd-row-color method-summary-table method-summary-table-tab2 \
method-summary-table-tab3">&nbsp;</div>""");
checkOutput("Foo.html", true, """
<li><a href="#method-detail" tabindex="0">Method Details</a>
<ol class="toc-list">
<li><a href="#m(T)" tabindex="0">m(T)</a></li>
<li><a href="#m(X)" tabindex="0">m(T)</a></li>
</ol>
</li>""");
checkOutput("index-all.html", true, """
<dt><a href="Foo.html#m(T)" class="member-name-link">m(T)</a>\
- Method in class <a href="Foo.html" title="class in Unnamed Package">Foo</a></dt>
<dd>&nbsp;</dd>
<dt><a href="Foo.html#m(X)" class="member-name-link">m(T)</a>\
- Method in class <a href="Foo.html" title="class in Unnamed Package">Foo</a></dt>
<dd>&nbsp;</dd>""");
checkOutput("member-search-index.js", true, """
{"p":"<Unnamed>","c":"Foo","l":"m(T)"},\
{"p":"<Unnamed>","c":"Foo","l":"m(T)","u":"m(X)"}""");
}
@Test
public void testNewAndDeprecated(Path base) throws IOException {
Path src = base.resolve("src");
tb.writeJavaFiles(src, """
public abstract class Foo {
/** @since today */
@Deprecated(since="tomorrow")
public Foo(T arg) { }
/** @since today */
@Deprecated(since="tomorrow")
public <T extends X> Foo(T arg) { }
/** @since today */
@Deprecated(since="tomorrow")
public <T extends Y> Foo(T arg) { }
/** @since today */
@Deprecated(since="tomorrow")
public abstract T m(T arg);
/** @since today */
@Deprecated(since="tomorrow")
public abstract <T extends X> T m(T arg);
/** @since today */
@Deprecated(since="tomorrow")
public abstract <T extends Y> T m(T arg);
}
class T { }
class X { }
class Y { }
""");
javadoc("-d", base.resolve("out").toString(),
"--since", "today",
src.resolve("Foo.java").toString());
checkExit(Exit.OK);
checkOutput("new-list.html", true, """
<div class="col-summary-item-name even-row-color"><a href="Foo.html#m(T)">Foo.m<wbr>(T)</a></div>
<div class="col-second even-row-color">today</div>
<div class="col-last even-row-color">&nbsp;</div>
<div class="col-summary-item-name odd-row-color"><a href="Foo.html#m(X)">Foo.m<wbr>(T)</a></div>
<div class="col-second odd-row-color">today</div>
<div class="col-last odd-row-color">&nbsp;</div>
<div class="col-summary-item-name even-row-color"><a href="Foo.html#m(Y)">Foo.m<wbr>(T)</a></div>
<div class="col-second even-row-color">today</div>
<div class="col-last even-row-color">&nbsp;</div>""");
checkOutput("new-list.html", true, """
<div class="col-summary-item-name even-row-color"><a href="Foo.html#%3Cinit%3E(T)">Foo<wbr>(T)</a></div>
<div class="col-second even-row-color">today</div>
<div class="col-last even-row-color">&nbsp;</div>
<div class="col-summary-item-name odd-row-color"><a href="Foo.html#%3Cinit%3E(X)">Foo<wbr>(T)</a></div>
<div class="col-second odd-row-color">today</div>
<div class="col-last odd-row-color">&nbsp;</div>
<div class="col-summary-item-name even-row-color"><a href="Foo.html#%3Cinit%3E(Y)">Foo<wbr>(T)</a></div>
<div class="col-second even-row-color">today</div>
<div class="col-last even-row-color">&nbsp;</div>""");
checkOutput("deprecated-list.html", true, """
<div class="col-summary-item-name even-row-color"><a href="Foo.html#m(T)">Foo.m<wbr>(T)</a></div>
<div class="col-second even-row-color">tomorrow</div>
<div class="col-last even-row-color"></div>
<div class="col-summary-item-name odd-row-color"><a href="Foo.html#m(X)">Foo.m<wbr>(T)</a></div>
<div class="col-second odd-row-color">tomorrow</div>
<div class="col-last odd-row-color"></div>
<div class="col-summary-item-name even-row-color"><a href="Foo.html#m(Y)">Foo.m<wbr>(T)</a></div>
<div class="col-second even-row-color">tomorrow</div>
<div class="col-last even-row-color"></div>""");
checkOutput("deprecated-list.html", true, """
<div class="col-summary-item-name even-row-color"><a href="Foo.html#%3Cinit%3E(T)">Foo<wbr>(T)</a></div>
<div class="col-second even-row-color">tomorrow</div>
<div class="col-last even-row-color"></div>
<div class="col-summary-item-name odd-row-color"><a href="Foo.html#%3Cinit%3E(X)">Foo<wbr>(T)</a></div>
<div class="col-second odd-row-color">tomorrow</div>
<div class="col-last odd-row-color"></div>
<div class="col-summary-item-name even-row-color"><a href="Foo.html#%3Cinit%3E(Y)">Foo<wbr>(T)</a></div>
<div class="col-second even-row-color">tomorrow</div>
<div class="col-last even-row-color"></div>""");
}
@Test
public void testPreview(Path base) throws IOException {
// unlike that for other tests, here we cannot simulate ambiguity between
// a type parameter and a like-named class, because for that the class
// needs to be in the unnamed package, otherwise its FQN won't be T
Path src = base.resolve("src");
tb.writeJavaFiles(src, """
package p;
import jdk.internal.javac.PreviewFeature;
public abstract class Foo {
@PreviewFeature(feature=PreviewFeature.Feature.TEST)
public <T extends X> Foo(T arg) { }
@PreviewFeature(feature=PreviewFeature.Feature.TEST)
public <T extends Y> Foo(T arg) { }
@PreviewFeature(feature=PreviewFeature.Feature.TEST)
public abstract <T extends X> T m(T arg);
@PreviewFeature(feature=PreviewFeature.Feature.TEST)
public abstract <T extends Y> T m(T arg);
}
class X { }
class Y { }
""");
javadoc("-d", base.resolve("out").toString(),
"--patch-module", "java.base=" + src.toAbsolutePath().toString(),
src.resolve("p").resolve("Foo.java").toString());
checkExit(Exit.OK);
checkOutput("preview-list.html", true, """
<div class="col-summary-item-name even-row-color method method-tab1">\
<a href="java.base/p/Foo.html#m(T)">p.Foo.m<wbr>(T)</a><sup>\
<a href="java.base/p/Foo.html#preview-m(T)">PREVIEW</a></sup></div>
<div class="col-second even-row-color method method-tab1">Test Feature</div>
<div class="col-last even-row-color method method-tab1"></div>
<div class="col-summary-item-name odd-row-color method method-tab1">\
<a href="java.base/p/Foo.html#m(p.Y)">p.Foo.m<wbr>(T)</a><sup>\
<a href="java.base/p/Foo.html#preview-m(p.Y)">PREVIEW</a></sup></div>
<div class="col-second odd-row-color method method-tab1">Test Feature</div>
<div class="col-last odd-row-color method method-tab1"></div>""");
checkOutput("preview-list.html", true, """
<div class="col-summary-item-name even-row-color constructor constructor-tab1">\
<a href="java.base/p/Foo.html#%3Cinit%3E(T)">p.Foo<wbr>(T)</a><sup>\
<a href="java.base/p/Foo.html#preview-%3Cinit%3E(T)">PREVIEW</a></sup></div>
<div class="col-second even-row-color constructor constructor-tab1">Test Feature</div>
<div class="col-last even-row-color constructor constructor-tab1"></div>
<div class="col-summary-item-name odd-row-color constructor constructor-tab1">\
<a href="java.base/p/Foo.html#%3Cinit%3E(p.Y)">p.Foo<wbr>(T)</a><sup>\
<a href="java.base/p/Foo.html#preview-%3Cinit%3E(p.Y)">PREVIEW</a></sup></div>
<div class="col-second odd-row-color constructor constructor-tab1">Test Feature</div>
<div class="col-last odd-row-color constructor constructor-tab1"></div>""");
}
}