6509045: {@inheritDoc} only copies one instance of the specified exception

Reviewed-by: jjg
This commit is contained in:
Pavel Rappo 2022-07-06 22:01:12 +00:00
parent 9a0fa82424
commit 8f24d25168
2 changed files with 269 additions and 10 deletions

View File

@ -25,6 +25,7 @@
package jdk.javadoc.internal.doclets.toolkit.taglets;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
@ -32,7 +33,6 @@ import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.lang.model.element.Element;
@ -160,22 +160,21 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet {
var utils = writer.configuration().utils;
Content result = writer.getOutputInstance();
var documentedInThisCall = new HashSet<String>();
for (Entry<ThrowsTree, ExecutableElement> entry : throwsTags.entrySet()) {
Element e = entry.getValue();
Map<ThrowsTree, ExecutableElement> flattenedExceptions = flatten(throwsTags, writer);
flattenedExceptions.forEach((ThrowsTree t, ExecutableElement e) -> {
var ch = utils.getCommentHelper(e);
ThrowsTree tag = entry.getKey();
Element te = ch.getException(tag);
String excName = tag.getExceptionName().toString();
Element te = ch.getException(t);
String excName = t.getExceptionName().toString();
TypeMirror substituteType = typeSubstitutions.get(excName);
if (alreadyDocumented.contains(excName)
|| (te != null && alreadyDocumented.contains(utils.getFullyQualifiedName(te, false)))
|| (substituteType != null && alreadyDocumented.contains(substituteType.toString()))) {
continue;
return;
}
if (alreadyDocumented.isEmpty() && documentedInThisCall.isEmpty()) {
result.add(writer.getThrowsHeader());
}
result.add(writer.throwsTagOutput(e, tag, substituteType));
result.add(writer.throwsTagOutput(e, t, substituteType));
if (substituteType != null) {
documentedInThisCall.add(substituteType.toString());
} else {
@ -183,11 +182,57 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet {
? utils.getFullyQualifiedName(te, false)
: excName);
}
}
});
alreadyDocumented.addAll(documentedInThisCall);
return result;
}
/*
* A single @throws tag from an overriding method can correspond to multiple
* @throws tags from an overridden method.
*/
private Map<ThrowsTree, ExecutableElement> flatten(Map<ThrowsTree, ExecutableElement> throwsTags,
TagletWriter writer) {
Map<ThrowsTree, ExecutableElement> result = new LinkedHashMap<>();
throwsTags.forEach((tag, taggedElement) -> {
var expandedTags = expand(tag, taggedElement, writer);
assert Collections.disjoint(result.entrySet(), expandedTags.entrySet());
result.putAll(expandedTags);
});
return result;
}
private Map<ThrowsTree, ExecutableElement> expand(ThrowsTree tag,
ExecutableElement e,
TagletWriter writer) {
// This method uses Map.of() to create maps of size zero and one.
// While such maps are effectively ordered, the syntax is more
// compact than that of LinkedHashMap.
// peek into @throws description
if (tag.getDescription().stream().noneMatch(d -> d.getKind() == DocTree.Kind.INHERIT_DOC)) {
// nothing to inherit
return Map.of(tag, e);
}
var input = new DocFinder.Input(writer.configuration().utils, e, this, new DocFinder.DocTreeInfo(tag, e), false, true);
var output = DocFinder.search(writer.configuration(), input);
if (output.tagList.size() <= 1) {
// outer code will handle this trivial case of inheritance
return Map.of(tag, e);
}
if (tag.getDescription().size() > 1) {
// there's more to description than just {@inheritDoc}
// it's likely a documentation error
var ch = writer.configuration().utils.getCommentHelper(e);
writer.configuration().getMessages().error(ch.getDocTreePath(tag), "doclet.inheritDocWithinInappropriateTag");
return Map.of();
}
Map<ThrowsTree, ExecutableElement> tags = new LinkedHashMap<>();
output.tagList.forEach(t -> tags.put((ThrowsTree) t, (ExecutableElement) output.holder));
return tags;
}
/**
* Inherit throws documentation for exceptions that were declared but not
* documented.

View File

@ -23,7 +23,7 @@
/*
* @test
* @bug 8067757
* @bug 8067757 6509045
* @library /tools/lib ../../lib
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
@ -350,4 +350,218 @@ public class TestOneToMany extends JavadocTester {
<dd><code><a href="MySubException.html" title="class in x">MySubException</a></code> - if that</dd>
</dl>""");
}
@Test
public void testUncheckedExceptionTag(Path base) throws Exception {
var src = base.resolve("src");
tb.writeJavaFiles(src, """
package x;
public class MyRuntimeException extends RuntimeException { }
""", """
package x;
public interface I {
/**
* @throws MyRuntimeException if this
* @throws MyRuntimeException if that
*/
void m();
}
""", """
package x;
public interface I1 extends I {
/**
* @throws MyRuntimeException {@inheritDoc}
*/
@Override
void m();
}
""", """
package x;
public class IImpl implements I {
/**
* @throws MyRuntimeException {@inheritDoc}
*/
@Override
public void m() { }
}
""", """
package x;
public class C {
/**
* @throws MyRuntimeException if this
* @throws MyRuntimeException if that
*/
public void m();
}
""", """
package x;
public class C1 extends C {
/**
* @throws MyRuntimeException {@inheritDoc}
*/
@Override
public void m() { }
}
""");
javadoc("-d", base.resolve("out").toString(),
"-sourcepath", src.toString(),
"x");
checkExit(Exit.OK);
checkOutput("x/IImpl.html", true, """
<dl class="notes">
<dt>Specified by:</dt>
<dd><code><a href="I.html#m()">m</a></code>&nbsp;in interface&nbsp;<code><a href="I.html" title="interface in x">I</a></code></dd>
<dt>Throws:</dt>
<dd><code><a href="MyRuntimeException.html" title="class in x">MyRuntimeException</a></code> - if this</dd>
<dd><code><a href="MyRuntimeException.html" title="class in x">MyRuntimeException</a></code> - if that</dd>
</dl>""");
checkOutput("x/I1.html", true, """
<dl class="notes">
<dt>Specified by:</dt>
<dd><code><a href="I.html#m()">m</a></code>&nbsp;in interface&nbsp;<code><a href="I.html" title="interface in x">I</a></code></dd>
<dt>Throws:</dt>
<dd><code><a href="MyRuntimeException.html" title="class in x">MyRuntimeException</a></code> - if this</dd>
<dd><code><a href="MyRuntimeException.html" title="class in x">MyRuntimeException</a></code> - if that</dd>
</dl>""");
checkOutput("x/C1.html", true, """
<dl class="notes">
<dt>Overrides:</dt>
<dd><code><a href="C.html#m()">m</a></code>&nbsp;in class&nbsp;<code><a href="C.html" title="class in x">C</a></code></dd>
<dt>Throws:</dt>
<dd><code><a href="MyRuntimeException.html" title="class in x">MyRuntimeException</a></code> - if this</dd>
<dd><code><a href="MyRuntimeException.html" title="class in x">MyRuntimeException</a></code> - if that</dd>
</dl>""");
}
@Test
public void testWholeShebang(Path base) throws Exception {
var src = base.resolve("src");
tb.writeJavaFiles(src, """
package x;
public class MyRuntimeException extends RuntimeException { }
""", """
package x;
public interface I {
/**
* @throws MyRuntimeException always
*/
void m();
}
""", """
package x;
public interface I1 extends I {
/**
* @throws MyRuntimeException sometimes
* @throws MyRuntimeException rarely
* @throws MyRuntimeException "{@inheritDoc}"
*/
@Override
void m();
}
""", """
package x;
public interface I2 extends I1 {
/**
* @throws MyRuntimeException occasionally
* @throws MyRuntimeException {@inheritDoc}
* @throws MyRuntimeException frequently
*/
@Override
void m() throws MyRuntimeException,
MyRuntimeException,
MyRuntimeException,
MyRuntimeException;
}
""");
javadoc("-d", base.resolve("out").toString(),
"-sourcepath", src.toString(),
"x");
checkExit(Exit.OK);
checkOutput("x/I.html", true, """
<dl class="notes">
<dt>Throws:</dt>
<dd><code><a href="MyRuntimeException.html" title="class in x">MyRuntimeException</a></code> - always</dd>
</dl>""");
checkOutput("x/I1.html", true, """
<dl class="notes">
<dt>Specified by:</dt>
<dd><code><a href="I.html#m()">m</a></code>&nbsp;in interface&nbsp;<code><a href="I.html" title="interface in x">I</a></code></dd>
<dt>Throws:</dt>
<dd><code><a href="MyRuntimeException.html" title="class in x">MyRuntimeException</a></code> - sometimes</dd>
<dd><code><a href="MyRuntimeException.html" title="class in x">MyRuntimeException</a></code> - rarely</dd>
<dd><code><a href="MyRuntimeException.html" title="class in x">MyRuntimeException</a></code> - "always"</dd>
</dl>""");
checkOutput("x/I2.html", true, """
<dl class="notes">
<dt>Specified by:</dt>
<dd><code><a href="I.html#m()">m</a></code>&nbsp;in interface&nbsp;<code><a href="I.html" title="interface in x">I</a></code></dd>
<dt>Specified by:</dt>
<dd><code><a href="I1.html#m()">m</a></code>&nbsp;in interface&nbsp;<code><a href="I1.html" title="interface in x">I1</a></code></dd>
<dt>Throws:</dt>
<dd><code><a href="MyRuntimeException.html" title="class in x">MyRuntimeException</a></code> - occasionally</dd>
<dd><code><a href="MyRuntimeException.html" title="class in x">MyRuntimeException</a></code> - sometimes</dd>
<dd><code><a href="MyRuntimeException.html" title="class in x">MyRuntimeException</a></code> - rarely</dd>
<dd><code><a href="MyRuntimeException.html" title="class in x">MyRuntimeException</a></code> - "always"</dd>
<dd><code><a href="MyRuntimeException.html" title="class in x">MyRuntimeException</a></code> - frequently</dd>
</dl>""");
}
@Test
public void testError(Path base) throws Exception {
var src = base.resolve("src");
tb.writeJavaFiles(src, """
package x;
public class MyRuntimeException extends RuntimeException { }
""", """
package x;
public interface I {
/**
* @throws MyRuntimeException sometimes
* @throws MyRuntimeException rarely
*/
void m();
}
""", """
package x;
public interface I1 extends I {
/**
* @throws MyRuntimeException "{@inheritDoc}"
*/
@Override
void m();
}
""");
javadoc("-d", base.resolve("out").toString(),
"-sourcepath", src.toString(),
"x");
checkExit(Exit.ERROR);
checkOutput(Output.OUT, true, """
I1.java:6: error: @inheritDoc cannot be used within this tag
* @throws MyRuntimeException "{@inheritDoc}"
^
""");
}
}