8285488: Improve DocFinder
8287796: Stop auto-inheriting documentation for subclasses of exceptions whose documentation is inherited 8291869: Match exceptions using types of javax.lang.model, not strings 8288045: Clean up ParamTaglet 8288046: Clean up ThrowsTaglet 8295277: Expand {@inheritDoc} in @throws fully Reviewed-by: jjg
This commit is contained in:
parent
97ab2c3ea6
commit
499406c764
@ -811,17 +811,17 @@ public class TagletWriterImpl extends TagletWriter {
|
||||
return HtmlTree.DT(contents.throws_);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Content throwsTagOutput(Element element, ThrowsTree throwsTag, TypeMirror substituteType) {
|
||||
@Deprecated(forRemoval = true)
|
||||
private Content throwsTagOutput(Element element, ThrowsTree throwsTag, TypeMirror substituteType) {
|
||||
ContentBuilder body = new ContentBuilder();
|
||||
CommentHelper ch = utils.getCommentHelper(element);
|
||||
Element exception = ch.getException(throwsTag);
|
||||
Content excName;
|
||||
if (substituteType != null) {
|
||||
excName = htmlWriter.getLink(new HtmlLinkInfo(configuration, HtmlLinkInfo.Kind.MEMBER,
|
||||
excName = htmlWriter.getLink(new HtmlLinkInfo(configuration, HtmlLinkInfo.Kind.MEMBER,
|
||||
substituteType));
|
||||
} else if (exception == null) {
|
||||
excName = RawHtml.of(throwsTag.getExceptionName().toString());
|
||||
excName = Text.of(throwsTag.getExceptionName().toString());
|
||||
} else if (exception.asType() == null) {
|
||||
excName = Text.of(utils.getFullyQualifiedName(exception));
|
||||
} else {
|
||||
@ -841,9 +841,16 @@ public class TagletWriterImpl extends TagletWriter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Content throwsTagOutput(TypeMirror throwsType) {
|
||||
return HtmlTree.DD(HtmlTree.CODE(htmlWriter.getLink(
|
||||
new HtmlLinkInfo(configuration, HtmlLinkInfo.Kind.MEMBER, throwsType))));
|
||||
public Content throwsTagOutput(TypeMirror throwsType, Optional<Content> content) {
|
||||
var linkInfo = new HtmlLinkInfo(configuration, HtmlLinkInfo.Kind.MEMBER, throwsType);
|
||||
linkInfo.excludeTypeBounds = true;
|
||||
var link = htmlWriter.getLink(linkInfo);
|
||||
var concat = new ContentBuilder(HtmlTree.CODE(link));
|
||||
if (content.isPresent()) {
|
||||
concat.add(" - ");
|
||||
concat.add(content.get());
|
||||
}
|
||||
return HtmlTree.DD(concat);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -107,6 +107,8 @@ doclet.link.see.no_label=missing reference label
|
||||
doclet.see.class_or_package_not_found=Tag {0}: reference not found: {1}
|
||||
doclet.see.class_or_package_not_accessible=Tag {0}: reference not accessible: {1}
|
||||
doclet.see.nested_link=Tag {0}: nested link
|
||||
doclet.throws.reference_not_found=cannot find exception type by name
|
||||
doclet.throws.reference_bad_type=not an exception type: {0}
|
||||
doclet.tag.invalid_usage=invalid usage of tag {0}
|
||||
doclet.tag.invalid_input=invalid input: ''{0}''
|
||||
doclet.tag.invalid=invalid @{0}
|
||||
|
@ -33,6 +33,7 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import javax.lang.model.element.Element;
|
||||
@ -50,6 +51,7 @@ import jdk.javadoc.internal.doclets.toolkit.Content;
|
||||
import jdk.javadoc.internal.doclets.toolkit.MemberSummaryWriter;
|
||||
import jdk.javadoc.internal.doclets.toolkit.WriterFactory;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.DocFinder;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.DocFinder.Result;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.Utils;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberTable;
|
||||
|
||||
@ -260,19 +262,18 @@ public abstract class MemberSummaryBuilder extends AbstractMemberBuilder {
|
||||
if (property != null && member instanceof ExecutableElement ee) {
|
||||
configuration.cmtUtils.updatePropertyMethodComment(ee, property);
|
||||
}
|
||||
List<? extends DocTree> firstSentenceTags = utils.getFirstSentenceTrees(member);
|
||||
if (utils.isMethod(member) && firstSentenceTags.isEmpty()) {
|
||||
//Inherit comments from overridden or implemented method if
|
||||
//necessary.
|
||||
DocFinder.Output inheritedDoc =
|
||||
DocFinder.search(configuration,
|
||||
new DocFinder.Input(utils, member));
|
||||
if (inheritedDoc.holder != null
|
||||
&& !utils.getFirstSentenceTrees(inheritedDoc.holder).isEmpty()) {
|
||||
firstSentenceTags = utils.getFirstSentenceTrees(inheritedDoc.holder);
|
||||
}
|
||||
if (utils.isMethod(member)) {
|
||||
var docFinder = utils.docFinder();
|
||||
Optional<List<? extends DocTree>> r = docFinder.search((ExecutableElement) member, (m -> {
|
||||
var firstSentenceTrees = utils.getFirstSentenceTrees(m);
|
||||
Optional<List<? extends DocTree>> optional = firstSentenceTrees.isEmpty() ? Optional.empty() : Optional.of(firstSentenceTrees);
|
||||
return Result.fromOptional(optional);
|
||||
})).toOptional();
|
||||
// The fact that we use `member` for possibly unrelated tags is suspicious
|
||||
writer.addMemberSummary(typeElement, member, r.orElse(List.of()));
|
||||
} else {
|
||||
writer.addMemberSummary(typeElement, member, utils.getFirstSentenceTrees(member));
|
||||
}
|
||||
writer.addMemberSummary(typeElement, member, firstSentenceTags);
|
||||
}
|
||||
summaryTreeList.add(writer.getSummaryTable(typeElement));
|
||||
}
|
||||
|
@ -32,11 +32,13 @@ import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
|
||||
import com.sun.source.doctree.DocTree;
|
||||
import jdk.javadoc.internal.doclets.toolkit.BaseOptions;
|
||||
import jdk.javadoc.internal.doclets.toolkit.Content;
|
||||
import jdk.javadoc.internal.doclets.toolkit.DocletException;
|
||||
import jdk.javadoc.internal.doclets.toolkit.MethodWriter;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.DocFinder;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.DocFinder.Result;
|
||||
|
||||
import static jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberTable.Kind.*;
|
||||
|
||||
@ -165,13 +167,10 @@ public class MethodBuilder extends AbstractMemberBuilder {
|
||||
protected void buildMethodComments(Content methodContent) {
|
||||
if (!options.noComment()) {
|
||||
assert utils.isMethod(currentMethod); // not all executables are methods
|
||||
ExecutableElement method = currentMethod;
|
||||
if (utils.getFullBody(currentMethod).isEmpty()) {
|
||||
DocFinder.Output docs = DocFinder.search(configuration,
|
||||
new DocFinder.Input(utils, currentMethod));
|
||||
if (!docs.inlineTags.isEmpty())
|
||||
method = (ExecutableElement) docs.holder;
|
||||
}
|
||||
var docFinder = utils.docFinder();
|
||||
Optional<ExecutableElement> r = docFinder.search(currentMethod,
|
||||
m -> Result.fromOptional(utils.getFullBody(m).isEmpty() ? Optional.empty() : Optional.of(m))).toOptional();
|
||||
ExecutableElement method = r.orElse(currentMethod);
|
||||
TypeMirror containingType = method.getEnclosingElement().asType();
|
||||
writer.addComments(containingType, method, methodContent);
|
||||
}
|
||||
|
@ -118,6 +118,9 @@ doclet.Factory=Factory:
|
||||
doclet.UnknownTag={0} is an unknown tag.
|
||||
doclet.UnknownTagLowercase={0} is an unknown tag -- same as a known tag except for case.
|
||||
doclet.inheritDocWithinInappropriateTag=@inheritDoc cannot be used within this tag
|
||||
doclet.inheritDocNoDoc=overridden methods do not document exception type {0}
|
||||
doclet.throwsInheritDocUnsupported=@inheritDoc is not supported for exception-type type parameters \
|
||||
that are not declared by a method; document such exception types directly
|
||||
doclet.noInheritedDoc=@inheritDoc used but {0} does not override or implement any method.
|
||||
doclet.tag_misuse=Tag {0} cannot be used in {1} documentation. It can only be used in the following types of documentation: {2}.
|
||||
doclet.Package_Summary=Package Summary
|
||||
|
@ -26,6 +26,8 @@
|
||||
package jdk.javadoc.internal.doclets.toolkit.taglets;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ElementKind;
|
||||
@ -38,6 +40,7 @@ import jdk.javadoc.internal.doclets.toolkit.Content;
|
||||
import jdk.javadoc.internal.doclets.toolkit.Messages;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.DocFinder;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.DocFinder.Result;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.Utils;
|
||||
|
||||
/**
|
||||
@ -61,52 +64,75 @@ public class InheritDocTaglet extends BaseTaglet {
|
||||
* such tag.</p>
|
||||
*
|
||||
* @param writer the writer that is writing the output.
|
||||
* @param e the {@link Element} that we are documenting.
|
||||
* @param method the method that we are documenting.
|
||||
* @param inheritDoc the {@code {@inheritDoc}} tag
|
||||
* @param isFirstSentence true if we only want to inherit the first sentence
|
||||
*/
|
||||
private Content retrieveInheritedDocumentation(TagletWriter writer,
|
||||
Element e,
|
||||
ExecutableElement method,
|
||||
DocTree inheritDoc,
|
||||
boolean isFirstSentence) {
|
||||
Content replacement = writer.getOutputInstance();
|
||||
BaseConfiguration configuration = writer.configuration();
|
||||
Messages messages = configuration.getMessages();
|
||||
Utils utils = configuration.utils;
|
||||
CommentHelper ch = utils.getCommentHelper(e);
|
||||
CommentHelper ch = utils.getCommentHelper(method);
|
||||
var path = ch.getDocTreePath(inheritDoc).getParentPath();
|
||||
DocTree holderTag = path.getLeaf();
|
||||
Taglet taglet = holderTag.getKind() == DocTree.Kind.DOC_COMMENT
|
||||
? null
|
||||
: configuration.tagletManager.getTaglet(ch.getTagName(holderTag));
|
||||
if (holderTag.getKind() == DocTree.Kind.DOC_COMMENT) {
|
||||
try {
|
||||
var docFinder = utils.docFinder();
|
||||
Optional<Documentation> r = docFinder.trySearch(method,
|
||||
m -> Result.fromOptional(extractMainDescription(m, isFirstSentence, utils))).toOptional();
|
||||
if (r.isPresent()) {
|
||||
replacement = writer.commentTagsToOutput(r.get().method, null,
|
||||
r.get().mainDescription, isFirstSentence);
|
||||
}
|
||||
} catch (DocFinder.NoOverriddenMethodsFound e) {
|
||||
String signature = utils.getSimpleName(method)
|
||||
+ utils.flatSignature(method, writer.getCurrentPageElement());
|
||||
messages.warning(method, "doclet.noInheritedDoc", signature);
|
||||
}
|
||||
return replacement;
|
||||
}
|
||||
|
||||
Taglet taglet = configuration.tagletManager.getTaglet(ch.getTagName(holderTag));
|
||||
if (taglet != null && !(taglet instanceof InheritableTaglet)) {
|
||||
// This tag does not support inheritance.
|
||||
messages.warning(path, "doclet.inheritDocWithinInappropriateTag");
|
||||
return replacement;
|
||||
}
|
||||
var input = new DocFinder.Input(utils, e, (InheritableTaglet) taglet,
|
||||
new DocFinder.DocTreeInfo(holderTag, e), isFirstSentence, true);
|
||||
DocFinder.Output inheritedDoc = DocFinder.search(configuration, input);
|
||||
if (inheritedDoc.isValidInheritDocTag) {
|
||||
if (!inheritedDoc.inlineTags.isEmpty()) {
|
||||
replacement = writer.commentTagsToOutput(inheritedDoc.holder, inheritedDoc.holderTag,
|
||||
inheritedDoc.inlineTags, isFirstSentence);
|
||||
|
||||
InheritableTaglet.Output inheritedDoc = ((InheritableTaglet) taglet).inherit(method, holderTag, isFirstSentence, configuration);
|
||||
if (inheritedDoc.isValidInheritDocTag()) {
|
||||
if (!inheritedDoc.inlineTags().isEmpty()) {
|
||||
replacement = writer.commentTagsToOutput(inheritedDoc.holder(), inheritedDoc.holderTag(),
|
||||
inheritedDoc.inlineTags(), isFirstSentence);
|
||||
}
|
||||
} else {
|
||||
String signature = utils.getSimpleName(e) +
|
||||
((utils.isExecutableElement(e))
|
||||
? utils.flatSignature((ExecutableElement) e, writer.getCurrentPageElement())
|
||||
: e.toString());
|
||||
messages.warning(e, "doclet.noInheritedDoc", signature);
|
||||
String signature = utils.getSimpleName(method)
|
||||
+ utils.flatSignature(method, writer.getCurrentPageElement());
|
||||
messages.warning(method, "doclet.noInheritedDoc", signature);
|
||||
}
|
||||
return replacement;
|
||||
}
|
||||
|
||||
private record Documentation(List<? extends DocTree> mainDescription, ExecutableElement method) { }
|
||||
|
||||
private static Optional<Documentation> extractMainDescription(ExecutableElement m,
|
||||
boolean extractFirstSentenceOnly,
|
||||
Utils utils) {
|
||||
List<? extends DocTree> docTrees = extractFirstSentenceOnly
|
||||
? utils.getFirstSentenceTrees(m)
|
||||
: utils.getFullBody(m);
|
||||
return docTrees.isEmpty() ? Optional.empty() : Optional.of(new Documentation(docTrees, m));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Content getInlineTagOutput(Element e, DocTree inheritDoc, TagletWriter tagletWriter) {
|
||||
if (e.getKind() != ElementKind.METHOD) {
|
||||
return tagletWriter.getOutputInstance();
|
||||
}
|
||||
return retrieveInheritedDocumentation(tagletWriter, e, inheritDoc, tagletWriter.isFirstSentence);
|
||||
return retrieveInheritedDocumentation(tagletWriter, (ExecutableElement) e, inheritDoc, tagletWriter.isFirstSentence);
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,13 @@
|
||||
|
||||
package jdk.javadoc.internal.doclets.toolkit.taglets;
|
||||
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.DocFinder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.lang.model.element.Element;
|
||||
|
||||
import com.sun.source.doctree.DocTree;
|
||||
import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration;
|
||||
|
||||
/**
|
||||
* A taglet should implement this interface if it supports an {@code {@inheritDoc}}
|
||||
@ -33,13 +39,24 @@ import jdk.javadoc.internal.doclets.toolkit.util.DocFinder;
|
||||
*/
|
||||
public interface InheritableTaglet extends Taglet {
|
||||
|
||||
/**
|
||||
* Given an {@link jdk.javadoc.internal.doclets.toolkit.util.DocFinder.Output}
|
||||
* object, set its values with the appropriate information to inherit
|
||||
* documentation.
|
||||
/*
|
||||
* Called by InheritDocTaglet on an inheritable taglet to expand {@inheritDoc}
|
||||
* found inside a tag corresponding to that taglet.
|
||||
*
|
||||
* @param input the input for documentation search
|
||||
* @param output the output for documentation search
|
||||
* When inheriting failed some assumption, or caused an error, the taglet
|
||||
* can return either of:
|
||||
*
|
||||
* - new Output(null, null, List.of(), false)
|
||||
* - new Output(null, null, List.of(), true)
|
||||
*
|
||||
* In the future, this could be reworked using some other mechanism,
|
||||
* such as throwing an exception.
|
||||
*/
|
||||
void inherit(DocFinder.Input input, DocFinder.Output output);
|
||||
Output inherit(Element owner, DocTree tag, boolean isFirstSentence, BaseConfiguration configuration);
|
||||
|
||||
record Output(DocTree holderTag,
|
||||
Element holder,
|
||||
List<? extends DocTree> inlineTags,
|
||||
boolean isValidInheritDocTag) {
|
||||
}
|
||||
}
|
||||
|
@ -28,17 +28,19 @@ package jdk.javadoc.internal.doclets.toolkit.taglets;
|
||||
import java.util.*;
|
||||
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ElementKind;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
|
||||
import com.sun.source.doctree.DocTree;
|
||||
import com.sun.source.doctree.ParamTree;
|
||||
import jdk.javadoc.doclet.Taglet.Location;
|
||||
import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration;
|
||||
import jdk.javadoc.internal.doclets.toolkit.Content;
|
||||
import jdk.javadoc.internal.doclets.toolkit.Messages;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.DocFinder;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.DocFinder.Input;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.DocFinder.Result;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.Utils;
|
||||
|
||||
/**
|
||||
@ -62,48 +64,34 @@ public class ParamTaglet extends BaseTaglet implements InheritableTaglet {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inherit(DocFinder.Input input, DocFinder.Output output) {
|
||||
Utils utils = input.utils;
|
||||
if (input.tagId == null) {
|
||||
var tag = (ParamTree) input.docTreeInfo.docTree();
|
||||
input.isTypeVariableParamTag = tag.isTypeParameter();
|
||||
ExecutableElement ee = (ExecutableElement) input.docTreeInfo.element();
|
||||
CommentHelper ch = utils.getCommentHelper(ee);
|
||||
List<? extends Element> parameters = input.isTypeVariableParamTag
|
||||
? ee.getTypeParameters()
|
||||
: ee.getParameters();
|
||||
String target = ch.getParameterName(tag);
|
||||
for (int i = 0; i < parameters.size(); i++) {
|
||||
Element e = parameters.get(i);
|
||||
String candidate = input.isTypeVariableParamTag
|
||||
? utils.getTypeName(e.asType(), false)
|
||||
: utils.getSimpleName(e);
|
||||
if (candidate.equals(target)) {
|
||||
input.tagId = Integer.toString(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
public Output inherit(Element owner, DocTree tag, boolean isFirstSentence, BaseConfiguration configuration) {
|
||||
assert owner.getKind() == ElementKind.METHOD;
|
||||
assert tag.getKind() == DocTree.Kind.PARAM;
|
||||
var method = (ExecutableElement) owner;
|
||||
var param = (ParamTree) tag;
|
||||
// find the position of an owner parameter described by the given tag
|
||||
List<? extends Element> parameterElements;
|
||||
if (param.isTypeParameter()) {
|
||||
parameterElements = method.getTypeParameters();
|
||||
} else {
|
||||
parameterElements = method.getParameters();
|
||||
}
|
||||
if (input.tagId == null)
|
||||
return;
|
||||
int position = Integer.parseInt(input.tagId);
|
||||
ExecutableElement ee = (ExecutableElement) input.element;
|
||||
CommentHelper ch = utils.getCommentHelper(ee);
|
||||
List<ParamTree> tags = input.isTypeVariableParamTag
|
||||
? utils.getTypeParamTrees(ee)
|
||||
: utils.getParamTrees(ee);
|
||||
List<? extends Element> parameters = input.isTypeVariableParamTag
|
||||
? ee.getTypeParameters()
|
||||
: ee.getParameters();
|
||||
Map<String, Integer> positionOfName = mapNameToPosition(utils, parameters);
|
||||
for (ParamTree tag : tags) {
|
||||
String paramName = ch.getParameterName(tag);
|
||||
if (positionOfName.containsKey(paramName) && positionOfName.get(paramName).equals(position)) {
|
||||
output.holder = input.element;
|
||||
output.holderTag = tag;
|
||||
output.inlineTags = ch.getBody(tag);
|
||||
return;
|
||||
}
|
||||
Map<String, Integer> stringIntegerMap = mapNameToPosition(configuration.utils, parameterElements);
|
||||
CommentHelper ch = configuration.utils.getCommentHelper(owner);
|
||||
Integer position = stringIntegerMap.get(ch.getParameterName(param));
|
||||
if (position == null) {
|
||||
return new Output(null, null, List.of(), true);
|
||||
}
|
||||
// try to inherit description of the respective parameter in an overridden method
|
||||
try {
|
||||
var docFinder = configuration.utils.docFinder();
|
||||
var r = docFinder.trySearch(method,
|
||||
m -> Result.fromOptional(extract(configuration.utils, m, position, param.isTypeParameter())))
|
||||
.toOptional();
|
||||
return r.map(result -> new Output(result.paramTree, result.method, result.paramTree.getDescription(), true))
|
||||
.orElseGet(() -> new Output(null, null, List.of(), true));
|
||||
} catch (DocFinder.NoOverriddenMethodsFound e) {
|
||||
return new Output(null, null, List.of(), false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -239,21 +227,35 @@ public class ParamTaglet extends BaseTaglet implements InheritableTaglet {
|
||||
boolean isFirst) {
|
||||
Utils utils = writer.configuration().utils;
|
||||
Content result = writer.getOutputInstance();
|
||||
Input input = new DocFinder.Input(writer.configuration().utils, holder, this,
|
||||
Integer.toString(position), kind == ParamKind.TYPE_PARAMETER);
|
||||
DocFinder.Output inheritedDoc = DocFinder.search(writer.configuration(), input);
|
||||
if (!inheritedDoc.inlineTags.isEmpty()) {
|
||||
var r = utils.docFinder().search((ExecutableElement) holder,
|
||||
m -> Result.fromOptional(extract(utils, m, position, kind == ParamKind.TYPE_PARAMETER)))
|
||||
.toOptional();
|
||||
if (r.isPresent()) {
|
||||
String name = kind != ParamKind.TYPE_PARAMETER
|
||||
? utils.getSimpleName(param)
|
||||
: utils.getTypeName(param.asType(), false);
|
||||
Content content = convertParam(inheritedDoc.holder, kind, writer,
|
||||
(ParamTree) inheritedDoc.holderTag,
|
||||
name, isFirst);
|
||||
Content content = convertParam(r.get().method, kind, writer,
|
||||
r.get().paramTree, name, isFirst);
|
||||
result.add(content);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private record Documentation(ParamTree paramTree, ExecutableElement method) { }
|
||||
|
||||
private static Optional<Documentation> extract(Utils utils, ExecutableElement method, Integer position, boolean typeParam) {
|
||||
var ch = utils.getCommentHelper(method);
|
||||
List<ParamTree> tags = typeParam
|
||||
? utils.getTypeParamTrees(method)
|
||||
: utils.getParamTrees(method);
|
||||
List<? extends Element> parameters = typeParam
|
||||
? method.getTypeParameters()
|
||||
: method.getParameters();
|
||||
var positionOfName = mapNameToPosition(utils, parameters);
|
||||
return tags.stream().filter(t -> position.equals(positionOfName.get(ch.getParameterName(t))))
|
||||
.map(t -> new Documentation(t, method)).findAny();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an individual {@code ParamTree} to {@code Content}, which is
|
||||
* prepended with the header if the parameter is first in the list.
|
||||
|
@ -25,22 +25,24 @@
|
||||
|
||||
package jdk.javadoc.internal.doclets.toolkit.taglets;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
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.type.TypeMirror;
|
||||
|
||||
import com.sun.source.doctree.DocTree;
|
||||
import com.sun.source.doctree.ReturnTree;
|
||||
import jdk.javadoc.doclet.Taglet.Location;
|
||||
import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration;
|
||||
import jdk.javadoc.internal.doclets.toolkit.Content;
|
||||
import jdk.javadoc.internal.doclets.toolkit.Messages;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.DocFinder;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.DocFinder.Input;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.DocFinder.Result;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.Utils;
|
||||
|
||||
/**
|
||||
@ -58,27 +60,14 @@ public class ReturnTaglet extends BaseTaglet implements InheritableTaglet {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inherit(DocFinder.Input input, DocFinder.Output output) {
|
||||
Utils utils = input.utils;
|
||||
CommentHelper ch = utils.getCommentHelper(input.element);
|
||||
|
||||
ReturnTree tag = null;
|
||||
List<? extends ReturnTree> tags = utils.getReturnTrees(input.element);
|
||||
if (!tags.isEmpty()) {
|
||||
tag = tags.get(0);
|
||||
} else {
|
||||
List<? extends DocTree> firstSentence = utils.getFirstSentenceTrees(input.element);
|
||||
if (firstSentence.size() == 1 && firstSentence.get(0).getKind() == DocTree.Kind.RETURN) {
|
||||
tag = (ReturnTree) firstSentence.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (tag != null) {
|
||||
output.holder = input.element;
|
||||
output.holderTag = tag;
|
||||
output.inlineTags = input.isFirstSentence
|
||||
? ch.getFirstSentenceTrees(output.holderTag)
|
||||
: ch.getDescription(output.holderTag);
|
||||
public Output inherit(Element owner, DocTree tag, boolean isFirstSentence, BaseConfiguration configuration) {
|
||||
try {
|
||||
var docFinder = configuration.utils.docFinder();
|
||||
var r = docFinder.trySearch((ExecutableElement) owner, m -> Result.fromOptional(extract(configuration.utils, m))).toOptional();
|
||||
return r.map(result -> new Output(result.returnTree, result.method, result.returnTree.getDescription(), true))
|
||||
.orElseGet(() -> new Output(null, null, List.of(), true));
|
||||
} catch (DocFinder.NoOverriddenMethodsFound e) {
|
||||
return new Output(null, null, List.of(), false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,12 +78,14 @@ public class ReturnTaglet extends BaseTaglet implements InheritableTaglet {
|
||||
|
||||
@Override
|
||||
public Content getAllBlockTagOutput(Element holder, TagletWriter writer) {
|
||||
assert holder.getKind() == ElementKind.METHOD : holder.getKind();
|
||||
var method = (ExecutableElement) holder;
|
||||
Messages messages = writer.configuration().getMessages();
|
||||
Utils utils = writer.configuration().utils;
|
||||
List<? extends ReturnTree> tags = utils.getReturnTrees(holder);
|
||||
|
||||
// Make sure we are not using @return tag on method with void return type.
|
||||
TypeMirror returnType = utils.getReturnType(writer.getCurrentPageElement(), (ExecutableElement) holder);
|
||||
// make sure we are not using @return on a method with the void return type
|
||||
TypeMirror returnType = utils.getReturnType(writer.getCurrentPageElement(), method);
|
||||
if (returnType != null && utils.isVoid(returnType)) {
|
||||
if (!tags.isEmpty() && !writer.configuration().isDocLintReferenceGroupEnabled()) {
|
||||
messages.warning(holder, "doclet.Return_tag_on_void_method");
|
||||
@ -102,22 +93,30 @@ public class ReturnTaglet extends BaseTaglet implements InheritableTaglet {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!tags.isEmpty()) {
|
||||
return writer.returnTagOutput(holder, tags.get(0), false);
|
||||
}
|
||||
// it would also be good to check if there are more than one @return
|
||||
// tags and produce a warning or error similarly to how it's done
|
||||
// above for a case where @return is used for void
|
||||
|
||||
// Check for inline tag in first sentence.
|
||||
List<? extends DocTree> firstSentence = utils.getFirstSentenceTrees(holder);
|
||||
if (firstSentence.size() == 1 && firstSentence.get(0).getKind() == DocTree.Kind.RETURN) {
|
||||
return writer.returnTagOutput(holder, (ReturnTree) firstSentence.get(0), false);
|
||||
}
|
||||
var docFinder = utils.docFinder();
|
||||
return docFinder.search(method, m -> Result.fromOptional(extract(utils, m))).toOptional()
|
||||
.map(r -> writer.returnTagOutput(r.method, r.returnTree, false))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
// Inherit @return tag if necessary.
|
||||
Input input = new DocFinder.Input(utils, holder, this);
|
||||
DocFinder.Output inheritedDoc = DocFinder.search(writer.configuration(), input);
|
||||
if (inheritedDoc.holderTag != null) {
|
||||
return writer.returnTagOutput(inheritedDoc.holder, (ReturnTree) inheritedDoc.holderTag, false);
|
||||
}
|
||||
return null;
|
||||
private record Documentation(ReturnTree returnTree, ExecutableElement method) { }
|
||||
|
||||
private static Optional<Documentation> extract(Utils utils, ExecutableElement method) {
|
||||
// TODO
|
||||
// Using getBlockTags(..., Kind.RETURN) for clarity. Since @return has become a bimodal tag,
|
||||
// Utils.getReturnTrees is now a misnomer: it returns only block returns, not all returns.
|
||||
// We could revisit this later.
|
||||
Stream<? extends ReturnTree> blockTags = utils.getBlockTags(method, DocTree.Kind.RETURN, ReturnTree.class).stream();
|
||||
Stream<? extends ReturnTree> mainDescriptionTags = utils.getFirstSentenceTrees(method).stream()
|
||||
.mapMulti((t, c) -> {
|
||||
if (t.getKind() == DocTree.Kind.RETURN) c.accept((ReturnTree) t);
|
||||
});
|
||||
// this method should not check validity of @return tags, hence findAny and not findFirst or what have you
|
||||
return Stream.concat(blockTags, mainDescriptionTags)
|
||||
.map(t -> new Documentation(t, method)).findAny();
|
||||
}
|
||||
}
|
||||
|
@ -27,16 +27,17 @@ package jdk.javadoc.internal.doclets.toolkit.taglets;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
|
||||
import com.sun.source.doctree.DocTree;
|
||||
import com.sun.source.doctree.SeeTree;
|
||||
import jdk.javadoc.doclet.Taglet.Location;
|
||||
import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration;
|
||||
import jdk.javadoc.internal.doclets.toolkit.Content;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.DocFinder;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.DocFinder.Input;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.DocFinder.Result;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.Utils;
|
||||
|
||||
/**
|
||||
@ -49,16 +50,8 @@ public class SeeTaglet extends BaseTaglet implements InheritableTaglet {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inherit(DocFinder.Input input, DocFinder.Output output) {
|
||||
List<? extends SeeTree> tags = input.utils.getSeeTrees(input.element);
|
||||
if (!tags.isEmpty()) {
|
||||
CommentHelper ch = input.utils.getCommentHelper(input.element);
|
||||
output.holder = input.element;
|
||||
output.holderTag = tags.get(0);
|
||||
output.inlineTags = input.isFirstSentence
|
||||
? ch.getFirstSentenceTrees(output.holderTag)
|
||||
: ch.getReference(output.holderTag);
|
||||
}
|
||||
public Output inherit(Element owner, DocTree tag, boolean isFirstSentence, BaseConfiguration configuration) {
|
||||
throw new UnsupportedOperationException("Not yet implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -66,14 +59,23 @@ public class SeeTaglet extends BaseTaglet implements InheritableTaglet {
|
||||
Utils utils = writer.configuration().utils;
|
||||
List<? extends SeeTree> tags = utils.getSeeTrees(holder);
|
||||
Element e = holder;
|
||||
if (tags.isEmpty() && utils.isMethod(holder)) {
|
||||
Input input = new DocFinder.Input(utils, holder, this);
|
||||
DocFinder.Output inheritedDoc = DocFinder.search(writer.configuration(), input);
|
||||
if (inheritedDoc.holder != null) {
|
||||
tags = utils.getSeeTrees(inheritedDoc.holder);
|
||||
e = inheritedDoc.holder;
|
||||
if (utils.isMethod(holder)) {
|
||||
var docFinder = utils.docFinder();
|
||||
Optional<Documentation> result = docFinder.search((ExecutableElement) holder,
|
||||
m -> Result.fromOptional(extract(utils, m))).toOptional();
|
||||
if (result.isPresent()) {
|
||||
ExecutableElement m = result.get().method();
|
||||
tags = utils.getSeeTrees(m);
|
||||
e = m;
|
||||
}
|
||||
}
|
||||
return writer.seeTagOutput(e, tags);
|
||||
}
|
||||
|
||||
private record Documentation(List<? extends SeeTree> seeTrees, ExecutableElement method) { }
|
||||
|
||||
private static Optional<Documentation> extract(Utils utils, ExecutableElement method) {
|
||||
List<? extends SeeTree> tags = utils.getSeeTrees(method);
|
||||
return tags.isEmpty() ? Optional.empty() : Optional.of(new Documentation(tags, method));
|
||||
}
|
||||
}
|
||||
|
@ -27,16 +27,20 @@ package jdk.javadoc.internal.doclets.toolkit.taglets;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ElementKind;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
|
||||
import com.sun.source.doctree.DocTree;
|
||||
import jdk.javadoc.doclet.Taglet.Location;
|
||||
|
||||
import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration;
|
||||
import jdk.javadoc.internal.doclets.toolkit.Content;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.DocFinder;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.DocFinder.Result;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.Utils;
|
||||
|
||||
/**
|
||||
@ -159,18 +163,31 @@ public class SimpleTaglet extends BaseTaglet implements InheritableTaglet {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inherit(DocFinder.Input input, DocFinder.Output output) {
|
||||
List<? extends DocTree> tags = input.utils.getBlockTags(input.element, this);
|
||||
if (!tags.isEmpty()) {
|
||||
output.holder = input.element;
|
||||
output.holderTag = tags.get(0);
|
||||
CommentHelper ch = input.utils.getCommentHelper(output.holder);
|
||||
output.inlineTags = input.isFirstSentence
|
||||
? ch.getFirstSentenceTrees(output.holderTag)
|
||||
: ch.getTags(output.holderTag);
|
||||
public Output inherit(Element owner, DocTree tag, boolean isFirstSentence, BaseConfiguration configuration) {
|
||||
assert owner.getKind() == ElementKind.METHOD;
|
||||
assert !isFirstSentence;
|
||||
try {
|
||||
var docFinder = configuration.utils.docFinder();
|
||||
var r = docFinder.trySearch((ExecutableElement) owner,
|
||||
m -> Result.fromOptional(extractFirst(m, configuration.utils))).toOptional();
|
||||
return r.map(result -> new Output(result.tag, result.method, result.description, true))
|
||||
.orElseGet(()->new Output(null, null, List.of(), true));
|
||||
} catch (DocFinder.NoOverriddenMethodsFound e) {
|
||||
return new Output(null, null, List.of(), false);
|
||||
}
|
||||
}
|
||||
|
||||
record Documentation(DocTree tag, List<? extends DocTree> description, ExecutableElement method) { }
|
||||
|
||||
private Optional<Documentation> extractFirst(ExecutableElement m, Utils utils) {
|
||||
List<? extends DocTree> tags = utils.getBlockTags(m, this);
|
||||
if (tags.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
DocTree t = tags.get(0);
|
||||
return Optional.of(new Documentation(t, utils.getCommentHelper(m).getDescription(t), m));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Content getAllBlockTagOutput(Element holder, TagletWriter writer) {
|
||||
Utils utils = writer.configuration().utils;
|
||||
|
@ -27,15 +27,16 @@ package jdk.javadoc.internal.doclets.toolkit.taglets;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
|
||||
import com.sun.source.doctree.DocTree;
|
||||
import com.sun.source.doctree.SpecTree;
|
||||
import jdk.javadoc.doclet.Taglet.Location;
|
||||
import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration;
|
||||
import jdk.javadoc.internal.doclets.toolkit.Content;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.DocFinder;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.DocFinder.Input;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.DocFinder.Result;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.Utils;
|
||||
|
||||
/**
|
||||
@ -48,16 +49,8 @@ public class SpecTaglet extends BaseTaglet implements InheritableTaglet {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inherit(Input input, DocFinder.Output output) {
|
||||
List<? extends SpecTree> tags = input.utils.getSpecTrees(input.element);
|
||||
if (!tags.isEmpty()) {
|
||||
CommentHelper ch = input.utils.getCommentHelper(input.element);
|
||||
output.holder = input.element;
|
||||
output.holderTag = tags.get(0);
|
||||
output.inlineTags = input.isFirstSentence
|
||||
? ch.getFirstSentenceTrees(output.holderTag)
|
||||
: ch.getTags(output.holderTag);
|
||||
}
|
||||
public Output inherit(Element owner, DocTree tag, boolean isFirstSentence, BaseConfiguration configuration) {
|
||||
throw new UnsupportedOperationException("Not yet implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -65,14 +58,23 @@ public class SpecTaglet extends BaseTaglet implements InheritableTaglet {
|
||||
Utils utils = writer.configuration().utils;
|
||||
List<? extends SpecTree> tags = utils.getSpecTrees(holder);
|
||||
Element e = holder;
|
||||
if (tags.isEmpty() && utils.isExecutableElement(holder)) {
|
||||
Input input = new Input(utils, holder, this);
|
||||
DocFinder.Output inheritedDoc = DocFinder.search(writer.configuration(), input);
|
||||
if (inheritedDoc.holder != null) {
|
||||
tags = utils.getSpecTrees(inheritedDoc.holder);
|
||||
e = inheritedDoc.holder;
|
||||
if (utils.isMethod(holder)) {
|
||||
var docFinder = utils.docFinder();
|
||||
Optional<Documentation> result = docFinder.search((ExecutableElement) holder,
|
||||
m -> Result.fromOptional(extract(utils, m))).toOptional();
|
||||
if (result.isPresent()) {
|
||||
ExecutableElement m = result.get().method();
|
||||
tags = utils.getSpecTrees(m);
|
||||
e = m;
|
||||
}
|
||||
}
|
||||
return writer.specTagOutput(e, tags);
|
||||
}
|
||||
|
||||
private record Documentation(List<? extends SpecTree> seeTrees, ExecutableElement method) { }
|
||||
|
||||
private static Optional<Documentation> extract(Utils utils, ExecutableElement method) {
|
||||
List<? extends SpecTree> tags = utils.getSpecTrees(method);
|
||||
return tags.isEmpty() ? Optional.empty() : Optional.of(new Documentation(tags, method));
|
||||
}
|
||||
}
|
||||
|
@ -598,7 +598,7 @@ public class TagletManager {
|
||||
|
||||
addStandardTaglet(new ParamTaglet());
|
||||
addStandardTaglet(new ReturnTaglet());
|
||||
addStandardTaglet(new ThrowsTaglet(), EXCEPTION);
|
||||
addStandardTaglet(new ThrowsTaglet(configuration), EXCEPTION);
|
||||
addStandardTaglet(
|
||||
new SimpleTaglet(SINCE, resources.getText("doclet.Since"),
|
||||
EnumSet.allOf(Location.class), !nosince));
|
||||
|
@ -223,25 +223,15 @@ public abstract class TagletWriter {
|
||||
*/
|
||||
protected abstract Content getThrowsHeader();
|
||||
|
||||
/**
|
||||
* Returns the output for a {@code @throws} tag.
|
||||
*
|
||||
* @param element The element that owns the doc comment
|
||||
* @param throwsTag the throws tag
|
||||
* @param substituteType instantiated type of a generic type-variable, or null
|
||||
*
|
||||
* @return the output
|
||||
*/
|
||||
protected abstract Content throwsTagOutput(Element element, ThrowsTree throwsTag, TypeMirror substituteType);
|
||||
|
||||
/**
|
||||
* Returns the output for a default {@code @throws} tag.
|
||||
*
|
||||
* @param throwsType the type that is thrown
|
||||
* @param content the optional content to add as a description
|
||||
*
|
||||
* @return the output
|
||||
*/
|
||||
protected abstract Content throwsTagOutput(TypeMirror throwsType);
|
||||
protected abstract Content throwsTagOutput(TypeMirror throwsType, Optional<Content> content);
|
||||
|
||||
/**
|
||||
* Returns the output for a {@code {@value}} tag.
|
||||
|
@ -25,20 +25,26 @@
|
||||
|
||||
package jdk.javadoc.internal.doclets.toolkit.taglets;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
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.PackageElement;
|
||||
import javax.lang.model.element.QualifiedNameable;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.element.TypeParameterElement;
|
||||
import javax.lang.model.type.ExecutableType;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.lang.model.util.Types;
|
||||
@ -47,252 +53,717 @@ import com.sun.source.doctree.DocTree;
|
||||
import com.sun.source.doctree.ThrowsTree;
|
||||
|
||||
import jdk.javadoc.doclet.Taglet.Location;
|
||||
import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder;
|
||||
import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration;
|
||||
import jdk.javadoc.internal.doclets.toolkit.Content;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.DocFinder;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.DocFinder.Result;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.Utils;
|
||||
|
||||
/**
|
||||
* A taglet that processes {@link ThrowsTree}, which represents
|
||||
* {@code @throws} and {@code @exception} tags.
|
||||
* A taglet that processes {@link ThrowsTree}, which represents {@code @throws}
|
||||
* and {@code @exception} tags, collectively referred to as exception tags.
|
||||
*/
|
||||
public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet {
|
||||
|
||||
public ThrowsTaglet() {
|
||||
/*
|
||||
* Relevant bits from JLS
|
||||
* ======================
|
||||
*
|
||||
* This list is _incomplete_ because some parts cannot be summarized here
|
||||
* and require careful reading of JLS.
|
||||
*
|
||||
* 11.1.1 The Kinds of Exceptions
|
||||
*
|
||||
* Throwable and all its subclasses are, collectively, the exception
|
||||
* classes.
|
||||
*
|
||||
* 8.4.6 Method Throws
|
||||
*
|
||||
* Throws:
|
||||
* throws ExceptionTypeList
|
||||
*
|
||||
* ExceptionTypeList:
|
||||
* ExceptionType {, ExceptionType}
|
||||
*
|
||||
* ExceptionType:
|
||||
* ClassType
|
||||
* TypeVariable
|
||||
*
|
||||
* It is a compile-time error if an ExceptionType mentioned in a throws
|
||||
* clause is not a subtype (4.10) of Throwable.
|
||||
*
|
||||
* Type variables are allowed in a throws clause even though they are
|
||||
* not allowed in a catch clause (14.20).
|
||||
*
|
||||
* It is permitted but not required to mention unchecked exception
|
||||
* classes (11.1.1) in a throws clause.
|
||||
*
|
||||
* 8.1.2 Generic Classes and Type Parameters
|
||||
*
|
||||
* It is a compile-time error if a generic class is a direct or indirect
|
||||
* subclass of Throwable.
|
||||
*
|
||||
* 8.8.5. Constructor Throws
|
||||
*
|
||||
* The throws clause for a constructor is identical in structure and
|
||||
* behavior to the throws clause for a method (8.4.6).
|
||||
*
|
||||
* 8.8. Constructor Declarations
|
||||
*
|
||||
* Constructor declarations are ... never inherited and therefore are not
|
||||
* subject to hiding or overriding.
|
||||
*
|
||||
* 8.4.4. Generic Methods
|
||||
*
|
||||
* A method is generic if it declares one or more type variables (4.4).
|
||||
* These type variables are known as the type parameters of the method.
|
||||
*
|
||||
* ...
|
||||
*
|
||||
* Two methods or constructors M and N have the same type parameters if
|
||||
* both of the following are true:
|
||||
*
|
||||
* - M and N have same number of type parameters (possibly zero).
|
||||
* ...
|
||||
*
|
||||
* 8.4.2. Method Signature
|
||||
*
|
||||
* Two methods or constructors, M and N, have the same signature if they
|
||||
* have ... the same type parameters (if any) (8.4.4) ...
|
||||
* ...
|
||||
* The signature of a method m1 is a subsignature of the signature of
|
||||
* a method m2 if either:
|
||||
*
|
||||
* - m2 has the same signature as m1, or
|
||||
* - the signature of m1 is the same as the erasure (4.6) of the
|
||||
* signature of m2.
|
||||
*
|
||||
* Two method signatures m1 and m2 are override-equivalent iff either
|
||||
* m1 is a subsignature of m2 or m2 is a subsignature of m1.
|
||||
*
|
||||
* 8.4.8.1. Overriding (by Instance Methods)
|
||||
*
|
||||
* An instance method mC declared in or inherited by class C, overrides
|
||||
* from C another method mA declared in class A, iff all of the following
|
||||
* are true:
|
||||
*
|
||||
* ...
|
||||
* - The signature of mC is a subsignature (8.4.2) of the signature of
|
||||
* mA as a member of the supertype of C that names A.
|
||||
*/
|
||||
|
||||
public ThrowsTaglet(BaseConfiguration configuration) {
|
||||
// of all language elements only constructors and methods can declare
|
||||
// thrown exceptions and, hence, document them
|
||||
super(DocTree.Kind.THROWS, false, EnumSet.of(Location.CONSTRUCTOR, Location.METHOD));
|
||||
this.configuration = configuration;
|
||||
this.utils = this.configuration.utils;
|
||||
}
|
||||
|
||||
private final BaseConfiguration configuration;
|
||||
private final Utils utils;
|
||||
|
||||
@Override
|
||||
public void inherit(DocFinder.Input input, DocFinder.Output output) {
|
||||
var utils = input.utils;
|
||||
Element target;
|
||||
var ch = utils.getCommentHelper(input.element);
|
||||
if (input.tagId == null) {
|
||||
var tag = (ThrowsTree) input.docTreeInfo.docTree();
|
||||
target = ch.getException(tag);
|
||||
input.tagId = target == null
|
||||
? tag.getExceptionName().getSignature()
|
||||
: utils.getFullyQualifiedName(target);
|
||||
} else {
|
||||
target = input.utils.findClass(input.element, input.tagId);
|
||||
}
|
||||
|
||||
// TODO warn if target == null as we cannot guarantee type-match, but at most FQN-match.
|
||||
|
||||
for (ThrowsTree tag : input.utils.getThrowsTrees(input.element)) {
|
||||
Element candidate = ch.getException(tag);
|
||||
if (candidate != null && (input.tagId.equals(utils.getSimpleName(candidate)) ||
|
||||
(input.tagId.equals(utils.getFullyQualifiedName(candidate))))) {
|
||||
output.holder = input.element;
|
||||
output.holderTag = tag;
|
||||
output.inlineTags = ch.getBody(output.holderTag);
|
||||
output.tagList.add(tag);
|
||||
} else if (target != null && candidate != null &&
|
||||
utils.isTypeElement(candidate) && utils.isTypeElement(target) &&
|
||||
utils.isSubclassOf((TypeElement) candidate, (TypeElement) target)) {
|
||||
output.tagList.add(tag);
|
||||
}
|
||||
}
|
||||
public Output inherit(Element owner, DocTree tag, boolean isFirstSentence, BaseConfiguration configuration) {
|
||||
// This method shouldn't be called because {@inheritDoc} tags inside
|
||||
// exception tags aren't dealt with individually. {@inheritDoc} tags
|
||||
// inside exception tags are collectively dealt with in
|
||||
// getAllBlockTagOutput.
|
||||
throw newAssertionError(owner, tag, isFirstSentence);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Content getAllBlockTagOutput(Element holder, TagletWriter writer) {
|
||||
var utils = writer.configuration().utils;
|
||||
try {
|
||||
return getAllBlockTagOutput0(holder, writer);
|
||||
} catch (Failure f) {
|
||||
// note that `f.holder()` is not necessarily the same as `holder`
|
||||
var ch = utils.getCommentHelper(f.holder());
|
||||
var messages = configuration.getMessages();
|
||||
if (f instanceof Failure.ExceptionTypeNotFound e) {
|
||||
var path = ch.getDocTreePath(e.tag().getExceptionName());
|
||||
messages.warning(path, "doclet.throws.reference_not_found");
|
||||
} else if (f instanceof Failure.NotExceptionType e) {
|
||||
var path = ch.getDocTreePath(e.tag().getExceptionName());
|
||||
// output the type we found to help the user diagnose the issue
|
||||
messages.warning(path, "doclet.throws.reference_bad_type", diagnosticDescriptionOf(e.type()));
|
||||
} else if (f instanceof Failure.Invalid e) {
|
||||
messages.error(ch.getDocTreePath(e.tag()), "doclet.inheritDocWithinInappropriateTag");
|
||||
} else if (f instanceof Failure.UnsupportedTypeParameter e) {
|
||||
var path = ch.getDocTreePath(e.tag().getExceptionName());
|
||||
messages.warning(path, "doclet.throwsInheritDocUnsupported");
|
||||
} else if (f instanceof Failure.Undocumented e) {
|
||||
messages.warning(ch.getDocTreePath(e.tag()), "doclet.inheritDocNoDoc", diagnosticDescriptionOf(e.exceptionElement));
|
||||
} else {
|
||||
// TODO: instead of if-else, use pattern matching for switch for both
|
||||
// readability and exhaustiveness when it's available
|
||||
throw newAssertionError(f);
|
||||
}
|
||||
} catch (DocFinder.NoOverriddenMethodsFound e) {
|
||||
// since {@inheritDoc} in @throws is processed by ThrowsTaglet (this taglet) rather than
|
||||
// InheritDocTaglet, we have to duplicate some of the behavior of the latter taglet
|
||||
String signature = utils.getSimpleName(holder)
|
||||
+ utils.flatSignature((ExecutableElement) holder, writer.getCurrentPageElement());
|
||||
configuration.getMessages().warning(holder, "doclet.noInheritedDoc", signature);
|
||||
}
|
||||
return writer.getOutputInstance(); // TODO: consider invalid rather than empty output
|
||||
}
|
||||
|
||||
private Content getAllBlockTagOutput0(Element holder,
|
||||
TagletWriter writer)
|
||||
throws Failure.ExceptionTypeNotFound,
|
||||
Failure.NotExceptionType,
|
||||
Failure.Invalid,
|
||||
Failure.Undocumented,
|
||||
Failure.UnsupportedTypeParameter,
|
||||
DocFinder.NoOverriddenMethodsFound
|
||||
{
|
||||
ElementKind kind = holder.getKind();
|
||||
if (kind != ElementKind.METHOD && kind != ElementKind.CONSTRUCTOR) {
|
||||
// Elements are processed by applicable taglets only. This taglet
|
||||
// is only applicable to executable elements such as methods
|
||||
// and constructors.
|
||||
throw newAssertionError(holder, kind);
|
||||
}
|
||||
var executable = (ExecutableElement) holder;
|
||||
ExecutableType instantiatedType = utils.asInstantiatedMethodType(
|
||||
writer.getCurrentPageElement(), executable);
|
||||
List<? extends TypeMirror> thrownTypes = instantiatedType.getThrownTypes();
|
||||
Map<String, TypeMirror> typeSubstitutions = getSubstitutedThrownTypes(
|
||||
List<? extends TypeMirror> substitutedExceptionTypes = instantiatedType.getThrownTypes();
|
||||
List<? extends TypeMirror> originalExceptionTypes = executable.getThrownTypes();
|
||||
Map<TypeMirror, TypeMirror> typeSubstitutions = getSubstitutedThrownTypes(
|
||||
utils.typeUtils,
|
||||
executable.getThrownTypes(),
|
||||
thrownTypes);
|
||||
Map<ThrowsTree, ExecutableElement> tagsMap = new LinkedHashMap<>();
|
||||
utils.getThrowsTrees(executable).forEach(t -> tagsMap.put(t, executable));
|
||||
Content result = writer.getOutputInstance();
|
||||
Set<String> alreadyDocumented = new HashSet<>();
|
||||
result.add(throwsTagsOutput(tagsMap, alreadyDocumented, typeSubstitutions, writer));
|
||||
result.add(inheritThrowsDocumentation(executable, thrownTypes, alreadyDocumented, typeSubstitutions, writer));
|
||||
result.add(linkToUndocumentedDeclaredExceptions(thrownTypes, alreadyDocumented, writer));
|
||||
return result;
|
||||
originalExceptionTypes,
|
||||
substitutedExceptionTypes);
|
||||
var exceptionSection = new ExceptionSectionBuilder(writer);
|
||||
// Step 1: Document exception tags
|
||||
Set<TypeMirror> alreadyDocumentedExceptions = new HashSet<>();
|
||||
List<ThrowsTree> exceptionTags = utils.getThrowsTrees(executable);
|
||||
for (ThrowsTree t : exceptionTags) {
|
||||
Element exceptionElement = getExceptionType(t, executable);
|
||||
outputAnExceptionTagDeeply(exceptionSection, exceptionElement, t, executable, alreadyDocumentedExceptions, typeSubstitutions, writer);
|
||||
}
|
||||
// Step 2: Document exception types from the `throws` clause (of a method)
|
||||
//
|
||||
// While methods can be inherited and overridden, constructors can be neither of those (JLS 8.8).
|
||||
// Therefore, it's only methods that participate in Step 2, which is about inheriting exception
|
||||
// documentation from ancestors.
|
||||
if (executable.getKind() == ElementKind.METHOD) {
|
||||
for (TypeMirror exceptionType : substitutedExceptionTypes) {
|
||||
Element exceptionElement = utils.typeUtils.asElement(exceptionType);
|
||||
Map<ThrowsTree, ExecutableElement> r;
|
||||
try {
|
||||
r = expandShallowly(exceptionElement, executable);
|
||||
} catch (Failure | DocFinder.NoOverriddenMethodsFound e) {
|
||||
// Ignore errors here because unlike @throws tags, the `throws` clause is implicit
|
||||
// documentation inheritance. It triggers a best-effort attempt to inherit
|
||||
// documentation. If there are errors in ancestors, they will likely be caught
|
||||
// once those ancestors are documented.
|
||||
continue;
|
||||
}
|
||||
if (r.isEmpty()) {
|
||||
// `exceptionType` is not documented by any tags from ancestors, skip it till Step 3
|
||||
continue;
|
||||
}
|
||||
if (!alreadyDocumentedExceptions.add(exceptionType)) {
|
||||
// it expands to something that has to have been documented on Step 1, skip
|
||||
continue;
|
||||
}
|
||||
for (Map.Entry<ThrowsTree, ExecutableElement> e : r.entrySet()) {
|
||||
outputAnExceptionTagDeeply(exceptionSection, exceptionElement, e.getKey(), e.getValue(), alreadyDocumentedExceptions, typeSubstitutions, writer);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Step 3: List those exceptions from the `throws` clause for which no documentation was found on Step 2
|
||||
for (TypeMirror e : substitutedExceptionTypes) {
|
||||
if (!alreadyDocumentedExceptions.add(e)) {
|
||||
continue;
|
||||
}
|
||||
exceptionSection.beginEntry(e);
|
||||
// this JavaDoc warning is similar to but different from that of DocLint:
|
||||
// dc.missing.throws = no @throws for {0}
|
||||
// TODO: comment out for now and revisit later;
|
||||
// commented out because of the generated noise for readObject/writeObject for serialized-form.html:
|
||||
// exceptionSection.continueEntry(writer.invalidTagOutput(configuration.getDocResources().getText("doclet.throws.undocumented", e), Optional.empty()));
|
||||
// configuration.getMessages().warning(holder, "doclet.throws.undocumented", e);
|
||||
exceptionSection.endEntry();
|
||||
}
|
||||
assert alreadyDocumentedExceptions.containsAll(substitutedExceptionTypes);
|
||||
return exceptionSection.build();
|
||||
}
|
||||
|
||||
private void outputAnExceptionTagDeeply(ExceptionSectionBuilder exceptionSection,
|
||||
Element originalExceptionElement,
|
||||
ThrowsTree tag,
|
||||
ExecutableElement holder,
|
||||
Set<TypeMirror> alreadyDocumentedExceptions,
|
||||
Map<TypeMirror, TypeMirror> typeSubstitutions,
|
||||
TagletWriter writer)
|
||||
throws Failure.ExceptionTypeNotFound,
|
||||
Failure.NotExceptionType,
|
||||
Failure.Invalid,
|
||||
Failure.Undocumented,
|
||||
Failure.UnsupportedTypeParameter,
|
||||
DocFinder.NoOverriddenMethodsFound
|
||||
{
|
||||
outputAnExceptionTagDeeply(exceptionSection, originalExceptionElement, tag, holder, true, alreadyDocumentedExceptions, typeSubstitutions, writer);
|
||||
}
|
||||
|
||||
private void outputAnExceptionTagDeeply(ExceptionSectionBuilder exceptionSection,
|
||||
Element originalExceptionElement,
|
||||
ThrowsTree tag,
|
||||
ExecutableElement holder,
|
||||
boolean beginNewEntry,
|
||||
Set<TypeMirror> alreadyDocumentedExceptions,
|
||||
Map<TypeMirror, TypeMirror> typeSubstitutions,
|
||||
TagletWriter writer)
|
||||
throws Failure.ExceptionTypeNotFound,
|
||||
Failure.NotExceptionType,
|
||||
Failure.Invalid,
|
||||
Failure.Undocumented,
|
||||
Failure.UnsupportedTypeParameter,
|
||||
DocFinder.NoOverriddenMethodsFound
|
||||
{
|
||||
var originalExceptionType = originalExceptionElement.asType();
|
||||
var exceptionType = typeSubstitutions.getOrDefault(originalExceptionType, originalExceptionType); // FIXME: ugh..........
|
||||
alreadyDocumentedExceptions.add(exceptionType);
|
||||
var description = tag.getDescription();
|
||||
int i = indexOfInheritDoc(tag, holder);
|
||||
if (i == -1) {
|
||||
// Since the description does not contain {@inheritDoc}, we either add a new entry, or
|
||||
// append to the current one. Here's an example of when we add a new entry:
|
||||
//
|
||||
// ... -> {@inheritDoc} -> <text>
|
||||
//
|
||||
// And here's an example of when we append to the current entry:
|
||||
//
|
||||
// ... -> <text> {@inheritDoc} <text> -> <text>
|
||||
|
||||
// if we don't need to add a new entry, assume it has been added before
|
||||
assert exceptionSection.debugEntryBegun() || beginNewEntry;
|
||||
if (beginNewEntry) { // add a new entry?
|
||||
// originalExceptionElement might be different from that that triggers this entry: for example, a
|
||||
// renamed exception-type type parameter
|
||||
exceptionSection.beginEntry(exceptionType);
|
||||
}
|
||||
// append to the current entry
|
||||
exceptionSection.continueEntry(writer.commentTagsToOutput(holder, description));
|
||||
if (beginNewEntry) { // if added a new entry, end it
|
||||
exceptionSection.endEntry();
|
||||
}
|
||||
} else { // expand a single {@inheritDoc}
|
||||
assert holder.getKind() == ElementKind.METHOD : holder.getKind(); // only methods can use {@inheritDoc}
|
||||
// Is the {@inheritDoc} that we found standalone (i.e. without preceding and following text)?
|
||||
boolean loneInheritDoc = description.size() == 1;
|
||||
assert !loneInheritDoc || i == 0 : i;
|
||||
boolean add = !loneInheritDoc && beginNewEntry;
|
||||
// we add a new entry if the {@inheritDoc} that we found has something else around
|
||||
// it and we can add a new entry (as instructed by the parent call)
|
||||
if (add) {
|
||||
exceptionSection.beginEntry(exceptionType);
|
||||
}
|
||||
if (i > 0) {
|
||||
// if there's anything preceding {@inheritDoc}, assume an entry has been added before
|
||||
assert exceptionSection.debugEntryBegun();
|
||||
Content beforeInheritDoc = writer.commentTagsToOutput(holder, description.subList(0, i));
|
||||
exceptionSection.continueEntry(beforeInheritDoc);
|
||||
}
|
||||
Map<ThrowsTree, ExecutableElement> tags;
|
||||
try {
|
||||
tags = expandShallowly(originalExceptionElement, holder);
|
||||
} catch (Failure.UnsupportedTypeParameter e) {
|
||||
// repack to fill in missing tag information
|
||||
throw new Failure.UnsupportedTypeParameter(e.element, tag, holder);
|
||||
}
|
||||
if (tags.isEmpty()) {
|
||||
throw new Failure.Undocumented(tag, holder, originalExceptionElement);
|
||||
}
|
||||
// if {@inheritDoc} is the only tag in the @throws description and
|
||||
// this call can add new entries to the exception section,
|
||||
// so can the recursive call
|
||||
boolean addNewEntryRecursively = beginNewEntry && !add;
|
||||
if (!addNewEntryRecursively && tags.size() > 1) {
|
||||
// current tag has more to description than just {@inheritDoc}
|
||||
// and thus cannot expand to multiple tags;
|
||||
// it's likely a documentation error
|
||||
throw new Failure.Invalid(tag, holder);
|
||||
}
|
||||
for (Map.Entry<ThrowsTree, ExecutableElement> e : tags.entrySet()) {
|
||||
outputAnExceptionTagDeeply(exceptionSection, originalExceptionElement, e.getKey(), e.getValue(), addNewEntryRecursively, alreadyDocumentedExceptions, typeSubstitutions, writer);
|
||||
}
|
||||
// this might be an empty list, which is fine
|
||||
if (!loneInheritDoc) {
|
||||
Content afterInheritDoc = writer.commentTagsToOutput(holder, description.subList(i + 1, description.size()));
|
||||
exceptionSection.continueEntry(afterInheritDoc);
|
||||
}
|
||||
if (add) {
|
||||
exceptionSection.endEntry();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int indexOfInheritDoc(ThrowsTree tag, ExecutableElement holder)
|
||||
throws Failure.Invalid
|
||||
{
|
||||
var description = tag.getDescription();
|
||||
int i = -1;
|
||||
for (var iterator = description.listIterator(); iterator.hasNext(); ) {
|
||||
DocTree t = iterator.next();
|
||||
if (t.getKind() == DocTree.Kind.INHERIT_DOC) {
|
||||
if (i != -1) {
|
||||
// an exception tag description contains more than one {@inheritDoc};
|
||||
// we consider it nonsensical and, hence, a documentation error
|
||||
throw new Failure.Invalid(t, holder);
|
||||
}
|
||||
i = iterator.previousIndex();
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
private Element getExceptionType(ThrowsTree tag, ExecutableElement holder)
|
||||
throws Failure.ExceptionTypeNotFound, Failure.NotExceptionType
|
||||
{
|
||||
Element e = utils.getCommentHelper(holder).getException(tag);
|
||||
if (e == null) {
|
||||
throw new Failure.ExceptionTypeNotFound(tag, holder);
|
||||
}
|
||||
// translate to a type mirror to perform a subtype test, which covers not only
|
||||
// classes (e.g. class X extends Exception) but also type variables
|
||||
// (e.g. <X extends Exception>)
|
||||
var t = e.asType();
|
||||
var subtypeTestInapplicable = switch (t.getKind()) {
|
||||
case EXECUTABLE, PACKAGE, MODULE -> true;
|
||||
default -> false;
|
||||
};
|
||||
if (subtypeTestInapplicable || !utils.typeUtils.isSubtype(t, utils.getThrowableType())) {
|
||||
// Aside from documentation errors, this condition might arise if the
|
||||
// source cannot be compiled or element we found is not what the
|
||||
// documentation author intended. Whatever the reason is (e.g.
|
||||
// see 8295543), we should not process such an element.
|
||||
throw new Failure.NotExceptionType(tag, holder, e);
|
||||
}
|
||||
var k = e.getKind();
|
||||
assert k == ElementKind.CLASS || k == ElementKind.TYPE_PARAMETER : k; // JLS 8.4.6
|
||||
return e;
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static sealed class Failure extends Exception {
|
||||
|
||||
private final DocTree tag;
|
||||
private final ExecutableElement holder;
|
||||
|
||||
Failure(DocTree tag, ExecutableElement holder) {
|
||||
super();
|
||||
this.tag = tag;
|
||||
this.holder = holder;
|
||||
}
|
||||
|
||||
DocTree tag() { return tag; }
|
||||
|
||||
ExecutableElement holder() { return holder; }
|
||||
|
||||
static final class ExceptionTypeNotFound extends Failure {
|
||||
|
||||
ExceptionTypeNotFound(ThrowsTree tag, ExecutableElement holder) {
|
||||
super(tag, holder);
|
||||
}
|
||||
|
||||
@Override ThrowsTree tag() { return (ThrowsTree) super.tag(); }
|
||||
}
|
||||
|
||||
static final class NotExceptionType extends Failure {
|
||||
|
||||
private final Element type;
|
||||
|
||||
public NotExceptionType(ThrowsTree tag, ExecutableElement holder, Element type) {
|
||||
super(tag, holder);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
Element type() { return type; }
|
||||
|
||||
@Override ThrowsTree tag() { return (ThrowsTree) super.tag(); }
|
||||
}
|
||||
|
||||
static final class Invalid extends Failure {
|
||||
|
||||
public Invalid(DocTree tag, ExecutableElement holder) {
|
||||
super(tag, holder);
|
||||
}
|
||||
}
|
||||
|
||||
static final class Undocumented extends Failure {
|
||||
|
||||
private final Element exceptionElement;
|
||||
|
||||
public Undocumented(DocTree tag, ExecutableElement holder, Element exceptionElement) {
|
||||
super(tag, holder);
|
||||
this.exceptionElement = exceptionElement;
|
||||
}
|
||||
}
|
||||
|
||||
static final class UnsupportedTypeParameter extends Failure {
|
||||
|
||||
private final Element element;
|
||||
|
||||
// careful: tag might be null
|
||||
public UnsupportedTypeParameter(Element element, ThrowsTree tag, ExecutableElement holder) {
|
||||
super(tag, holder);
|
||||
this.element = element;
|
||||
}
|
||||
|
||||
@Override ThrowsTree tag() { return (ThrowsTree) super.tag(); }
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns immediately inherited tags that document the provided exception type.
|
||||
*
|
||||
* A map associates a doc tree with its holder element externally. Such maps
|
||||
* have defined iteration order of entries, whose keys and values
|
||||
* are non-null.
|
||||
*/
|
||||
private Map<ThrowsTree, ExecutableElement> expandShallowly(Element exceptionType,
|
||||
ExecutableElement holder)
|
||||
throws Failure.ExceptionTypeNotFound,
|
||||
Failure.NotExceptionType,
|
||||
Failure.Invalid,
|
||||
Failure.UnsupportedTypeParameter,
|
||||
DocFinder.NoOverriddenMethodsFound
|
||||
{
|
||||
ElementKind kind = exceptionType.getKind();
|
||||
DocFinder.Criterion<Map<ThrowsTree, ExecutableElement>, Failure> criterion;
|
||||
if (kind == ElementKind.CLASS) {
|
||||
criterion = method -> {
|
||||
var tags = findByTypeElement(exceptionType, method);
|
||||
return toResult(exceptionType, method, tags);
|
||||
};
|
||||
} else {
|
||||
// Type parameters declared by a method are matched by position; the basis for
|
||||
// such position matching is JLS sections 8.4.2 and 8.4.4. We don't match
|
||||
// type parameters not declared by a method (e.g. declared by the
|
||||
// enclosing class or interface) because
|
||||
criterion = method -> {
|
||||
// TODO: add a test for the throws clause mentioning
|
||||
// a type parameter which is not declared by holder
|
||||
int i = holder.getTypeParameters().indexOf((TypeParameterElement) exceptionType);
|
||||
if (i == -1) { // the type parameter is not declared by `holder`
|
||||
throw new Failure.UnsupportedTypeParameter(exceptionType, null /* don't know if tag-related */, holder);
|
||||
}
|
||||
// if one method overrides the other, then those methods must
|
||||
// have the same number of type parameters (JLS 8.4.2)
|
||||
assert utils.elementUtils.overrides(holder, method, (TypeElement) holder.getEnclosingElement());
|
||||
var typeParameterElement = method.getTypeParameters().get(i);
|
||||
var tags = findByTypeElement(typeParameterElement, method);
|
||||
return toResult(exceptionType, method, tags);
|
||||
};
|
||||
}
|
||||
Result<Map<ThrowsTree, ExecutableElement>> result;
|
||||
try {
|
||||
result = utils.docFinder().trySearch(holder, criterion);
|
||||
} catch (Failure.NotExceptionType
|
||||
| Failure.ExceptionTypeNotFound
|
||||
| Failure.UnsupportedTypeParameter x) {
|
||||
// Here's why we do this ugly exception processing: the language does not allow us to
|
||||
// instantiate the exception type parameter in criterion with a union of specific
|
||||
// exceptions (i.e. Failure.ExceptionTypeNotFound | Failure.NotExceptionType),
|
||||
// so we instantiate it with a general Failure. We then refine the specific
|
||||
// exception being thrown, from the general exception we caught.
|
||||
throw x;
|
||||
} catch (Failure f) {
|
||||
throw newAssertionError(f);
|
||||
}
|
||||
if (result instanceof Result.Conclude<Map<ThrowsTree, ExecutableElement>> c) {
|
||||
return c.value();
|
||||
}
|
||||
return Map.of(); // an empty map is effectively ordered
|
||||
}
|
||||
|
||||
private static Result<Map<ThrowsTree, ExecutableElement>> toResult(Element target,
|
||||
ExecutableElement holder,
|
||||
List<ThrowsTree> tags) {
|
||||
if (!tags.isEmpty()) {
|
||||
// if there are tags for the target exception type, conclude search successfully
|
||||
return Result.CONCLUDE(toExceptionTags(holder, tags));
|
||||
}
|
||||
return Result.CONTINUE();
|
||||
// TODO: reintroduce this back for JDK-8295800:
|
||||
// if (holder.getThrownTypes().contains(target.asType())) {
|
||||
// // if there are no tags for the target exception type, BUT that type is
|
||||
// // mentioned in the `throws` clause, continue search
|
||||
// return Result.CONTINUE();
|
||||
// }
|
||||
// // there are no tags for the target exception type AND that type is not
|
||||
// // mentioned in the `throws` clause, skip search on the remaining part
|
||||
// // of the current branch of the hierarchy
|
||||
// // TODO: expand on this and add a test in JDK-8295800;
|
||||
// // for both checked and unchecked exceptions
|
||||
// return Result.SKIP();
|
||||
}
|
||||
|
||||
/*
|
||||
* Associates exception tags with their holder.
|
||||
*
|
||||
* Such a map is used as a data structure to pass around methods that output tags to content.
|
||||
*/
|
||||
private static Map<ThrowsTree, ExecutableElement> toExceptionTags(ExecutableElement holder,
|
||||
List<ThrowsTree> tags)
|
||||
{
|
||||
// preserve the tag order using the linked hash map
|
||||
var map = new LinkedHashMap<ThrowsTree, ExecutableElement>();
|
||||
for (var t : tags) {
|
||||
var prev = map.put(t, holder);
|
||||
assert prev == null; // there should be no equal exception tags
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private List<ThrowsTree> findByTypeElement(Element targetExceptionType,
|
||||
ExecutableElement executable)
|
||||
throws Failure.ExceptionTypeNotFound,
|
||||
Failure.NotExceptionType
|
||||
{
|
||||
var result = new LinkedList<ThrowsTree>();
|
||||
for (ThrowsTree t : utils.getThrowsTrees(executable)) {
|
||||
Element candidate = getExceptionType(t, executable);
|
||||
if (targetExceptionType.equals(candidate)) {
|
||||
result.add(t);
|
||||
}
|
||||
}
|
||||
return List.copyOf(result);
|
||||
}
|
||||
|
||||
/*
|
||||
* An exception section (that is, the "Throws:" section in the Method
|
||||
* or Constructor Details section) builder.
|
||||
*
|
||||
* The section is being built sequentially from top to bottom.
|
||||
*
|
||||
* Adapts one-off methods of writer to continuous building.
|
||||
*/
|
||||
private static class ExceptionSectionBuilder {
|
||||
|
||||
private final TagletWriter writer;
|
||||
private final Content result;
|
||||
private ContentBuilder current;
|
||||
private boolean began;
|
||||
private boolean headerAdded;
|
||||
private TypeMirror exceptionType;
|
||||
|
||||
ExceptionSectionBuilder(TagletWriter writer) {
|
||||
this.writer = writer;
|
||||
this.result = writer.getOutputInstance();
|
||||
}
|
||||
|
||||
void beginEntry(TypeMirror exceptionType) {
|
||||
if (began) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
began = true;
|
||||
current = new ContentBuilder();
|
||||
this.exceptionType = exceptionType;
|
||||
}
|
||||
|
||||
void continueEntry(Content c) {
|
||||
if (!began) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
current.add(c);
|
||||
}
|
||||
|
||||
public void endEntry() {
|
||||
if (!began) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
began = false;
|
||||
if (!headerAdded) {
|
||||
headerAdded = true;
|
||||
result.add(writer.getThrowsHeader());
|
||||
}
|
||||
result.add(writer.throwsTagOutput(exceptionType, current.isEmpty() ? Optional.empty() : Optional.of(current)));
|
||||
current = null;
|
||||
}
|
||||
|
||||
Content build() {
|
||||
return result;
|
||||
}
|
||||
|
||||
// for debugging purposes only
|
||||
boolean debugEntryBegun() {
|
||||
return began;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of substitutions for a list of thrown types with the original type-variable
|
||||
* name as a key and the instantiated type as a value. If no types need to be substituted
|
||||
* as a key and the instantiated type as a value. If no types need to be substituted
|
||||
* an empty map is returned.
|
||||
* @param declaredThrownTypes the originally declared thrown types.
|
||||
* @param instantiatedThrownTypes the thrown types in the context of the current type.
|
||||
* @return map of declared to instantiated thrown types or an empty map.
|
||||
*/
|
||||
private Map<String, TypeMirror> getSubstitutedThrownTypes(Types types,
|
||||
List<? extends TypeMirror> declaredThrownTypes,
|
||||
List<? extends TypeMirror> instantiatedThrownTypes) {
|
||||
if (!declaredThrownTypes.equals(instantiatedThrownTypes)) {
|
||||
Map<String, TypeMirror> map = new HashMap<>();
|
||||
Iterator<? extends TypeMirror> i1 = declaredThrownTypes.iterator();
|
||||
Iterator<? extends TypeMirror> i2 = instantiatedThrownTypes.iterator();
|
||||
while (i1.hasNext() && i2.hasNext()) {
|
||||
TypeMirror t1 = i1.next();
|
||||
TypeMirror t2 = i2.next();
|
||||
if (!types.isSameType(t1, t2))
|
||||
map.put(t1.toString(), t2);
|
||||
private Map<TypeMirror, TypeMirror> getSubstitutedThrownTypes(Types types,
|
||||
List<? extends TypeMirror> declaredThrownTypes,
|
||||
List<? extends TypeMirror> instantiatedThrownTypes) {
|
||||
Map<TypeMirror, TypeMirror> map = new HashMap<>();
|
||||
var i1 = declaredThrownTypes.iterator();
|
||||
var i2 = instantiatedThrownTypes.iterator();
|
||||
while (i1.hasNext() && i2.hasNext()) {
|
||||
TypeMirror t1 = i1.next();
|
||||
TypeMirror t2 = i2.next();
|
||||
if (!types.isSameType(t1, t2)) {
|
||||
map.put(t1, t2);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
return Map.of();
|
||||
// correspondence between types is established positionally, i.e.
|
||||
// pairwise, which means that the lists must have the same
|
||||
// number of elements; if they don't, this algorithm is
|
||||
// broken
|
||||
assert !i1.hasNext() && !i2.hasNext();
|
||||
// copyOf is unordered and this is fine: this map is for queries;
|
||||
// it doesn't control rendering order
|
||||
return Map.copyOf(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the generated content for a collection of {@code @throws} tags.
|
||||
*
|
||||
* @param throwsTags the tags to be converted; each tag is mapped to
|
||||
* a method it appears on
|
||||
* @param alreadyDocumented the set of exceptions that have already been
|
||||
* documented and thus must not be documented by
|
||||
* this method. All exceptions documented by this
|
||||
* method will be added to this set upon the
|
||||
* method's return.
|
||||
* @param writer the taglet-writer used by the doclet
|
||||
* @return the generated content for the tags
|
||||
*/
|
||||
private Content throwsTagsOutput(Map<ThrowsTree, ExecutableElement> throwsTags,
|
||||
Set<String> alreadyDocumented,
|
||||
Map<String, TypeMirror> typeSubstitutions,
|
||||
TagletWriter writer) {
|
||||
var utils = writer.configuration().utils;
|
||||
Content result = writer.getOutputInstance();
|
||||
var documentedInThisCall = new HashSet<String>();
|
||||
Map<ThrowsTree, ExecutableElement> flattenedExceptions = flatten(throwsTags, writer);
|
||||
flattenedExceptions.forEach((ThrowsTree t, ExecutableElement e) -> {
|
||||
var ch = utils.getCommentHelper(e);
|
||||
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()))) {
|
||||
return;
|
||||
}
|
||||
if (alreadyDocumented.isEmpty() && documentedInThisCall.isEmpty()) {
|
||||
result.add(writer.getThrowsHeader());
|
||||
}
|
||||
result.add(writer.throwsTagOutput(e, t, substituteType));
|
||||
if (substituteType != null) {
|
||||
documentedInThisCall.add(substituteType.toString());
|
||||
} else {
|
||||
documentedInThisCall.add(te != null
|
||||
? utils.getFullyQualifiedName(te, false)
|
||||
: excName);
|
||||
}
|
||||
});
|
||||
alreadyDocumented.addAll(documentedInThisCall);
|
||||
return result;
|
||||
private static AssertionError newAssertionError(Object... objects) {
|
||||
return new AssertionError(Arrays.toString(objects));
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 static String diagnosticDescriptionOf(Element e) {
|
||||
var name = e instanceof QualifiedNameable q ? q.getQualifiedName() : e.getSimpleName();
|
||||
return name + " (" + detailedDescriptionOf(e) + ")";
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
private Content inheritThrowsDocumentation(ExecutableElement holder,
|
||||
List<? extends TypeMirror> declaredExceptionTypes,
|
||||
Set<String> alreadyDocumented,
|
||||
Map<String, TypeMirror> typeSubstitutions,
|
||||
TagletWriter writer) {
|
||||
Content result = writer.getOutputInstance();
|
||||
if (holder.getKind() != ElementKind.METHOD) {
|
||||
// (Optimization.)
|
||||
// Of all executable elements, only methods and constructors are documented.
|
||||
// Of these two, only methods inherit documentation.
|
||||
// Don't waste time on constructors.
|
||||
assert holder.getKind() == ElementKind.CONSTRUCTOR : holder.getKind();
|
||||
return result;
|
||||
}
|
||||
var utils = writer.configuration().utils;
|
||||
Map<ThrowsTree, ExecutableElement> declaredExceptionTags = new LinkedHashMap<>();
|
||||
for (TypeMirror declaredExceptionType : declaredExceptionTypes) {
|
||||
var input = new DocFinder.Input(utils, holder, this,
|
||||
utils.getTypeName(declaredExceptionType, false));
|
||||
DocFinder.Output inheritedDoc = DocFinder.search(writer.configuration(), input);
|
||||
if (inheritedDoc.tagList.isEmpty()) {
|
||||
input = new DocFinder.Input(utils, holder, this,
|
||||
utils.getTypeName(declaredExceptionType, true));
|
||||
inheritedDoc = DocFinder.search(writer.configuration(), input);
|
||||
private static String detailedDescriptionOf(Element e) {
|
||||
// It might be important to describe the element in detail. Sometimes
|
||||
// elements share the same simple and/or qualified name. Outputting
|
||||
// individual components of that name as well as their kinds helps
|
||||
// the user disambiguate such elements.
|
||||
var lowerCasedKind = e.getKind().toString().toLowerCase(Locale.ROOT);
|
||||
var thisElementDescription = lowerCasedKind + " " + switch (e.getKind()) {
|
||||
// A package is never enclosed in a package and a module is
|
||||
// never never enclosed in a module, no matter what their
|
||||
// qualified name might suggest. Get their qualified
|
||||
// name directly. Also, unnamed packages and
|
||||
// modules require special treatment.
|
||||
case PACKAGE -> {
|
||||
var p = (PackageElement) e;
|
||||
// might use i18n in the future
|
||||
yield p.isUnnamed() ? "<unnamed package>" : p.getQualifiedName();
|
||||
}
|
||||
if (!inheritedDoc.tagList.isEmpty()) {
|
||||
if (inheritedDoc.holder == null) {
|
||||
inheritedDoc.holder = holder;
|
||||
}
|
||||
var h = (ExecutableElement) inheritedDoc.holder;
|
||||
inheritedDoc.tagList.forEach(t -> declaredExceptionTags.put((ThrowsTree) t, h));
|
||||
case MODULE -> {
|
||||
var m = (ModuleElement) e;
|
||||
// might use i18n in the future, if there's value in displaying an unnamed module
|
||||
yield m.isUnnamed() ? "<unnamed module>" : m.getQualifiedName();
|
||||
}
|
||||
default -> e.getSimpleName();
|
||||
};
|
||||
if (e.getEnclosingElement() == null) {
|
||||
return thisElementDescription;
|
||||
}
|
||||
result.add(throwsTagsOutput(declaredExceptionTags, alreadyDocumented, typeSubstitutions,
|
||||
writer));
|
||||
return result;
|
||||
}
|
||||
|
||||
private Content linkToUndocumentedDeclaredExceptions(List<? extends TypeMirror> declaredExceptionTypes,
|
||||
Set<String> alreadyDocumented,
|
||||
TagletWriter writer) {
|
||||
// TODO: assert declaredExceptionTypes are instantiated
|
||||
var utils = writer.configuration().utils;
|
||||
Content result = writer.getOutputInstance();
|
||||
for (TypeMirror declaredExceptionType : declaredExceptionTypes) {
|
||||
TypeElement te = utils.asTypeElement(declaredExceptionType);
|
||||
if (te != null &&
|
||||
!alreadyDocumented.contains(declaredExceptionType.toString()) &&
|
||||
!alreadyDocumented.contains(utils.getFullyQualifiedName(te, false))) {
|
||||
if (alreadyDocumented.isEmpty()) {
|
||||
result.add(writer.getThrowsHeader());
|
||||
}
|
||||
result.add(writer.throwsTagOutput(declaredExceptionType));
|
||||
alreadyDocumented.add(utils.getSimpleName(te));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
var enclosingElementDescription = detailedDescriptionOf(e.getEnclosingElement());
|
||||
return enclosingElementDescription + " " + thisElementDescription;
|
||||
}
|
||||
}
|
||||
|
@ -56,6 +56,7 @@ import com.sun.source.util.DocTrees;
|
||||
import com.sun.source.util.SimpleDocTreeVisitor;
|
||||
import com.sun.source.util.TreePath;
|
||||
import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration;
|
||||
import jdk.javadoc.internal.doclets.toolkit.util.DocFinder.Result;
|
||||
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ElementKind;
|
||||
@ -66,6 +67,7 @@ import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.TypeKind;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static com.sun.source.doctree.DocTree.Kind.SEE;
|
||||
import static com.sun.source.doctree.DocTree.Kind.SERIAL_FIELD;
|
||||
@ -125,6 +127,7 @@ public class CommentHelper {
|
||||
}
|
||||
|
||||
Element getElement(ReferenceTree rtree) {
|
||||
// We need to lookup type variables and other types
|
||||
Utils utils = configuration.utils;
|
||||
// likely a synthesized tree
|
||||
if (path == null) {
|
||||
@ -533,12 +536,15 @@ public class CommentHelper {
|
||||
|
||||
private DocTreePath getInheritedDocTreePath(DocTree dtree, ExecutableElement ee) {
|
||||
Utils utils = configuration.utils;
|
||||
DocFinder.Output inheritedDoc =
|
||||
DocFinder.search(configuration,
|
||||
new DocFinder.Input(utils, ee));
|
||||
return inheritedDoc.holder == ee
|
||||
var docFinder = utils.docFinder();
|
||||
Optional<ExecutableElement> inheritedDoc = docFinder.search(ee,
|
||||
(m -> {
|
||||
Optional<ExecutableElement> optional = utils.getFullBody(m).isEmpty() ? Optional.empty() : Optional.of(m);
|
||||
return Result.fromOptional(optional);
|
||||
})).toOptional();
|
||||
return inheritedDoc.isEmpty() || inheritedDoc.get().equals(ee)
|
||||
? null
|
||||
: utils.getCommentHelper(inheritedDoc.holder).getDocTreePath(dtree);
|
||||
: utils.getCommentHelper(inheritedDoc.get()).getDocTreePath(dtree);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -25,239 +25,218 @@
|
||||
|
||||
package jdk.javadoc.internal.doclets.toolkit.util;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
|
||||
import com.sun.source.doctree.DocTree;
|
||||
import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration;
|
||||
import jdk.javadoc.internal.doclets.toolkit.taglets.InheritableTaglet;
|
||||
|
||||
/**
|
||||
* Search for the requested documentation. Inherit documentation if necessary.
|
||||
*/
|
||||
public class DocFinder {
|
||||
|
||||
public record DocTreeInfo(DocTree docTree, Element element) { }
|
||||
|
||||
/**
|
||||
* The class that encapsulates the input.
|
||||
/*
|
||||
* A specialized, possibly stateful, function that accepts a method in the
|
||||
* hierarchy and returns a value that controls the search or throws an
|
||||
* exception, which terminates the search and transparently bubbles
|
||||
* up the stack.
|
||||
*/
|
||||
public static class Input {
|
||||
@FunctionalInterface
|
||||
public interface Criterion<T, X extends Throwable> {
|
||||
Result<T> apply(ExecutableElement method) throws X;
|
||||
}
|
||||
|
||||
/**
|
||||
* The element to search documentation from.
|
||||
*/
|
||||
public Element element;
|
||||
private final Function<ExecutableElement, ExecutableElement> overriddenMethodLookup;
|
||||
private final BiFunction<ExecutableElement, ExecutableElement, Iterable<ExecutableElement>> implementedMethodsLookup;
|
||||
|
||||
/**
|
||||
* The taglet to search for documentation on behalf of. Null if we want
|
||||
* to search for overall documentation.
|
||||
*/
|
||||
public InheritableTaglet taglet;
|
||||
DocFinder(Function<ExecutableElement, ExecutableElement> overriddenMethodLookup,
|
||||
BiFunction<ExecutableElement, ExecutableElement, Iterable<ExecutableElement>> implementedMethodsLookup) {
|
||||
this.overriddenMethodLookup = overriddenMethodLookup;
|
||||
this.implementedMethodsLookup = implementedMethodsLookup;
|
||||
}
|
||||
|
||||
/**
|
||||
* The id of the tag to retrieve documentation for.
|
||||
*/
|
||||
public String tagId;
|
||||
@SuppressWarnings("serial")
|
||||
public static final class NoOverriddenMethodsFound extends Exception {
|
||||
|
||||
/**
|
||||
* The tag to retrieve documentation for. This is only used for the
|
||||
* {@code {@inheritDoc}} tag.
|
||||
*/
|
||||
public final DocTreeInfo docTreeInfo;
|
||||
// only DocFinder should instantiate this exception
|
||||
private NoOverriddenMethodsFound() { }
|
||||
}
|
||||
|
||||
/**
|
||||
* True if we only want to search for the first sentence.
|
||||
*/
|
||||
public boolean isFirstSentence;
|
||||
public <T, X extends Throwable> Result<T> search(ExecutableElement method,
|
||||
Criterion<T, X> criterion)
|
||||
throws X
|
||||
{
|
||||
return search(method, true, criterion);
|
||||
}
|
||||
|
||||
/**
|
||||
* True if we are looking for documentation to replace the {@code {@inheritDoc}} tag.
|
||||
*/
|
||||
public boolean isInheritDocTag;
|
||||
|
||||
/**
|
||||
* Used to distinguish between type variable param tags and regular
|
||||
* param tags.
|
||||
*/
|
||||
public boolean isTypeVariableParamTag;
|
||||
|
||||
public final Utils utils;
|
||||
|
||||
public Input(Utils utils,
|
||||
Element element,
|
||||
InheritableTaglet taglet,
|
||||
String tagId) {
|
||||
this(utils, element);
|
||||
this.taglet = taglet;
|
||||
this.tagId = tagId;
|
||||
}
|
||||
|
||||
public Input(Utils utils,
|
||||
Element element,
|
||||
InheritableTaglet taglet,
|
||||
String tagId,
|
||||
boolean isTypeVariableParamTag) {
|
||||
this(utils, element);
|
||||
this.taglet = taglet;
|
||||
this.tagId = tagId;
|
||||
this.isTypeVariableParamTag = isTypeVariableParamTag;
|
||||
}
|
||||
|
||||
public Input(Utils utils, Element element, InheritableTaglet taglet) {
|
||||
this(utils, element);
|
||||
this.taglet = taglet;
|
||||
}
|
||||
|
||||
public Input(Utils utils, Element element) {
|
||||
this.element = Objects.requireNonNull(element);
|
||||
this.utils = utils;
|
||||
this.docTreeInfo = new DocTreeInfo(null, null);
|
||||
}
|
||||
|
||||
public Input(Utils utils,
|
||||
Element element,
|
||||
InheritableTaglet taglet,
|
||||
DocTreeInfo dtInfo,
|
||||
boolean isFirstSentence,
|
||||
boolean isInheritDocTag) {
|
||||
this.utils = utils;
|
||||
this.element = Objects.requireNonNull(element);
|
||||
this.taglet = taglet;
|
||||
this.isFirstSentence = isFirstSentence;
|
||||
this.isInheritDocTag = isInheritDocTag;
|
||||
this.docTreeInfo = dtInfo;
|
||||
}
|
||||
|
||||
private Input copy() {
|
||||
var copy = new Input(utils, element, taglet, docTreeInfo,
|
||||
isFirstSentence, isInheritDocTag);
|
||||
copy.tagId = tagId;
|
||||
copy.isTypeVariableParamTag = isTypeVariableParamTag;
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* For debugging purposes.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
String encl = element == null ? "" : element.getEnclosingElement().toString() + "::";
|
||||
return "Input{" + "element=" + encl + element
|
||||
+ ", taglet=" + taglet
|
||||
+ ", tagId=" + tagId + ", tag=" + docTreeInfo
|
||||
+ ", isFirstSentence=" + isFirstSentence
|
||||
+ ", isInheritDocTag=" + isInheritDocTag
|
||||
+ ", isTypeVariableParamTag=" + isTypeVariableParamTag
|
||||
+ ", utils=" + utils + '}';
|
||||
public <T, X extends Throwable> Result<T> search(ExecutableElement method,
|
||||
boolean includeMethod,
|
||||
Criterion<T, X> criterion)
|
||||
throws X
|
||||
{
|
||||
try {
|
||||
return search0(method, includeMethod, false, criterion);
|
||||
} catch (NoOverriddenMethodsFound e) {
|
||||
// should not happen because the exception flag is unset
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The class that encapsulates the output.
|
||||
public <T, X extends Throwable> Result<T> trySearch(ExecutableElement method,
|
||||
Criterion<T, X> criterion)
|
||||
throws NoOverriddenMethodsFound, X
|
||||
{
|
||||
return search0(method, false, true, criterion);
|
||||
}
|
||||
|
||||
/*
|
||||
* Searches through the overridden methods hierarchy of the provided method.
|
||||
*
|
||||
* Depending on how it is instructed, the search begins from either the given
|
||||
* method or the first method that the given method overrides. The search
|
||||
* then applies the given criterion to methods it encounters, in the
|
||||
* hierarchy order, until either of the following happens:
|
||||
*
|
||||
* - the criterion concludes the search
|
||||
* - the criterion throws an exception
|
||||
* - the hierarchy is exhausted
|
||||
*
|
||||
* If the search succeeds, the returned result is of type Conclude.
|
||||
* Otherwise, the returned result is generally that of the most
|
||||
* recent call to Criterion::apply.
|
||||
*
|
||||
* If the given method overrides no methods (i.e. hierarchy consists of the
|
||||
* given method only) and the search is instructed to detect that, the
|
||||
* search terminates with an exception.
|
||||
*/
|
||||
public static class Output {
|
||||
private <T, X extends Throwable> Result<T> search0(ExecutableElement method,
|
||||
boolean includeMethodInSearch,
|
||||
boolean throwExceptionIfDoesNotOverride,
|
||||
Criterion<T, X> criterion)
|
||||
throws NoOverriddenMethodsFound, X
|
||||
{
|
||||
// if the "overrides" check is requested and does not pass, throw the exception
|
||||
// first so that it trumps the result that the search would otherwise had
|
||||
Iterator<ExecutableElement> methods = methodsOverriddenBy(method);
|
||||
if (throwExceptionIfDoesNotOverride && !methods.hasNext() ) {
|
||||
throw new NoOverriddenMethodsFound();
|
||||
}
|
||||
Result<T> r = includeMethodInSearch ? criterion.apply(method) : Result.CONTINUE();
|
||||
if (!(r instanceof Result.Continue<T>)) {
|
||||
return r;
|
||||
}
|
||||
while (methods.hasNext()) {
|
||||
ExecutableElement m = methods.next();
|
||||
r = search0(m, true, false /* don't check for overrides */, criterion);
|
||||
if (r instanceof Result.Conclude<T>) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* The tag that holds the documentation. Null if documentation
|
||||
* is not held by a tag.
|
||||
*/
|
||||
public DocTree holderTag;
|
||||
// We see both overridden and implemented methods as overridden
|
||||
// (see JLS 8.4.8.1. Overriding (by Instance Methods))
|
||||
private Iterator<ExecutableElement> methodsOverriddenBy(ExecutableElement method) {
|
||||
// TODO: create a lazy iterator if required
|
||||
var list = new ArrayList<ExecutableElement>();
|
||||
ExecutableElement overridden = overriddenMethodLookup.apply(method);
|
||||
if (overridden != null) {
|
||||
list.add(overridden);
|
||||
}
|
||||
implementedMethodsLookup.apply(method, method).forEach(list::add);
|
||||
return list.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* The element that holds the documentation.
|
||||
*/
|
||||
public Element holder;
|
||||
private static final Result<?> SKIP = new Skipped<>();
|
||||
private static final Result<?> CONTINUE = new Continued<>();
|
||||
|
||||
/**
|
||||
* The inherited documentation.
|
||||
*/
|
||||
public List<? extends DocTree> inlineTags = List.of();
|
||||
/*
|
||||
* Use static factory methods to get the desired result to return from
|
||||
* Criterion. Use instanceof to check for a result type returned from
|
||||
* a search. If a use case permits and you prefer Optional API, use
|
||||
* the fromOptional/toOptional convenience methods to get and
|
||||
* check for the result respectively.
|
||||
*/
|
||||
public sealed interface Result<T> {
|
||||
|
||||
/**
|
||||
* False if documentation could not be inherited.
|
||||
*/
|
||||
public boolean isValidInheritDocTag = true;
|
||||
sealed interface Skip<T> extends Result<T> permits Skipped { }
|
||||
|
||||
/**
|
||||
* When automatically inheriting throws tags, you sometimes must inherit
|
||||
* more than one tag. For example, if a method declares that it throws
|
||||
* IOException and the overridden method has {@code @throws} tags for IOException and
|
||||
* ZipException, both tags would be inherited because ZipException is a
|
||||
* subclass of IOException. This allows multiple tag inheritance.
|
||||
*/
|
||||
public final List<DocTree> tagList = new ArrayList<>();
|
||||
sealed interface Continue<T> extends Result<T> permits Continued { }
|
||||
|
||||
/**
|
||||
* For debugging purposes.
|
||||
sealed interface Conclude<T> extends Result<T> permits Concluded {
|
||||
|
||||
T value();
|
||||
}
|
||||
|
||||
/*
|
||||
* Skips the search on the part of the hierarchy above the method for
|
||||
* which this result is returned and continues the search from that
|
||||
* method sibling, if any.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
String encl = holder == null ? "" : holder.getEnclosingElement().toString() + "::";
|
||||
return "Output{" + "holderTag=" + holderTag
|
||||
+ ", holder=" + encl + holder
|
||||
+ ", inlineTags=" + inlineTags
|
||||
+ ", isValidInheritDocTag=" + isValidInheritDocTag
|
||||
+ ", tagList=" + tagList + '}';
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T> Result<T> SKIP() {
|
||||
return (Result<T>) SKIP;
|
||||
}
|
||||
|
||||
/*
|
||||
* Continues the search.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T> Result<T> CONTINUE() {
|
||||
return (Result<T>) CONTINUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Concludes the search with the given result.
|
||||
*/
|
||||
static <T> Result<T> CONCLUDE(T value) {
|
||||
return new Concluded<>(value);
|
||||
}
|
||||
|
||||
/*
|
||||
* Translates this Result into Optional.
|
||||
*
|
||||
* Convenience method. Call on the result of a search if you are only
|
||||
* interested in whether the search succeeded or failed and you
|
||||
* prefer the Optional API.
|
||||
*/
|
||||
default Optional<T> toOptional() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/*
|
||||
* Translates the given Optional into a binary decision whether to
|
||||
* conclude the search or continue it.
|
||||
*
|
||||
* Convenience method. Use in Criterion that can easily provide
|
||||
* suitable Optional. Don't use if Criterion needs to skip.
|
||||
*/
|
||||
static <T> Result<T> fromOptional(Optional<T> optional) {
|
||||
return optional.map(Result::CONCLUDE).orElseGet(Result::CONTINUE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for the requested comments in the given element. If it does not
|
||||
* have comments, return the inherited comments if possible.
|
||||
*
|
||||
* @param input the input object used to perform the search.
|
||||
*
|
||||
* @return an Output object representing the documentation that was found.
|
||||
*/
|
||||
public static Output search(BaseConfiguration configuration, Input input) {
|
||||
Output output = new Output();
|
||||
Utils utils = configuration.utils;
|
||||
if (input.isInheritDocTag) {
|
||||
//Do nothing because "element" does not have any documentation.
|
||||
//All it has is {@inheritDoc}.
|
||||
} else if (input.taglet == null) {
|
||||
//We want overall documentation.
|
||||
output.inlineTags = input.isFirstSentence
|
||||
? utils.getFirstSentenceTrees(input.element)
|
||||
: utils.getFullBody(input.element);
|
||||
output.holder = input.element;
|
||||
} else {
|
||||
input.taglet.inherit(input, output);
|
||||
// Note: we hide records behind interfaces, as implementation detail.
|
||||
// We don't directly implement Result with these records because it
|
||||
// would require more exposure and commitment than is desired. For
|
||||
// example, there would need to be public constructors, which
|
||||
// would circumvent static factory methods.
|
||||
|
||||
private record Skipped<T>() implements DocFinder.Result.Skip<T> { }
|
||||
|
||||
private record Continued<T>() implements DocFinder.Result.Continue<T> { }
|
||||
|
||||
private record Concluded<T>(T value) implements DocFinder.Result.Conclude<T> {
|
||||
|
||||
Concluded {
|
||||
Objects.requireNonNull(value);
|
||||
}
|
||||
|
||||
if (!output.inlineTags.isEmpty()) {
|
||||
return output;
|
||||
@Override
|
||||
public Optional<T> toOptional() {
|
||||
return Optional.of(value);
|
||||
}
|
||||
output.isValidInheritDocTag = false;
|
||||
Input inheritedSearchInput = input.copy();
|
||||
inheritedSearchInput.isInheritDocTag = false;
|
||||
if (utils.isMethod(input.element)) {
|
||||
ExecutableElement m = (ExecutableElement) input.element;
|
||||
ExecutableElement overriddenMethod = utils.overriddenMethod(m);
|
||||
if (overriddenMethod != null) {
|
||||
inheritedSearchInput.element = overriddenMethod;
|
||||
output = search(configuration, inheritedSearchInput);
|
||||
output.isValidInheritDocTag = true;
|
||||
if (!output.inlineTags.isEmpty()) {
|
||||
return output;
|
||||
}
|
||||
}
|
||||
TypeElement encl = utils.getEnclosingTypeElement(input.element);
|
||||
VisibleMemberTable vmt = configuration.getVisibleMemberTable(encl);
|
||||
List<ExecutableElement> implementedMethods = vmt.getImplementedMethods(m);
|
||||
for (ExecutableElement implementedMethod : implementedMethods) {
|
||||
inheritedSearchInput.element = implementedMethod;
|
||||
output = search(configuration, inheritedSearchInput);
|
||||
output.isValidInheritDocTag = true;
|
||||
if (!output.inlineTags.isEmpty()) {
|
||||
return output;
|
||||
}
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
@ -138,6 +138,7 @@ public class Utils {
|
||||
public final Types typeUtils;
|
||||
public final Comparators comparators;
|
||||
private final JavaScriptScanner javaScriptScanner;
|
||||
private final DocFinder docFinder = newDocFinder();
|
||||
|
||||
public Utils(BaseConfiguration c) {
|
||||
configuration = c;
|
||||
@ -1914,6 +1915,9 @@ public class Utils {
|
||||
|
||||
private SimpleElementVisitor14<String, Void> snvisitor = null;
|
||||
|
||||
// If `e` is a static nested class, this method will return e's simple name
|
||||
// preceded by `.` and an outer type; this is not how JLS defines "simple
|
||||
// name". See "Simple Name", "Qualified Name", "Fully Qualified Name".
|
||||
private String getSimpleName0(Element e) {
|
||||
if (snvisitor == null) {
|
||||
snvisitor = new SimpleElementVisitor14<>() {
|
||||
@ -1927,7 +1931,7 @@ public class Utils {
|
||||
StringBuilder sb = new StringBuilder(e.getSimpleName().toString());
|
||||
Element enclosed = e.getEnclosingElement();
|
||||
while (enclosed != null
|
||||
&& (enclosed.getKind().isClass() || enclosed.getKind().isInterface())) {
|
||||
&& (enclosed.getKind().isDeclaredType())) {
|
||||
sb.insert(0, enclosed.getSimpleName() + ".");
|
||||
enclosed = enclosed.getEnclosingElement();
|
||||
}
|
||||
@ -2796,4 +2800,16 @@ public class Utils {
|
||||
boolean isPreview(Element el);
|
||||
}
|
||||
|
||||
public DocFinder docFinder() {
|
||||
return docFinder;
|
||||
}
|
||||
|
||||
private DocFinder newDocFinder() {
|
||||
return new DocFinder(this::overriddenMethod, this::implementedMethods);
|
||||
}
|
||||
|
||||
private Iterable<ExecutableElement> implementedMethods(ExecutableElement originalMethod, ExecutableElement m) {
|
||||
var type = configuration.utils.getEnclosingTypeElement(m);
|
||||
return configuration.getVisibleMemberTable(type).getImplementedMethods(originalMethod);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2009, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2009, 2022, 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
|
||||
@ -185,7 +185,7 @@ public class TestHtmlDefinitionListTag extends JavadocTester {
|
||||
<dt>Throws:</dt>
|
||||
<dd><code>java.lang.IllegalArgumentException</code> - if the <code>owner</code>'s
|
||||
<code>GraphicsConfiguration</code> is not from a screen device</dd>
|
||||
<dd><code>HeadlessException</code></dd>
|
||||
<dd><code>java.awt.HeadlessException</code></dd>
|
||||
</dl>""",
|
||||
"""
|
||||
<dl class="notes">
|
||||
@ -304,7 +304,7 @@ public class TestHtmlDefinitionListTag extends JavadocTester {
|
||||
<dt>Throws:</dt>
|
||||
<dd><code>java.lang.IllegalArgumentException</code> - if the <code>owner</code>'s
|
||||
<code>GraphicsConfiguration</code> is not from a screen device</dd>
|
||||
<dd><code>HeadlessException</code></dd>
|
||||
<dd><code>java.awt.HeadlessException</code></dd>
|
||||
</dl>""",
|
||||
"""
|
||||
<dl class="notes">
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2009, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2009, 2022, 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
|
||||
@ -74,7 +74,7 @@ public class C1 implements Serializable {
|
||||
* @param test boolean value
|
||||
* @exception IllegalArgumentException if the <code>owner</code>'s
|
||||
* <code>GraphicsConfiguration</code> is not from a screen device
|
||||
* @exception HeadlessException
|
||||
* @exception java.awt.HeadlessException
|
||||
*/
|
||||
public C1(String title, boolean test) {
|
||||
|
||||
|
@ -54,7 +54,7 @@ public class TestTagInheritance extends JavadocTester {
|
||||
+ "does not override or implement any method.");
|
||||
|
||||
//Test valid usage of inheritDoc tag.
|
||||
for (int i = 1; i < 40; i++) {
|
||||
for (int i = 1; i < 39; i++) {
|
||||
checkOutput("pkg/TestTagInheritance.html", true,
|
||||
"Test " + i + " passes");
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2001, 2003, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2001, 2022, 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
|
||||
@ -66,7 +66,6 @@ java.lang.NullPointerException {
|
||||
* @param <Q> Test 33 passes.
|
||||
* @param x2 Test 35 passes.
|
||||
* @throws java.io.IOException Test 37 passes.
|
||||
* @throws java.util.zip.ZipException Test 39 passes.
|
||||
*/
|
||||
public <P,Q> String testSuperSuperMethod2(int x1, int x2) {
|
||||
return null;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2014, 2022, 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
|
||||
@ -29,4 +29,22 @@ public abstract class Abstract {
|
||||
*/
|
||||
abstract void method() throws NullPointerException;
|
||||
|
||||
// NOTE: Not sure why this test suggests that IndexOutOfBoundsException
|
||||
// should not appear due to compatibility with some buggy behavior.
|
||||
//
|
||||
// Here's the expected behavior: documentation for an exception X is never
|
||||
// inherited by an overrider unless it "pulls" it by either (or both)
|
||||
// of these:
|
||||
//
|
||||
// * tag:
|
||||
// @throws X {@inheritDoc}
|
||||
// * clause:
|
||||
// throws ..., X,...
|
||||
//
|
||||
// Neither of those are applicable here. Even taking into account
|
||||
// mechanisms such as the one introduced in 4947455, neither of
|
||||
// NullPointerException and IndexOutOfBoundsException is a subclass
|
||||
// of the other.
|
||||
//
|
||||
// So, IndexOutOfBoundsException should not appear in Extender.
|
||||
}
|
||||
|
@ -0,0 +1,490 @@
|
||||
/*
|
||||
* Copyright (c) 2022, 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 8291869
|
||||
* @library /tools/lib ../../lib
|
||||
* @modules jdk.compiler/com.sun.tools.javac.api
|
||||
* jdk.compiler/com.sun.tools.javac.main
|
||||
* jdk.javadoc/jdk.javadoc.internal.tool
|
||||
* @build toolbox.ToolBox javadoc.tester.*
|
||||
* @run main TestExceptionTypeMatching
|
||||
*/
|
||||
|
||||
import javadoc.tester.JavadocTester;
|
||||
import toolbox.ToolBox;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/*
|
||||
* The goal of the tests in this suite is two-fold:
|
||||
*
|
||||
* 1. Provoke javadoc into treating like-named but different elements as
|
||||
* the same element
|
||||
* 2. Provoke javadoc into treating differently named but semantically
|
||||
* same elements as different elements
|
||||
*/
|
||||
public class TestExceptionTypeMatching extends JavadocTester {
|
||||
|
||||
public static void main(String... args) throws Exception {
|
||||
var tester = new TestExceptionTypeMatching();
|
||||
tester.runTests(m -> new Object[]{Paths.get(m.getName())});
|
||||
}
|
||||
|
||||
private final ToolBox tb = new ToolBox();
|
||||
|
||||
/*
|
||||
* In Child, MyException is c.MyException, whereas in Parent, MyException
|
||||
* is p.MyException. Those are different exceptions which happen to
|
||||
* share the simple name.
|
||||
*/
|
||||
@Test
|
||||
public void testDifferentPackages(Path base) throws Exception {
|
||||
var src = base.resolve("src");
|
||||
tb.writeJavaFiles(src, """
|
||||
package c;
|
||||
|
||||
import p.Parent;
|
||||
|
||||
public class Child extends Parent {
|
||||
|
||||
/** @throws MyException {@inheritDoc} */
|
||||
@Override
|
||||
public void m() { }
|
||||
}
|
||||
""", """
|
||||
package c;
|
||||
|
||||
public class MyException extends RuntimeException { }
|
||||
|
||||
""", """
|
||||
package p;
|
||||
|
||||
public class Parent {
|
||||
|
||||
/** @throws MyException sometimes */
|
||||
public void m() { }
|
||||
}
|
||||
""", """
|
||||
package p;
|
||||
|
||||
public class MyException extends RuntimeException { }
|
||||
""");
|
||||
javadoc("-d", base.resolve("out").toString(), "-sourcepath", src.toString(), "c", "p");
|
||||
checkExit(Exit.OK);
|
||||
checkOutput(Output.OUT, true, """
|
||||
Child.java:7: warning: overridden methods do not document exception type c.MyException \
|
||||
(module <unnamed module> package c class MyException)
|
||||
/** @throws MyException {@inheritDoc} */
|
||||
^
|
||||
""");
|
||||
}
|
||||
|
||||
/*
|
||||
* Type parameters declared by methods where one of the methods overrides
|
||||
* the other, are matched by position, not by name. In this example, <P>
|
||||
* and <R> are semantically the same.
|
||||
*/
|
||||
@Test
|
||||
public void testDifferentTypeVariables1(Path base) throws Exception {
|
||||
var src = base.resolve("src");
|
||||
tb.writeJavaFiles(src, """
|
||||
package x;
|
||||
|
||||
public class Parent {
|
||||
|
||||
/** @throws P sometimes */
|
||||
public <P extends RuntimeException> void m() { }
|
||||
}
|
||||
""", """
|
||||
package x;
|
||||
|
||||
public class Child extends Parent {
|
||||
|
||||
/** @throws R {@inheritDoc} */
|
||||
@Override
|
||||
public <R extends RuntimeException> void m() { }
|
||||
}
|
||||
""");
|
||||
javadoc("-d", base.resolve("out").toString(), "-sourcepath", src.toString(), "x");
|
||||
checkExit(Exit.OK);
|
||||
checkOutput("x/Child.html", true, """
|
||||
<dl class="notes">
|
||||
<dt>Overrides:</dt>
|
||||
<dd><code><a href="Parent.html#m()">m</a></code> in class <code>\
|
||||
<a href="Parent.html" title="class in x">Parent</a></code></dd>
|
||||
<dt>Throws:</dt>
|
||||
<dd><code>R</code> - sometimes</dd>
|
||||
</dl>
|
||||
""");
|
||||
}
|
||||
|
||||
/*
|
||||
* Type parameters declared by methods where one of the methods overrides
|
||||
* the other, are matched by position, not by name.
|
||||
*
|
||||
* Here the match is criss-cross:
|
||||
*
|
||||
* - Child.m's <K> corresponds to Parent.m's <V>
|
||||
* - Child.m's <V> corresponds to Parent.m's <K>
|
||||
*/
|
||||
@Test
|
||||
public void testDifferentTypeVariables2(Path base) throws Exception {
|
||||
var src = base.resolve("src");
|
||||
tb.writeJavaFiles(src, """
|
||||
package x;
|
||||
|
||||
public class Parent {
|
||||
|
||||
/**
|
||||
* @throws K some of the times
|
||||
* @throws V other times
|
||||
*/
|
||||
public <K extends RuntimeException, V extends RuntimeException> void m() { }
|
||||
}
|
||||
""", """
|
||||
package x;
|
||||
|
||||
public class Child extends Parent {
|
||||
|
||||
/**
|
||||
* @throws K {@inheritDoc}
|
||||
* @throws V {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public <V extends RuntimeException, K extends RuntimeException> void m() { }
|
||||
}
|
||||
""");
|
||||
javadoc("-d", base.resolve("out").toString(), "-sourcepath", src.toString(), "x");
|
||||
checkExit(Exit.OK);
|
||||
checkOutput("x/Child.html", true, """
|
||||
<dl class="notes">
|
||||
<dt>Overrides:</dt>
|
||||
<dd><code><a href="Parent.html#m()">m</a></code> in class <code>\
|
||||
<a href="Parent.html" title="class in x">Parent</a></code></dd>
|
||||
<dt>Throws:</dt>
|
||||
<dd><code>K</code> - other times</dd>
|
||||
<dd><code>V</code> - some of the times</dd>
|
||||
</dl>
|
||||
""");
|
||||
}
|
||||
|
||||
/*
|
||||
* X is unknown to Child.m as it isn't defined by Child.m and
|
||||
* type parameters declared by methods are not inherited.
|
||||
*/
|
||||
@Test
|
||||
public void testUndefinedTypeParameter(Path base) throws Exception {
|
||||
var src = base.resolve("src");
|
||||
tb.writeJavaFiles(src, """
|
||||
package x;
|
||||
|
||||
public class Parent {
|
||||
|
||||
/** @throws T sometimes */
|
||||
public <T extends RuntimeException> void m() { }
|
||||
}
|
||||
""", """
|
||||
package x;
|
||||
|
||||
public class Child extends Parent {
|
||||
|
||||
/** @throws T {@inheritDoc} */
|
||||
@Override
|
||||
public void m() { }
|
||||
}
|
||||
""");
|
||||
// turn off DocLint so that it does not interfere with diagnostics
|
||||
// by raising an error for the condition we are testing:
|
||||
//
|
||||
// Child.java:5: error: reference not found
|
||||
// /** @throws T {@inheritDoc} */
|
||||
// ^
|
||||
javadoc("-Xdoclint:none", "-d", base.resolve("out").toString(), "-sourcepath", src.toString(), "x");
|
||||
checkExit(Exit.OK);
|
||||
checkOutput(Output.OUT, true, """
|
||||
Child.java:5: warning: cannot find exception type by name
|
||||
/** @throws T {@inheritDoc} */
|
||||
^
|
||||
""");
|
||||
}
|
||||
|
||||
// A related (but separate from this test suite) test. This test is
|
||||
// introduced here because it tests for the error condition that is
|
||||
// detected by JDK-8291869, which is tested by tests in this test
|
||||
// suite.
|
||||
// TODO: consider moving this test to a more suitable test suite.
|
||||
@Test
|
||||
public void testWrongType(Path base) throws Exception {
|
||||
var src = base.resolve("src");
|
||||
tb.writeJavaFiles(src, """
|
||||
package x;
|
||||
|
||||
public class MyClass {
|
||||
|
||||
/** @throws OtherClass description */
|
||||
public void m() { }
|
||||
}
|
||||
""", """
|
||||
package x;
|
||||
|
||||
public class OtherClass { }
|
||||
""");
|
||||
// turn off DocLint so that it does not interfere with diagnostics
|
||||
// by raising an error for the condition we are testing
|
||||
javadoc("-Xdoclint:none", "-d", base.resolve("out").toString(), "-sourcepath", src.toString(), "x");
|
||||
checkExit(Exit.OK);
|
||||
checkOutput(Output.OUT, true, """
|
||||
MyClass.java:5: warning: not an exception type: \
|
||||
x.OtherClass (module <unnamed module> package x class OtherClass)
|
||||
/** @throws OtherClass description */
|
||||
^
|
||||
""");
|
||||
checkOutput("x/MyClass.html", false, """
|
||||
<dl class="notes">
|
||||
<dt>Throws:</dt>
|
||||
<dd><code><a href="OtherClass.html" title="class in x">OtherClass</a></code> - description</dd>
|
||||
</dl>
|
||||
""");
|
||||
}
|
||||
|
||||
// A related (but separate from this test suite) test. This test is
|
||||
// introduced here because it tests for the error condition that is
|
||||
// detected by JDK-8291869, which is tested by tests in this test
|
||||
// suite.
|
||||
// TODO: consider moving this test to a more suitable test suite.
|
||||
@Test
|
||||
public void testExceptionTypeNotFound(Path base) throws Exception {
|
||||
var src = base.resolve("src");
|
||||
tb.writeJavaFiles(src, """
|
||||
package x;
|
||||
|
||||
public class MyClass {
|
||||
|
||||
/** @throws un1queEn0ughS0asT0N0tBeF0und description */
|
||||
public void m() { }
|
||||
}
|
||||
""");
|
||||
// turn off DocLint so that it does not interfere with diagnostics
|
||||
// by raising an error for the condition we are testing
|
||||
javadoc("-Xdoclint:none", "-d", base.resolve("out").toString(), "-sourcepath", src.toString(), "x");
|
||||
checkExit(Exit.OK);
|
||||
checkOutput(Output.OUT, true, """
|
||||
MyClass.java:5: warning: cannot find exception type by name
|
||||
/** @throws un1queEn0ughS0asT0N0tBeF0und description */
|
||||
^
|
||||
""");
|
||||
}
|
||||
|
||||
/*
|
||||
* In Child, R is a class residing in an unnamed package, whereas
|
||||
* in Parent, R is a type variable.
|
||||
*/
|
||||
@Test
|
||||
public void testTypeAndTypeParameter(Path base) throws Exception {
|
||||
var src = base.resolve("src");
|
||||
tb.writeJavaFiles(src, """
|
||||
public class Parent {
|
||||
|
||||
/** @throws R sometimes */
|
||||
public <R extends RuntimeException> void m() { }
|
||||
}
|
||||
""", """
|
||||
public class Child extends Parent {
|
||||
|
||||
/** @throws R {@inheritDoc} */
|
||||
@Override public void m() { }
|
||||
}
|
||||
""", """
|
||||
public class R extends RuntimeException { }
|
||||
""");
|
||||
javadoc("-d", base.resolve("out").toString(), src.resolve("Parent.java").toString(),
|
||||
src.resolve("Child.java").toString(), src.resolve("R.java").toString());
|
||||
checkExit(Exit.OK);
|
||||
checkOutput(Output.OUT, true, """
|
||||
Child.java:3: warning: overridden methods do not document exception type R \
|
||||
(module <unnamed module> package <unnamed package> class R)
|
||||
/** @throws R {@inheritDoc} */
|
||||
^
|
||||
""");
|
||||
checkOutput("Child.html", false, """
|
||||
<dl class="notes">
|
||||
<dt>Overrides:</dt>
|
||||
<dd><code><a href="Parent.html#m()">m</a></code> in class <code>\
|
||||
<a href="Parent.html" title="class in Unnamed Package">Parent</a></code></dd>
|
||||
<dt>Throws:</dt>
|
||||
<dd><code><a href="R.html" title="class in Unnamed Package">R</a></code> - sometimes</dd>
|
||||
</dl>""");
|
||||
checkOutput("Child.html", false, """
|
||||
<dl class="notes">
|
||||
<dt>Overrides:</dt>
|
||||
<dd><code><a href="Parent.html#m()">m</a></code> in class <code>\
|
||||
<a href="Parent.html" title="class in Unnamed Package">Parent</a></code></dd>
|
||||
<dt>Throws:</dt>
|
||||
<dd><code>R</code> - sometimes</dd>
|
||||
</dl>""");
|
||||
}
|
||||
|
||||
/*
|
||||
* There are two different exceptions that share the same simple name:
|
||||
*
|
||||
* 1. P.MyException (a nested static class in an unnamed package)
|
||||
* 2. P.MyException (a public class in the P package)
|
||||
*
|
||||
* Although unconventional, it is not prohibited for a package name to
|
||||
* start with an upper case letter. This test disregards that
|
||||
* convention for the setup to work: the package and the
|
||||
* class should have the same FQN to be confusing.
|
||||
*
|
||||
* A permissible but equally unconventional alternative would be to
|
||||
* keep the package lower-case but give the class a lower-case name p.
|
||||
*
|
||||
* This setup works likely because of JLS 6.3. Scope of a Declaration:
|
||||
*
|
||||
* The scope of a top level class or interface (7.6) is all class
|
||||
* and interface declarations in the package in which the top
|
||||
* level class or interface is declared.
|
||||
*/
|
||||
@Test
|
||||
public void testOuterClassAndPackage(Path base) throws Exception {
|
||||
var src = base.resolve("src");
|
||||
tb.writeJavaFiles(src, """
|
||||
package P;
|
||||
|
||||
public class MyException extends RuntimeException { }
|
||||
""", """
|
||||
package pkg;
|
||||
|
||||
public class Parent {
|
||||
|
||||
/** @throws P.MyException sometimes */
|
||||
public void m() { }
|
||||
}
|
||||
""", """
|
||||
public class Child extends pkg.Parent {
|
||||
|
||||
/** @throws P.MyException {@inheritDoc} */
|
||||
@Override
|
||||
public void m() { }
|
||||
}
|
||||
""", """
|
||||
public class P {
|
||||
public static class MyException extends RuntimeException { }
|
||||
}
|
||||
""");
|
||||
setAutomaticCheckLinks(false); // otherwise the link checker reports that P.MyException is defined twice
|
||||
// (tracked by 8297085)
|
||||
javadoc("-d",
|
||||
base.resolve("out").toString(),
|
||||
src.resolve("P").resolve("MyException.java").toString(),
|
||||
src.resolve("pkg").resolve("Parent.java").toString(),
|
||||
src.resolve("Child.java").toString(),
|
||||
src.resolve("P.java").toString());
|
||||
checkExit(Exit.OK);
|
||||
checkOutput(Output.OUT, true, """
|
||||
Child.java:3: warning: overridden methods do not document exception type P.MyException \
|
||||
(module <unnamed module> package <unnamed package> class P class MyException)
|
||||
/** @throws P.MyException {@inheritDoc} */
|
||||
^
|
||||
""");
|
||||
checkOutput("Child.html", false, """
|
||||
<dl class="notes">
|
||||
<dt>Overrides:</dt>
|
||||
<dd><code><a href="pkg/Parent.html#m()">m</a></code> in class <code>\
|
||||
<a href="pkg/Parent.html" title="class in pkg">Parent</a></code></dd>
|
||||
<dt>Throws:</dt>
|
||||
<dd><code><a href="P.MyException.html" title="class in Unnamed Package">P.MyException</a></code> - sometimes</dd>
|
||||
</dl>""");
|
||||
checkOutput("Child.html", false, "P/MyException.html");
|
||||
}
|
||||
|
||||
/*
|
||||
* It's unclear how to match type parameters that aren't declared by
|
||||
* a method. For example, consider that for B to be a subtype of A,
|
||||
* it is not necessary for A and B to have the same number or
|
||||
* types of type parameters.
|
||||
*
|
||||
* For that reason, exception documentation inheritance involving
|
||||
* such parameters is currently unsupported. This test simply
|
||||
* checks that we produce helpful warnings.
|
||||
*/
|
||||
@Test
|
||||
public void testGenericTypes(Path base) throws Exception {
|
||||
var src = base.resolve("src");
|
||||
tb.writeJavaFiles(src, """
|
||||
package x;
|
||||
|
||||
public class Parent<T extends RuntimeException> {
|
||||
|
||||
/** @throws T description */
|
||||
public void m() { }
|
||||
}
|
||||
""", """
|
||||
package x;
|
||||
|
||||
public class Child1<T extends RuntimeException> extends Parent<T> {
|
||||
|
||||
/** @throws T {@inheritDoc} */
|
||||
@Override public void m() { }
|
||||
}
|
||||
""", """
|
||||
package x;
|
||||
|
||||
public class Child2<T extends IllegalArgumentException> extends Parent<T> {
|
||||
|
||||
/** @throws T {@inheritDoc} */
|
||||
@Override public void m() { }
|
||||
}
|
||||
""", """
|
||||
package x;
|
||||
|
||||
public class Child3 extends Parent<NullPointerException> {
|
||||
|
||||
/** @throws NullPointerException {@inheritDoc} */
|
||||
@Override public void m() { }
|
||||
}
|
||||
""");
|
||||
javadoc("-d", base.resolve("out").toString(), "-sourcepath", src.toString(), "x");
|
||||
checkExit(Exit.OK);
|
||||
checkOutput(Output.OUT, true, """
|
||||
Child1.java:5: warning: @inheritDoc is not supported for exception-type type parameters \
|
||||
that are not declared by a method; document such exception types directly
|
||||
/** @throws T {@inheritDoc} */
|
||||
^
|
||||
""");
|
||||
checkOutput(Output.OUT, true, """
|
||||
Child2.java:5: warning: @inheritDoc is not supported for exception-type type parameters \
|
||||
that are not declared by a method; document such exception types directly
|
||||
/** @throws T {@inheritDoc} */
|
||||
^
|
||||
""");
|
||||
checkOutput(Output.OUT, true, """
|
||||
Child3.java:5: warning: overridden methods do not document exception type java.lang.NullPointerException \
|
||||
(module java.base package java.lang class NullPointerException)
|
||||
/** @throws NullPointerException {@inheritDoc} */
|
||||
^
|
||||
""");
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8067757 6509045
|
||||
* @bug 8067757 6509045 8295277
|
||||
* @library /tools/lib ../../lib
|
||||
* @modules jdk.compiler/com.sun.tools.javac.api
|
||||
* jdk.compiler/com.sun.tools.javac.main
|
||||
@ -564,4 +564,178 @@ public class TestOneToMany extends JavadocTester {
|
||||
^
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeeperError(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();
|
||||
}
|
||||
""", """
|
||||
package x;
|
||||
|
||||
public interface I2 extends I1 {
|
||||
|
||||
/**
|
||||
* @throws MyRuntimeException '{@inheritDoc}'
|
||||
*/
|
||||
@Override
|
||||
void m();
|
||||
}
|
||||
""");
|
||||
javadoc("-d", base.resolve("out").toString(),
|
||||
"-sourcepath", src.toString(),
|
||||
"x");
|
||||
checkExit(Exit.ERROR);
|
||||
new OutputChecker(Output.OUT)
|
||||
.setExpectFound(true)
|
||||
.checkAnyOf(
|
||||
"""
|
||||
I2.java:6: error: @inheritDoc cannot be used within this tag
|
||||
* @throws MyRuntimeException '{@inheritDoc}'
|
||||
^""",
|
||||
"""
|
||||
I1.java:6: error: @inheritDoc cannot be used within this tag
|
||||
* @throws MyRuntimeException "{@inheritDoc}"
|
||||
^""");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFullExpansion(Path base) throws Exception {
|
||||
var src = base.resolve("src");
|
||||
tb.writeJavaFiles(src, """
|
||||
package x;
|
||||
|
||||
public class MyRuntimeException extends RuntimeException { }
|
||||
""", """
|
||||
package x;
|
||||
|
||||
public interface Child extends Parent {
|
||||
|
||||
/**
|
||||
* @throws MyRuntimeException child 1
|
||||
* @throws MyRuntimeException {@inheritDoc}
|
||||
*/
|
||||
@Override void m();
|
||||
}
|
||||
""", """
|
||||
package x;
|
||||
|
||||
public interface Parent extends GrandParent {
|
||||
|
||||
/**
|
||||
* @throws MyRuntimeException parent 1
|
||||
* @throws MyRuntimeException {@inheritDoc}
|
||||
*/
|
||||
@Override void m();
|
||||
}
|
||||
""", """
|
||||
package x;
|
||||
|
||||
public interface GrandParent {
|
||||
|
||||
/**
|
||||
* @throws MyRuntimeException grandparent 1
|
||||
* @throws MyRuntimeException grandparent 2
|
||||
*/
|
||||
void m();
|
||||
}
|
||||
""");
|
||||
javadoc("-d", base.resolve("out").toString(),
|
||||
"-sourcepath", src.toString(),
|
||||
"x");
|
||||
checkExit(Exit.OK);
|
||||
checkOutput("x/Child.html", true, """
|
||||
<dl class="notes">
|
||||
<dt>Specified by:</dt>
|
||||
<dd><code><a href="GrandParent.html#m()">m</a></code> in interface <code><a href="GrandParent.html" title="interface in x">GrandParent</a></code></dd>
|
||||
<dt>Specified by:</dt>
|
||||
<dd><code><a href="Parent.html#m()">m</a></code> in interface <code><a href="Parent.html" title="interface in x">Parent</a></code></dd>
|
||||
<dt>Throws:</dt>
|
||||
<dd><code><a href="MyRuntimeException.html" title="class in x">MyRuntimeException</a></code> - child 1</dd>
|
||||
<dd><code><a href="MyRuntimeException.html" title="class in x">MyRuntimeException</a></code> - parent 1</dd>
|
||||
<dd><code><a href="MyRuntimeException.html" title="class in x">MyRuntimeException</a></code> - grandparent 1</dd>
|
||||
<dd><code><a href="MyRuntimeException.html" title="class in x">MyRuntimeException</a></code> - grandparent 2</dd>
|
||||
</dl>
|
||||
</section>
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChainEmbeddedInheritDoc(Path base) throws Exception {
|
||||
var src = base.resolve("src");
|
||||
tb.writeJavaFiles(src, """
|
||||
package x;
|
||||
|
||||
public class MyRuntimeException extends RuntimeException { }
|
||||
""", """
|
||||
package x;
|
||||
|
||||
public interface Child extends Parent {
|
||||
|
||||
/**
|
||||
* @throws MyRuntimeException "{@inheritDoc}"
|
||||
*/
|
||||
@Override void m();
|
||||
}
|
||||
""", """
|
||||
package x;
|
||||
|
||||
public interface Parent extends GrandParent {
|
||||
|
||||
/**
|
||||
* @throws MyRuntimeException '{@inheritDoc}'
|
||||
*/
|
||||
@Override void m();
|
||||
}
|
||||
""", """
|
||||
package x;
|
||||
|
||||
public interface GrandParent {
|
||||
|
||||
/**
|
||||
* @throws MyRuntimeException grandparent
|
||||
*/
|
||||
void m();
|
||||
}
|
||||
""");
|
||||
javadoc("-d", base.resolve("out").toString(),
|
||||
"-sourcepath", src.toString(),
|
||||
"x");
|
||||
checkExit(Exit.OK);
|
||||
checkOutput("x/Child.html", true, """
|
||||
<dl class="notes">
|
||||
<dt>Specified by:</dt>
|
||||
<dd><code><a href="GrandParent.html#m()">m</a></code> in interface <code><a href="GrandParent.html" title="interface in x">GrandParent</a></code></dd>
|
||||
<dt>Specified by:</dt>
|
||||
<dd><code><a href="Parent.html#m()">m</a></code> in interface <code><a href="Parent.html" title="interface in x">Parent</a></code></dd>
|
||||
<dt>Throws:</dt>
|
||||
<dd><code><a href="MyRuntimeException.html" title="class in x">MyRuntimeException</a></code> - "'grandparent'"</dd>
|
||||
</dl>
|
||||
</section>
|
||||
""");
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,6 @@ public class TestStdDoclet {
|
||||
|
||||
/**
|
||||
* More dummy comments.
|
||||
* @throws DoesNotExist oops, javadoc does not see this
|
||||
* @see DoesNotExist
|
||||
*/
|
||||
void run() throws Exception {
|
||||
|
Loading…
Reference in New Issue
Block a user