6509045: {@inheritDoc} only copies one instance of the specified exception
Reviewed-by: jjg
This commit is contained in:
parent
9a0fa82424
commit
8f24d25168
@ -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.
|
||||
|
@ -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> in interface <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> in interface <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> in class <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> in interface <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> in interface <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> in interface <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}"
|
||||
^
|
||||
""");
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user