8285368: Overhaul doc-comment inheritance

6376959: Algorithm for Inheriting Method Comments seems to go not as documented
6934301: Support directed inheriting of class comments with @inheritDoc

Reviewed-by: jjg, rriggs, aivanov, smarks, martin
This commit is contained in:
Pavel Rappo 2023-06-15 17:47:41 +00:00
parent 3eeb681a0d
commit 3e0bbd290c
32 changed files with 1736 additions and 134 deletions

View File

@ -1195,6 +1195,8 @@ public class TreeMap<K,V>
* {@code Set.remove}, {@code removeAll}, {@code retainAll} and
* {@code clear} operations. It does not support the
* {@code add} or {@code addAll} operations.
*
* @return {@inheritDoc SortedMap}
*/
public Set<Map.Entry<K,V>> entrySet() {
EntrySet es = entrySet;

View File

@ -1531,7 +1531,7 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
// ConcurrentMap methods
/**
* {@inheritDoc}
* {@inheritDoc ConcurrentMap}
*
* @return the previous value associated with the specified key,
* or {@code null} if there was no mapping for the key
@ -1542,9 +1542,10 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
}
/**
* {@inheritDoc}
* {@inheritDoc ConcurrentMap}
*
* @throws NullPointerException if the specified key is null
* @return {@inheritDoc ConcurrentMap}
*/
public boolean remove(Object key, Object value) {
if (key == null)
@ -1553,9 +1554,10 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
}
/**
* {@inheritDoc}
* {@inheritDoc ConcurrentMap}
*
* @throws NullPointerException if any of the arguments are null
* @return {@inheritDoc ConcurrentMap}
*/
public boolean replace(K key, V oldValue, V newValue) {
if (key == null || oldValue == null || newValue == null)
@ -1564,7 +1566,7 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
}
/**
* {@inheritDoc}
* {@inheritDoc ConcurrentMap}
*
* @return the previous value associated with the specified key,
* or {@code null} if there was no mapping for the key

View File

@ -1773,7 +1773,7 @@ public class ConcurrentSkipListMap<K,V> extends AbstractMap<K,V>
/* ------ ConcurrentMap API methods ------ */
/**
* {@inheritDoc}
* {@inheritDoc ConcurrentMap}
*
* @return the previous value associated with the specified key,
* or {@code null} if there was no mapping for the key
@ -1788,11 +1788,12 @@ public class ConcurrentSkipListMap<K,V> extends AbstractMap<K,V>
}
/**
* {@inheritDoc}
* {@inheritDoc ConcurrentMap}
*
* @throws ClassCastException if the specified key cannot be compared
* with the keys currently in the map
* @throws NullPointerException if the specified key is null
* @return {@inheritDoc ConcurrentMap}
*/
public boolean remove(Object key, Object value) {
if (key == null)
@ -1801,11 +1802,12 @@ public class ConcurrentSkipListMap<K,V> extends AbstractMap<K,V>
}
/**
* {@inheritDoc}
* {@inheritDoc ConcurrentMap}
*
* @throws ClassCastException if the specified key cannot be compared
* with the keys currently in the map
* @throws NullPointerException if any of the arguments are null
* @return {@inheritDoc ConcurrentMap}
*/
public boolean replace(K key, V oldValue, V newValue) {
if (key == null || oldValue == null || newValue == null)
@ -1824,7 +1826,7 @@ public class ConcurrentSkipListMap<K,V> extends AbstractMap<K,V>
}
/**
* {@inheritDoc}
* {@inheritDoc ConcurrentMap}
*
* @return the previous value associated with the specified key,
* or {@code null} if there was no mapping for the key

View File

@ -628,7 +628,9 @@ public class LinkedBlockingDeque<E>
}
/**
* {@inheritDoc BlockingDeque}
* @throws NullPointerException if the specified element is null
* @return {@inheritDoc BlockingDeque}
*/
public boolean offer(E e) {
return offerLast(e);
@ -665,6 +667,10 @@ public class LinkedBlockingDeque<E>
return removeFirst();
}
/**
* {@inheritDoc BlockingDeque}
* @return {@inheritDoc BlockingDeque}
*/
public E poll() {
return pollFirst();
}
@ -691,6 +697,10 @@ public class LinkedBlockingDeque<E>
return getFirst();
}
/**
* {@inheritDoc BlockingDeque}
* @return {@inheritDoc BlockingDeque}
*/
public E peek() {
return peekFirst();
}

View File

@ -240,6 +240,11 @@ public class FileCacheImageOutputStream extends ImageOutputStreamImpl {
StreamCloser.removeFromQueue(closeAction);
}
/**
* {@inheritDoc ImageOutputStream}
* @param pos {@inheritDoc ImageOutputStream}
* @throws IOException {@inheritDoc ImageOutputStream}
*/
public void flushBefore(long pos) throws IOException {
long oFlushedPos = flushedPos;
super.flushBefore(pos); // this will call checkClosed() for us

View File

@ -184,6 +184,11 @@ public class MemoryCacheImageOutputStream extends ImageOutputStreamImpl {
stream = null;
}
/**
* {@inheritDoc ImageOutputStream}
* @param pos {@inheritDoc ImageOutputStream}
* @throws IOException {@inheritDoc ImageOutputStream}
*/
public void flushBefore(long pos) throws IOException {
long oFlushedPos = flushedPos;
super.flushBefore(pos); // this will call checkClosed() for us

View File

@ -248,6 +248,10 @@ public class BasicDesktopIconUI extends DesktopIconUI {
*/
public MouseInputHandler() {}
/**
* {@inheritDoc java.awt.event.MouseListener}
* @param e {@inheritDoc java.awt.event.MouseListener}
*/
public void mouseReleased(MouseEvent e) {
_x = 0;
_y = 0;
@ -263,6 +267,10 @@ public class BasicDesktopIconUI extends DesktopIconUI {
}
/**
* {@inheritDoc java.awt.event.MouseListener}
* @param e {@inheritDoc java.awt.event.MouseListener}
*/
public void mousePressed(MouseEvent e) {
Point p = SwingUtilities.convertPoint((Component)e.getSource(),
e.getX(), e.getY(), null);

View File

@ -855,6 +855,10 @@ public class BasicInternalFrameUI extends InternalFrameUI
*/
protected BorderListener() {}
/**
* {@inheritDoc java.awt.event.MouseListener}
* @param e {@inheritDoc java.awt.event.MouseListener}
*/
public void mouseClicked(MouseEvent e) {
if(e.getClickCount() > 1 && e.getSource() == getNorthPane()) {
if(frame.isIconifiable() && frame.isIcon()) {
@ -911,10 +915,18 @@ public class BasicInternalFrameUI extends InternalFrameUI
discardRelease = true;
}
/**
* {@inheritDoc java.awt.event.MouseListener}
* @param e {@inheritDoc java.awt.event.MouseListener}
*/
public void mouseReleased(MouseEvent e) {
finishMouseReleased();
}
/**
* {@inheritDoc java.awt.event.MouseListener}
* @param e {@inheritDoc java.awt.event.MouseListener}
*/
public void mousePressed(MouseEvent e) {
Point p = SwingUtilities.convertPoint((Component)e.getSource(),
e.getX(), e.getY(), null);
@ -1300,10 +1312,18 @@ public class BasicInternalFrameUI extends InternalFrameUI
updateFrameCursor();
}
/**
* {@inheritDoc java.awt.event.MouseListener}
* @param e {@inheritDoc java.awt.event.MouseListener}
*/
public void mouseEntered(MouseEvent e) {
updateFrameCursor();
}
/**
* {@inheritDoc java.awt.event.MouseListener}
* @param e {@inheritDoc java.awt.event.MouseListener}
*/
public void mouseExited(MouseEvent e) {
updateFrameCursor();
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2023, 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
@ -30,8 +30,21 @@ package com.sun.source.doctree;
*
* <pre>
* {&#064;inheritDoc}
* {&#064;inheritDoc supertype}
* </pre>
*
* @since 1.8
*/
public interface InheritDocTree extends InlineTagTree { }
public interface InheritDocTree extends InlineTagTree {
/**
* {@return the reference to a superclass or superinterface from which
* to inherit documentation, or {@code null} if no reference was provided}
*
* @implSpec this implementation returns {@code null}.
* @since 22
*/
default ReferenceTree getSupertype() {
return null;
}
}

View File

@ -238,6 +238,17 @@ public interface DocTreeFactory {
*/
InheritDocTree newInheritDocTree();
/**
* Creates a new {@code InheritDocTree} object, to represent an {@code {@inheritDoc}} tag.
* @param supertype a superclass or superinterface reference
* @return an {@code InheritDocTree} object
* @implSpec This implementation throws {@code UnsupportedOperationException}.
* @since 22
*/
default InheritDocTree newInheritDocTree(ReferenceTree supertype) {
throw new UnsupportedOperationException();
}
/**
* Creates a new {@code LinkTree} object, to represent a {@code {@link }} tag.
* @param ref the API element being referenced

View File

@ -340,7 +340,7 @@ public class DocTreeScanner<R,P> implements DocTreeVisitor<R,P> {
/**
* {@inheritDoc}
*
* @implSpec This implementation returns {@code null}.
* @implSpec This implementation scans the children in left to right order.
*
* @param node {@inheritDoc}
* @param p {@inheritDoc}
@ -348,7 +348,7 @@ public class DocTreeScanner<R,P> implements DocTreeVisitor<R,P> {
*/
@Override
public R visitInheritDoc(InheritDocTree node, P p) {
return null;
return scan(node.getSupertype(), p);
}
/**

View File

@ -1301,13 +1301,21 @@ public class DocCommentParser {
}
},
// {@inheritDoc}
// {@inheritDoc class-name}
new TagParser(TagParser.Kind.INLINE, DCTree.Kind.INHERIT_DOC) {
@Override
public DCTree parse(int pos) throws ParseException {
DCReference ref = reference(ReferenceParser.Mode.MEMBER_DISALLOWED);
skipWhitespace();
if (ch == '}') {
nextChar();
return m.at(pos).newInheritDocTree();
// for backward compatibility, use the original legacy
// method if no ref is given
if (ref == null) {
return m.at(pos).newInheritDocTree();
} else {
return m.at(pos).newInheritDocTree(ref);
}
}
final int errorPos = bp;
inlineText(WhitespaceRetentionPolicy.REMOVE_ALL); // skip unexpected content

View File

@ -748,11 +748,22 @@ public abstract class DCTree implements DocTree {
}
public static class DCInheritDoc extends DCInlineTag<DCInheritDoc> implements InheritDocTree {
public final DCReference supertype;
public DCInheritDoc(DCReference supertype) {
this.supertype = supertype;
}
@Override @DefinedBy(Api.COMPILER_TREE)
public Kind getKind() {
return Kind.INHERIT_DOC;
}
@Override
public ReferenceTree getSupertype() {
return supertype;
}
@Override @DefinedBy(Api.COMPILER_TREE)
public <R, D> R accept(DocTreeVisitor<R, D> v, D d) {
return v.visitInheritDoc(this, d);

View File

@ -310,6 +310,10 @@ public class DocPretty implements DocTreeVisitor<Void,Void> {
try {
print('{');
printTagName(node);
if (node.getSupertype() != null) {
print(" ");
print(node.getSupertype());
}
print('}');
} catch (IOException e) {
throw new UncheckedIOException(e);

View File

@ -311,7 +311,12 @@ public class DocTreeMaker implements DocTreeFactory {
@Override @DefinedBy(Api.COMPILER_TREE)
public DCInheritDoc newInheritDocTree() {
DCInheritDoc tree = new DCInheritDoc();
return newInheritDocTree(null);
}
@Override @DefinedBy(Api.COMPILER_TREE)
public DCInheritDoc newInheritDocTree(ReferenceTree supertype) {
DCInheritDoc tree = new DCInheritDoc((DCReference) supertype);
tree.pos = pos;
return tree;
}

View File

@ -119,6 +119,7 @@ doclet.Version=Version:
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.inheritDocBadSupertype=cannot find the overridden method
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 \

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2001, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2001, 2023, 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
@ -27,13 +27,15 @@ 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;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import com.sun.source.doctree.DocTree;
import com.sun.source.doctree.InheritDocTree;
import com.sun.source.util.DocTreePath;
import jdk.javadoc.doclet.Taglet.Location;
import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration;
import jdk.javadoc.internal.doclets.toolkit.Content;
@ -42,6 +44,7 @@ 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;
import jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberTable;
/**
* A taglet that represents the {@code {@inheritDoc}} tag.
@ -70,25 +73,68 @@ public class InheritDocTaglet extends BaseTaglet {
*/
private Content retrieveInheritedDocumentation(TagletWriter writer,
ExecutableElement method,
DocTree inheritDoc,
InheritDocTree inheritDoc,
boolean isFirstSentence) {
Content replacement = writer.getOutputInstance();
BaseConfiguration configuration = writer.configuration();
Messages messages = configuration.getMessages();
Utils utils = configuration.utils;
CommentHelper ch = utils.getCommentHelper(method);
var path = ch.getDocTreePath(inheritDoc).getParentPath();
DocTreePath inheritDocPath = ch.getDocTreePath(inheritDoc);
var path = inheritDocPath.getParentPath();
DocTree holderTag = path.getLeaf();
ExecutableElement src = null;
// 1. Does @inheritDoc specify a type?
if (inheritDoc.getSupertype() != null) {
// 2. Can we find that type?
var supertype = (TypeElement) ch.getReferencedElement(inheritDoc.getSupertype());
if (supertype == null) {
messages.error(inheritDocPath, "doclet.inheritDocBadSupertype");
return replacement;
}
// 3. Does that type have a method that this method overrides?
// Skip a direct check that the type that declares this method is a subtype
// of the type that @inheritDoc specifies. Do the "overrides" check for
// individual methods. Not only will such a check find the overridden
// method, but it will also filter out self-referring inheritors
// (S is the same type as the type that contains {@inheritDoc S})
// due to irreflexivity of the "overrides" relationship.
//
// This way we do more work in erroneous case, but less in the typical
// case. We don't optimize for the former.
VisibleMemberTable visibleMemberTable = configuration.getVisibleMemberTable(supertype);
List<Element> methods = visibleMemberTable.getAllVisibleMembers(VisibleMemberTable.Kind.METHODS);
for (Element e : methods) {
ExecutableElement m = (ExecutableElement) e;
if (utils.elementUtils.overrides(method, m, (TypeElement) method.getEnclosingElement())) {
assert !method.equals(m) : Utils.diagnosticDescriptionOf(method);
src = m;
break;
}
}
if (src == null) {
// "self-inheritors" and supertypes that do not contain a method
// that this method overrides
messages.error(inheritDocPath, "doclet.inheritDocBadSupertype");
return replacement;
}
}
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);
Result<Documentation> d;
if (src == null) {
d = docFinder.find(method, m -> extractMainDescription(m, isFirstSentence, utils));
} else {
d = docFinder.search(src, m -> extractMainDescription(m, isFirstSentence, utils));
}
} catch (DocFinder.NoOverriddenMethodsFound e) {
if (d instanceof Result.Conclude<Documentation> doc) {
replacement = writer.commentTagsToOutput(doc.value().method, null,
doc.value().mainDescription, isFirstSentence);
}
} catch (DocFinder.NoOverriddenMethodFound e) {
String signature = utils.getSimpleName(method)
+ utils.flatSignature(method, writer.getCurrentPageElement());
messages.warning(method, "doclet.noInheritedDoc", signature);
@ -97,13 +143,16 @@ public class InheritDocTaglet extends BaseTaglet {
}
Taglet taglet = configuration.tagletManager.getTaglet(ch.getTagName(holderTag));
if (taglet != null && !(taglet instanceof InheritableTaglet)) {
// taglet is null if holderTag is unknown, which it shouldn't be since we reached here
assert taglet != null;
if (!(taglet instanceof InheritableTaglet inheritableTaglet)) {
// This tag does not support inheritance.
messages.warning(path, "doclet.inheritDocWithinInappropriateTag");
return replacement;
}
InheritableTaglet.Output inheritedDoc = ((InheritableTaglet) taglet).inherit(method, holderTag, isFirstSentence, configuration);
InheritableTaglet.Output inheritedDoc = inheritableTaglet.inherit(method, src, holderTag, isFirstSentence, configuration);
if (inheritedDoc.isValidInheritDocTag()) {
if (!inheritedDoc.inlineTags().isEmpty()) {
replacement = writer.commentTagsToOutput(inheritedDoc.holder(), inheritedDoc.holderTag(),
@ -119,13 +168,13 @@ public class InheritDocTaglet extends BaseTaglet {
private record Documentation(List<? extends DocTree> mainDescription, ExecutableElement method) { }
private static Optional<Documentation> extractMainDescription(ExecutableElement m,
private static Result<Documentation> extractMainDescription(ExecutableElement m,
boolean extractFirstSentenceOnly,
Utils utils) {
List<? extends DocTree> docTrees = extractFirstSentenceOnly
var mainDescriptionTrees = extractFirstSentenceOnly
? utils.getFirstSentenceTrees(m)
: utils.getFullBody(m);
return docTrees.isEmpty() ? Optional.empty() : Optional.of(new Documentation(docTrees, m));
return mainDescriptionTrees.isEmpty() ? Result.CONTINUE() : Result.CONCLUDE(new Documentation(mainDescriptionTrees, m));
}
@Override
@ -133,6 +182,6 @@ public class InheritDocTaglet extends BaseTaglet {
if (e.getKind() != ElementKind.METHOD) {
return tagletWriter.getOutputInstance();
}
return retrieveInheritedDocumentation(tagletWriter, (ExecutableElement) e, inheritDoc, tagletWriter.isFirstSentence);
return retrieveInheritedDocumentation(tagletWriter, (ExecutableElement) e, (InheritDocTree) inheritDoc, tagletWriter.isFirstSentence);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2023, 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
@ -40,8 +40,8 @@ import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration;
public interface InheritableTaglet extends Taglet {
/*
* Called by InheritDocTaglet on an inheritable taglet to expand {@inheritDoc}
* found inside a tag corresponding to that taglet.
* Called by InheritDocTaglet on an inheritable taglet to expand {@inheritDoc S}
* found inside a tag corresponding to that taglet in a method (dst).
*
* When inheriting failed some assumption, or caused an error, the taglet
* can return either of:
@ -52,7 +52,7 @@ public interface InheritableTaglet extends Taglet {
* In the future, this could be reworked using some other mechanism,
* such as throwing an exception.
*/
Output inherit(Element owner, DocTree tag, boolean isFirstSentence, BaseConfiguration configuration);
Output inherit(Element dst, Element src, DocTree tag, boolean isFirstSentence, BaseConfiguration configuration);
record Output(DocTree holderTag,
Element holder,

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2001, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2001, 2023, 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
@ -64,10 +64,10 @@ public class ParamTaglet extends BaseTaglet implements InheritableTaglet {
}
@Override
public Output inherit(Element owner, DocTree tag, boolean isFirstSentence, BaseConfiguration configuration) {
assert owner.getKind() == ElementKind.METHOD;
public Output inherit(Element dst, Element src, DocTree tag, boolean isFirstSentence, BaseConfiguration configuration) {
assert dst.getKind() == ElementKind.METHOD;
assert tag.getKind() == DocTree.Kind.PARAM;
var method = (ExecutableElement) owner;
var method = (ExecutableElement) dst;
var param = (ParamTree) tag;
// find the position of an owner parameter described by the given tag
List<? extends Element> parameterElements;
@ -77,7 +77,7 @@ public class ParamTaglet extends BaseTaglet implements InheritableTaglet {
parameterElements = method.getParameters();
}
Map<String, Integer> stringIntegerMap = mapNameToPosition(configuration.utils, parameterElements);
CommentHelper ch = configuration.utils.getCommentHelper(owner);
CommentHelper ch = configuration.utils.getCommentHelper(dst);
Integer position = stringIntegerMap.get(ch.getParameterName(param));
if (position == null) {
return new Output(null, null, List.of(), true);
@ -85,12 +85,20 @@ public class ParamTaglet extends BaseTaglet implements InheritableTaglet {
// 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();
Optional<Documentation> r;
if (src != null){
r = docFinder.search((ExecutableElement) src,
m -> Result.fromOptional(extract(configuration.utils, m, position, param.isTypeParameter())))
.toOptional();
} else {
r = docFinder.find((ExecutableElement) dst,
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) {
} catch (DocFinder.NoOverriddenMethodFound e) {
return new Output(null, null, List.of(), false);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2001, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2001, 2023, 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
@ -60,13 +60,18 @@ public class ReturnTaglet extends BaseTaglet implements InheritableTaglet {
}
@Override
public Output inherit(Element owner, DocTree tag, boolean isFirstSentence, BaseConfiguration configuration) {
public Output inherit(Element dst, Element src, 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();
Optional<Documentation> r;
if (src == null) {
r = docFinder.find((ExecutableElement) dst, m -> Result.fromOptional(extract(configuration.utils, m))).toOptional();
} else {
r = docFinder.search((ExecutableElement) src, 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) {
} catch (DocFinder.NoOverriddenMethodFound e) {
return new Output(null, null, List.of(), false);
}
}

View File

@ -51,8 +51,8 @@ public class SeeTaglet extends BaseTaglet implements InheritableTaglet {
}
@Override
public Output inherit(Element owner, DocTree tag, boolean isFirstSentence, BaseConfiguration configuration) {
CommentHelper ch = configuration.utils.getCommentHelper(owner);
public Output inherit(Element dst, Element src, DocTree tag, boolean isFirstSentence, BaseConfiguration configuration) {
CommentHelper ch = configuration.utils.getCommentHelper(dst);
var path = ch.getDocTreePath(tag);
configuration.getMessages().warning(path, "doclet.inheritDocWithinInappropriateTag");
return new Output(null, null, List.of(), true /* true, otherwise there will be an exception up the stack */);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2001, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2001, 2023, 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
@ -163,16 +163,22 @@ public class SimpleTaglet extends BaseTaglet implements InheritableTaglet {
}
@Override
public Output inherit(Element owner, DocTree tag, boolean isFirstSentence, BaseConfiguration configuration) {
assert owner.getKind() == ElementKind.METHOD;
public Output inherit(Element dst, Element src, DocTree tag, boolean isFirstSentence, BaseConfiguration configuration) {
assert dst.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();
Optional<Documentation> r;
if (src == null) {
r = docFinder.find((ExecutableElement) dst,
m -> Result.fromOptional(extractFirst(m, configuration.utils))).toOptional();
} else {
r = docFinder.search((ExecutableElement) src,
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) {
} catch (DocFinder.NoOverriddenMethodFound e) {
return new Output(null, null, List.of(), false);
}
}

View File

@ -50,8 +50,8 @@ public class SpecTaglet extends BaseTaglet implements InheritableTaglet {
}
@Override
public Output inherit(Element owner, DocTree tag, boolean isFirstSentence, BaseConfiguration configuration) {
CommentHelper ch = configuration.utils.getCommentHelper(owner);
public Output inherit(Element dst, Element src, DocTree tag, boolean isFirstSentence, BaseConfiguration configuration) {
CommentHelper ch = configuration.utils.getCommentHelper(dst);
var path = ch.getDocTreePath(tag);
configuration.getMessages().warning(path, "doclet.inheritDocWithinInappropriateTag");
return new Output(null, null, List.of(), true /* true, otherwise there will be an exception up the stack */);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2001, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2001, 2023, 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
@ -50,8 +50,10 @@ import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import com.sun.source.doctree.DocTree;
import com.sun.source.doctree.InheritDocTree;
import com.sun.source.doctree.ThrowsTree;
import com.sun.source.util.DocTreePath;
import jdk.javadoc.doclet.Taglet.Location;
import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder;
import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration;
@ -59,6 +61,7 @@ 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;
import jdk.javadoc.internal.doclets.toolkit.util.VisibleMemberTable;
/**
* A taglet that processes {@link ThrowsTree}, which represents {@code @throws}
@ -165,12 +168,12 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet {
private final Utils utils;
@Override
public Output inherit(Element owner, DocTree tag, boolean isFirstSentence, BaseConfiguration configuration) {
public Output inherit(Element dst, Element src, 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);
throw newAssertionError(dst, tag, isFirstSentence);
}
@Override
@ -193,6 +196,9 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet {
} else if (f instanceof Failure.UnsupportedTypeParameter e) {
var path = ch.getDocTreePath(e.tag().getExceptionName());
messages.warning(path, "doclet.throwsInheritDocUnsupported");
} else if (f instanceof Failure.NoOverrideFound e) {
var path = ch.getDocTreePath(e.inheritDoc);
messages.error(path, "doclet.inheritDocBadSupertype");
} else if (f instanceof Failure.Undocumented e) {
messages.warning(ch.getDocTreePath(e.tag()), "doclet.inheritDocNoDoc", diagnosticDescriptionOf(e.exceptionElement));
} else {
@ -200,7 +206,7 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet {
// readability and exhaustiveness when it's available
throw newAssertionError(f);
}
} catch (DocFinder.NoOverriddenMethodsFound e) {
} catch (DocFinder.NoOverriddenMethodFound 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)
@ -217,7 +223,8 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet {
Failure.Invalid,
Failure.Undocumented,
Failure.UnsupportedTypeParameter,
DocFinder.NoOverriddenMethodsFound
Failure.NoOverrideFound,
DocFinder.NoOverriddenMethodFound
{
ElementKind kind = holder.getKind();
if (kind != ElementKind.METHOD && kind != ElementKind.CONSTRUCTOR) {
@ -253,8 +260,8 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet {
Element exceptionElement = utils.typeUtils.asElement(exceptionType);
Map<ThrowsTree, ExecutableElement> r;
try {
r = expandShallowly(exceptionElement, executable);
} catch (Failure | DocFinder.NoOverriddenMethodsFound e) {
r = expandShallowly(exceptionElement, executable, Optional.empty());
} catch (Failure | DocFinder.NoOverriddenMethodFound 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
@ -304,7 +311,8 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet {
Failure.Invalid,
Failure.Undocumented,
Failure.UnsupportedTypeParameter,
DocFinder.NoOverriddenMethodsFound
Failure.NoOverrideFound,
DocFinder.NoOverriddenMethodFound
{
outputAnExceptionTagDeeply(exceptionSection, originalExceptionElement, tag, holder, true, alreadyDocumentedExceptions, typeSubstitutions, writer);
}
@ -322,8 +330,8 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet {
Failure.Invalid,
Failure.Undocumented,
Failure.UnsupportedTypeParameter,
DocFinder.NoOverriddenMethodsFound
{
Failure.NoOverrideFound,
DocFinder.NoOverriddenMethodFound {
var originalExceptionType = originalExceptionElement.asType();
var exceptionType = typeSubstitutions.getOrDefault(originalExceptionType, originalExceptionType); // FIXME: ugh..........
alreadyDocumentedExceptions.add(exceptionType);
@ -368,9 +376,34 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet {
Content beforeInheritDoc = writer.commentTagsToOutput(holder, description.subList(0, i));
exceptionSection.continueEntry(beforeInheritDoc);
}
var inheritDoc = (InheritDocTree) tag.getDescription().get(i);
var ch = utils.getCommentHelper(holder);
// Sadly, almost exact duplicating code from InheritDocTaglet:
ExecutableElement src = null;
if (inheritDoc.getSupertype() != null) {
var supertype = (TypeElement) ch.getReferencedElement(inheritDoc.getSupertype());
if (supertype == null) {
throw new Failure.NoOverrideFound(tag, holder, inheritDoc);
}
VisibleMemberTable visibleMemberTable = configuration.getVisibleMemberTable(supertype);
List<Element> methods = visibleMemberTable.getAllVisibleMembers(VisibleMemberTable.Kind.METHODS);
for (Element e : methods) {
ExecutableElement m = (ExecutableElement) e;
if (utils.elementUtils.overrides(holder, m, (TypeElement) holder.getEnclosingElement())) {
assert !holder.equals(m) : Utils.diagnosticDescriptionOf(holder);
src = m;
break;
}
}
if (src == null) {
throw new Failure.NoOverrideFound(tag, holder, inheritDoc);
}
}
Map<ThrowsTree, ExecutableElement> tags;
try {
tags = expandShallowly(originalExceptionElement, holder);
tags = expandShallowly(originalExceptionElement, holder, Optional.ofNullable(src));
} catch (Failure.UnsupportedTypeParameter e) {
// repack to fill in missing tag information
throw new Failure.UnsupportedTypeParameter(e.element, tag, holder);
@ -516,6 +549,16 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet {
@Override ThrowsTree tag() { return (ThrowsTree) super.tag(); }
}
static final class NoOverrideFound extends Failure {
private final InheritDocTree inheritDoc;
public NoOverrideFound(DocTree tag, ExecutableElement holder, InheritDocTree inheritDoc) {
super(tag, holder);
this.inheritDoc = inheritDoc;
}
}
}
/*
@ -526,12 +569,13 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet {
* are non-null.
*/
private Map<ThrowsTree, ExecutableElement> expandShallowly(Element exceptionType,
ExecutableElement holder)
ExecutableElement holder,
Optional<ExecutableElement> src)
throws Failure.ExceptionTypeNotFound,
Failure.NotExceptionType,
Failure.Invalid,
Failure.UnsupportedTypeParameter,
DocFinder.NoOverriddenMethodsFound
DocFinder.NoOverriddenMethodFound
{
ElementKind kind = exceptionType.getKind();
DocFinder.Criterion<Map<ThrowsTree, ExecutableElement>, Failure> criterion;
@ -562,7 +606,11 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet {
}
Result<Map<ThrowsTree, ExecutableElement>> result;
try {
result = utils.docFinder().trySearch(holder, criterion);
if (src.isPresent()) {
result = utils.docFinder().search(src.get(), criterion);
} else {
result = utils.docFinder().find(holder, criterion);
}
} catch (Failure.NotExceptionType
| Failure.ExceptionTypeNotFound
| Failure.UnsupportedTypeParameter x) {

View File

@ -33,6 +33,7 @@ import com.sun.source.doctree.DocCommentTree;
import com.sun.source.doctree.DocTree;
import com.sun.source.doctree.EscapeTree;
import com.sun.source.doctree.IdentifierTree;
import com.sun.source.doctree.InheritDocTree;
import com.sun.source.doctree.InlineTagTree;
import com.sun.source.doctree.LinkTree;
import com.sun.source.doctree.LiteralTree;
@ -317,6 +318,11 @@ public class CommentHelper {
return null;
}
@Override
public R visitInheritDoc(InheritDocTree node, Void p) {
return visit(node.getSupertype(), p);
}
@Override
public R visitLink(LinkTree node, Void p) {
return visit(node.getReference(), null);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2023, 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
@ -25,13 +25,10 @@
package jdk.javadoc.internal.doclets.toolkit.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.ExecutableElement;
public class DocFinder {
@ -47,45 +44,34 @@ public class DocFinder {
Result<T> apply(ExecutableElement method) throws X;
}
private final Function<ExecutableElement, ExecutableElement> overriddenMethodLookup;
private final BiFunction<ExecutableElement, ExecutableElement, Iterable<ExecutableElement>> implementedMethodsLookup;
private final Function<ExecutableElement, Iterable<? extends ExecutableElement>> overriddenMethodLookup;
DocFinder(Function<ExecutableElement, ExecutableElement> overriddenMethodLookup,
BiFunction<ExecutableElement, ExecutableElement, Iterable<ExecutableElement>> implementedMethodsLookup) {
DocFinder(Function<ExecutableElement, Iterable<? extends ExecutableElement>> overriddenMethodLookup) {
this.overriddenMethodLookup = overriddenMethodLookup;
this.implementedMethodsLookup = implementedMethodsLookup;
}
@SuppressWarnings("serial")
public static final class NoOverriddenMethodsFound extends Exception {
public static final class NoOverriddenMethodFound extends Exception {
// only DocFinder should instantiate this exception
private NoOverriddenMethodsFound() { }
private NoOverriddenMethodFound() { }
}
public <T, X extends Throwable> Result<T> search(ExecutableElement method,
Criterion<T, X> criterion)
throws X
{
return search(method, true, criterion);
}
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) {
return search0(method, true, false, criterion);
} catch (NoOverriddenMethodFound e) {
// should not happen because the exception flag is unset
throw new AssertionError(e);
}
}
public <T, X extends Throwable> Result<T> trySearch(ExecutableElement method,
Criterion<T, X> criterion)
throws NoOverriddenMethodsFound, X
public <T, X extends Throwable> Result<T> find(ExecutableElement method,
Criterion<T, X> criterion)
throws NoOverriddenMethodFound, X
{
return search0(method, false, true, criterion);
}
@ -114,13 +100,13 @@ public class DocFinder {
boolean includeMethodInSearch,
boolean throwExceptionIfDoesNotOverride,
Criterion<T, X> criterion)
throws NoOverriddenMethodsFound, X
throws NoOverriddenMethodFound, 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);
Iterator<? extends ExecutableElement> methods = overriddenMethodLookup.apply(method).iterator();
if (throwExceptionIfDoesNotOverride && !methods.hasNext() ) {
throw new NoOverriddenMethodsFound();
throw new NoOverriddenMethodFound();
}
Result<T> r = includeMethodInSearch ? criterion.apply(method) : Result.CONTINUE();
if (!(r instanceof Result.Continue<T>)) {
@ -128,7 +114,7 @@ public class DocFinder {
}
while (methods.hasNext()) {
ExecutableElement m = methods.next();
r = search0(m, true, false /* don't check for overrides */, criterion);
r = criterion.apply(m);
if (r instanceof Result.Conclude<T>) {
return r;
}
@ -136,19 +122,6 @@ public class DocFinder {
return r;
}
// 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();
}
private static final Result<?> SKIP = new Skipped<>();
private static final Result<?> CONTINUE = new Continued<>();
@ -208,7 +181,7 @@ public class DocFinder {
/*
* Translates the given Optional into a binary decision whether to
* conclude the search or continue it.
* conclude the search or to continue it.
*
* Convenience method. Use in Criterion that can easily provide
* suitable Optional. Don't use if Criterion needs to skip.

View File

@ -34,7 +34,9 @@ import java.text.ParseException;
import java.text.RuleBasedCollator;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.EnumSet;
import java.util.HashMap;
@ -46,12 +48,14 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.lang.model.AnnotatedConstruct;
import javax.lang.model.SourceVersion;
@ -64,6 +68,7 @@ import javax.lang.model.element.Modifier;
import javax.lang.model.element.ModuleElement;
import javax.lang.model.element.ModuleElement.RequiresDirective;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.QualifiedNameable;
import javax.lang.model.element.RecordComponentElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
@ -118,7 +123,6 @@ import jdk.javadoc.internal.doclets.toolkit.CommentUtils.DocCommentInfo;
import jdk.javadoc.internal.doclets.toolkit.Resources;
import jdk.javadoc.internal.doclets.toolkit.taglets.BaseTaglet;
import jdk.javadoc.internal.doclets.toolkit.taglets.Taglet;
import jdk.javadoc.internal.tool.DocEnvImpl;
import static javax.lang.model.element.ElementKind.*;
import static javax.lang.model.type.TypeKind.*;
@ -138,12 +142,14 @@ public class Utils {
public final Comparators comparators;
private final JavaScriptScanner javaScriptScanner;
private final DocFinder docFinder = newDocFinder();
private final TypeElement JAVA_LANG_OBJECT;
public Utils(BaseConfiguration c) {
configuration = c;
options = configuration.getOptions();
resources = configuration.getDocResources();
elementUtils = c.docEnv.getElementUtils();
JAVA_LANG_OBJECT = elementUtils.getTypeElement("java.lang.Object");
typeUtils = c.docEnv.getTypeUtils();
docTrees = c.docEnv.getDocTrees();
javaScriptScanner = c.isAllowScriptInComments() ? null : new JavaScriptScanner();
@ -2748,14 +2754,125 @@ public class Utils {
}
private DocFinder newDocFinder() {
return new DocFinder(e -> {
var i = overriddenMethod(e);
return i == null ? null : i.overriddenMethod();
}, this::implementedMethods);
return new DocFinder(this::overriddenMethods);
}
private Iterable<ExecutableElement> implementedMethods(ExecutableElement originalMethod, ExecutableElement m) {
var type = configuration.utils.getEnclosingTypeElement(m);
return configuration.getVisibleMemberTable(type).getImplementedMethods(originalMethod);
/*
* Returns an iterable over all unique methods overridden by the given
* method from its enclosing type element. The methods encounter order
* is that of described in the "Automatic Supertype Search" section of
* the Documentation Comment Specification for the Standard Doclet.
*/
private Iterable<? extends ExecutableElement> overriddenMethods(ExecutableElement method) {
return () -> new Overrides(method);
}
private class Overrides implements Iterator<ExecutableElement> {
// prefer java.util.Deque to java.util.Stack API for stacks
final Deque<TypeElement> searchStack = new ArrayDeque<>();
final Set<TypeElement> visited = new HashSet<>();
final ExecutableElement overrider;
ExecutableElement next;
public Overrides(ExecutableElement method) {
if (method.getKind() != ElementKind.METHOD) {
throw new IllegalArgumentException(diagnosticDescriptionOf(method));
}
overrider = method;
// java.lang.Object is to be searched for overrides last
searchStack.push(JAVA_LANG_OBJECT);
searchStack.push((TypeElement) method.getEnclosingElement());
}
@Override
public boolean hasNext() {
if (next != null) {
return true;
}
updateNext();
return next != null;
}
@Override
public ExecutableElement next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
var n = next;
updateNext();
return n;
}
private void updateNext() {
while (!searchStack.isEmpty()) {
// replace the top class or interface with its supertypes
var t = searchStack.pop();
// <TODO refactor once java.util.List.reversed() from
// SequencedCollection is available>
var filteredSupertypes = typeUtils.directSupertypes(t.asType()).stream()
.map(t_ -> (TypeElement) ((DeclaredType) t_).asElement())
// filter out java.lang.Object using the fact that at
// most one class type comes first in the stream of
// direct supertypes
.dropWhile(JAVA_LANG_OBJECT::equals)
.filter(visited::add) // idempotent side effect
.collect(Collectors.toCollection(ArrayList::new));
// push supertypes in reverse order, so that they are popped
// back in the initial order
Collections.reverse(filteredSupertypes);
filteredSupertypes.forEach(searchStack::push);
// </TODO>
// consider only the declared methods for consistency with
// the existing facilities, such as Utils.overriddenMethod
// and VisibleMemberTable.getImplementedMethods
TypeElement peek = searchStack.peek();
if (peek == null) {
next = null; // end-of-hierarchy
break;
}
if (isPlainInterface(peek) && !isPublic(peek) && !isLinkable(peek)) {
// we don't consider such interfaces directly, but may consider
// their supertypes (subject to this check for each of them)
continue;
}
List<Element> declaredMethods = configuration.getVisibleMemberTable(peek)
.getMembers(VisibleMemberTable.Kind.METHODS);
var overridden = declaredMethods.stream()
.filter(candidate -> elementUtils.overrides(overrider, (ExecutableElement) candidate,
(TypeElement) overrider.getEnclosingElement()))
.findFirst();
// assume a method may override at most one method in any
// given class or interface; hence findFirst
assert declaredMethods.stream()
.filter(candidate -> elementUtils.overrides(overrider, (ExecutableElement) candidate,
(TypeElement) overrider.getEnclosingElement()))
.count() <= 1 : diagnosticDescriptionOf(overrider);
if (overridden.isPresent()) {
next = (ExecutableElement) overridden.get();
break;
}
// TODO we're currently ignoring simpleOverride
// (it's unavailable in this data structure)
}
// if the stack is empty, there's no unconsumed override:
// if that ever fails, an iterator's client will be stuck
// in an infinite loop
assert !searchStack.isEmpty() || next == null
: diagnosticDescriptionOf(overrider);
}
}
public static String diagnosticDescriptionOf(Element e) {
if (e == null) // shouldn't NPE if passed null
return "null";
return e + ", " + (e instanceof QualifiedNameable q ? q.getQualifiedName() : e.getSimpleName())
+ ", " + e.getKind() + ", " + Objects.toIdentityString(e);
}
}

View File

@ -0,0 +1,665 @@
/*
* Copyright (c) 2022, 2023, 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 6934301
* @library /tools/lib ../../lib
* @modules jdk.javadoc/jdk.javadoc.internal.tool
* @build toolbox.ToolBox javadoc.tester.*
* @run main TestDirectedInheritance
*/
import java.nio.file.Path;
import javadoc.tester.JavadocTester;
import toolbox.ToolBox;
public class TestDirectedInheritance extends JavadocTester {
public static void main(String... args) throws Exception {
new TestDirectedInheritance().runTests(m -> new Object[]{Path.of(m.getName())});
}
private final ToolBox tb = new ToolBox();
/*
* Javadoc won't crash if an unknown tag uses {@inheritDoc}.
*/
@Test
public void testUnknownTag(Path base) throws Exception {
Path src = base.resolve("src");
tb.writeJavaFiles(src, """
package x;
public interface I1 {
/** @foo bar */
void m();
}
""", """
package x;
public interface E1 extends I1 {
/** @foo {@inheritDoc} */
void m();
}
""", """
package x;
public interface E2 extends I1 {
/** @foo {@inheritDoc I1} */
void m();
}
""");
// DocLint should neither prevent nor cause a crash. Explicitly check that
// there's no crash with DocLint on and off, but don't check that the exit
// code is OK, it likely isn't (after all, there's an unknown tag).
setAutomaticCheckNoStacktrace(true);
{ // DocLint is explicit
int i = 0;
for (var check : new String[]{":all", ":none", ""}) {
var outputDir = "out-DocLint-" + i++; // use separate output directories
javadoc("-Xdoclint" + check,
"-d", base.resolve(outputDir).toString(),
"--source-path", src.toString(),
"x");
}
}
// DocLint is default
javadoc("-d", base.resolve("out").toString(),
"--source-path", src.toString(),
"x");
}
/*
* An interface method inherits documentation from that interface's rightmost
* superinterface in the `extends` clause.
*/
@Test
public void testInterfaceInheritsFromSuperinterface(Path base) throws Exception {
Path src = base.resolve("src");
tb.writeJavaFiles(src, """
package x;
public interface I1 {
/**
* I1: main description
*
* @param <A> I1: first type parameter
* @param <B> I1: second type parameter
*
* @param bObj I1: parameter
* @return I1: return
*
* @throws B I1: first description of an exception
* @throws B I1: second description of an exception
*/
<A, B extends RuntimeException> int m(A bObj);
}
""", """
package x;
public interface I2 {
/**
* I2: main description
*
* @param <C> I2: first type parameter
* @param <D> I2: second type parameter
*
* @param cObj I2: parameter
* @return I2: return
*
* @throws D I2: first description of an exception
* @throws D I2: second description of an exception
*/
<C, D extends RuntimeException> int m(C cObj);
}
""", """
package x;
public interface E1 extends I1, I2 {
/**
* {@inheritDoc I2}
*
* @param <E> {@inheritDoc I2}
* @param <F> {@inheritDoc I2}
*
* @param eObj {@inheritDoc I2}
* @return {@inheritDoc I2}
*
* @throws F {@inheritDoc I2}
*/
<E, F extends RuntimeException> int m(E eObj);
}
""");
javadoc("-d", base.resolve("out").toString(),
"--source-path", src.toString(),
"x");
checkExit(Exit.OK);
new OutputChecker("x/E1.html").check("""
<div class="block">I2: main description</div>
""", """
<dt>Type Parameters:</dt>
<dd><code>E</code> - I2: first type parameter</dd>
<dd><code>F</code> - I2: second type parameter</dd>
<dt>Parameters:</dt>
<dd><code>eObj</code> - I2: parameter</dd>
<dt>Returns:</dt>
<dd>I2: return</dd>
<dt>Throws:</dt>
<dd><code>F</code> - I2: first description of an exception</dd>
<dd><code>F</code> - I2: second description of an exception</dd>
</dl>""");
new OutputChecker(Output.OUT).setExpectFound(false)
.check("warning: not a direct supertype"); // no unexpected warnings
}
/*
* An interface method both provides and inherits the main description and
* the exception documentation from all its superinterfaces.
*
* Note: the same does not work for @param and @return as these are one-to-one.
*/
@Test
public void testInterfaceInheritsFromAllSuperinterfaces(Path base) throws Exception {
Path src = base.resolve("src");
tb.writeJavaFiles(src, """
package x;
public interface I1 {
/**
* I1: main description
*
* @throws B I1: first description of an exception
* @throws B I1: second description of an exception
*/
<A, B extends RuntimeException> int m(A bObj);
}
""", """
package x;
public interface I2 {
/**
* I2: main description
*
* @throws D I2: first description of an exception
* @throws D I2: second description of an exception
*/
<C, D extends RuntimeException> int m(C cObj);
}
""", """
package x;
public interface E1 extends I1, I2 {
/**
* E1: main description
* {@inheritDoc I2}
* {@inheritDoc I1}
*
* @throws F E1: description of an exception
* @throws F {@inheritDoc I2}
* @throws F {@inheritDoc I1}
*/
<E, F extends RuntimeException> int m(E eObj);
}
""");
javadoc("-d", base.resolve("out").toString(),
"--source-path", src.toString(),
"x");
checkExit(Exit.OK);
new OutputChecker("x/E1.html").check("""
<div class="block">E1: main description
I2: main description
I1: main description</div>""", """
<dt>Throws:</dt>
<dd><code>F</code> - E1: description of an exception</dd>
<dd><code>F</code> - I2: first description of an exception</dd>
<dd><code>F</code> - I2: second description of an exception</dd>
<dd><code>F</code> - I1: first description of an exception</dd>
<dd><code>F</code> - I1: second description of an exception</dd>
</dl>""");
new OutputChecker(Output.OUT).setExpectFound(false)
.check("warning: not a direct supertype"); // no unexpected warnings
}
/*
* C1.m directedly inherits documentation from B1, which inherits A.m
* along with its documentation.
*
* C2.m directedly inherits documentation from B2, whose m overrides A.m
* and implicitly inherits its documentation.
*/
@Test
public void testRecursiveInheritance1(Path base) throws Exception {
Path src = base.resolve("src");
tb.writeJavaFiles(src, """
package x;
public class A {
/** A.m() */
public void m() { }
}
""", """
package x;
public class B1 extends A { }
""", """
package x;
public class C1 extends B1 {
/** {@inheritDoc B1} */
@Override public void m() { }
}
""", """
package x;
public class B2 extends A {
@Override public void m() { }
}
""", """
package x;
public class C2 extends B2 {
/** {@inheritDoc B2} */
@Override public void m() { }
}
""");
javadoc("-d", base.resolve("out").toString(),
"--source-path", src.toString(),
"x");
checkExit(Exit.OK);
var m = """
<section class="detail" id="m()">
<h3>m</h3>
<div class="member-signature"><span class="modifiers">\
public</span>&nbsp;<span class="return-type">void</span>\
&nbsp;<span class="element-name">m</span>()</div>
<div class="block">A.m()</div>""";
new OutputChecker("x/C1.html").check(m);
new OutputChecker("x/C2.html").check(m);
new OutputChecker(Output.OUT).setExpectFound(false)
.check("warning: not a direct supertype"); // no unexpected warnings
}
/*
* C1.m directedly inherits documentation from B1, which in turn inherits
* it undirectedly from A.
*
* C2.m directedly inherits documentation from B2, which in turn inherits
* in directedly from A.
*/
@Test
public void testRecursiveInheritance2(Path base) throws Exception {
Path src = base.resolve("src");
tb.writeJavaFiles(src, """
package x;
public class A {
/** A.m() */
public void m() { }
}
""", """
package x;
public class B1 extends A {
/** {@inheritDoc} */
@Override public void m() { }
}
""", """
package x;
public class C1 extends B1 {
/** {@inheritDoc B1} */
@Override
public void m() { }
}
""", """
package x;
public class B2 extends A {
/** {@inheritDoc A} */
@Override public void m() { }
}
""", """
package x;
public class C2 extends B2 {
/** {@inheritDoc B2} */
@Override public void m() { }
}
""");
javadoc("-d", base.resolve("out").toString(),
"--source-path", src.toString(),
"x");
checkExit(Exit.OK);
var m = """
<section class="detail" id="m()">
<h3>m</h3>
<div class="member-signature"><span class="modifiers">\
public</span>&nbsp;<span class="return-type">void</span>\
&nbsp;<span class="element-name">m</span>()</div>
<div class="block">A.m()</div>""";
new OutputChecker("x/C1.html").check(m);
new OutputChecker("x/C2.html").check(m);
new OutputChecker(Output.OUT).setExpectFound(false)
.check("warning: not a direct supertype"); // no unexpected warnings
}
/*
* Currently, there's no special error for a documentation comment that inherits
* from itself. Instead, such a comment is seen as a general case of a comment
* that inherits from a documentation of a method which that comment's method
* does not override (JLS says that a method does not override itself).
*
* TODO: DocLint might not always be able to find another type, but it
* should always be capable of detecting the same type; we could
* consider implementing this check _also_ in DocLint
*/
@Test
public void testSelfInheritance(Path base) throws Exception {
Path src = base.resolve("src");
tb.writeJavaFiles(src, """
package x;
public class A {
/** {@inheritDoc A} */
public Integer minus(Integer i) { return -i; }
}
""", """
package x;
public class B {
/** @param i {@inheritDoc B} */
public Integer minus(Integer i) { return -i; }
}
""", """
package x;
public class C {
/** @param <T> {@inheritDoc C} */
public <T> Integer minus(Integer i) { return -i; }
}
""", """
package x;
public class D {
/** @return {@inheritDoc D} */
public Integer minus(Integer i) { return -i; }
}
""", """
package x;
public class E {
/** @throws NullPointerException {@inheritDoc E} */
public Integer minus(Integer i) { return -i; }
}
""", """
package x;
public class F {
/** @throws T NullPointerException {@inheritDoc F} */
public <T extends RuntimeException> Integer minus(Integer i) { return -i; }
}
""");
javadoc("-Xdoclint:none", // turn off DocLint
"-d", base.resolve("out").toString(),
"--source-path", src.toString(),
"x");
checkExit(Exit.ERROR);
new OutputChecker(Output.OUT).setExpectOrdered(false).check("""
A.java:3: error: cannot find the overridden method
/** {@inheritDoc A} */
^""", """
B.java:3: error: cannot find the overridden method
/** @param i {@inheritDoc B} */
^""", """
C.java:3: error: cannot find the overridden method
/** @param <T> {@inheritDoc C} */
^""", """
D.java:3: error: cannot find the overridden method
/** @return {@inheritDoc D} */
^""", """
E.java:3: error: cannot find the overridden method
/** @throws NullPointerException {@inheritDoc E} */
^""", """
F.java:3: error: cannot find the overridden method
/** @throws T NullPointerException {@inheritDoc F} */
^""");
new OutputChecker(Output.OUT).setExpectFound(false)
.check("warning: not a direct supertype"); // no unexpected warnings
}
/*
* While E1.m and I.m have override-equivalent signatures, E1 does not extend I.
* While E2 extends I, E2.m1 does not override I.m. In either case, there's no
* (overridden) method to inherit documentation from.
*/
@Test
public void testInvalidSupertype1(Path base) throws Exception {
Path src = base.resolve("src");
tb.writeJavaFiles(src, """
package x;
public interface I {
/**
* I: main description
*
* @param <A> I: first type parameter
* @param <B> I: second type parameter
*
* @param bObj I: parameter
* @return I: return
*
* @throws B I: first description of an exception
* @throws B I: second description of an exception
*/
<A, B extends RuntimeException> int m(A bObj);
}
""", """
package x;
public interface E1 {
/**
* {@inheritDoc I}
*
* @param <C> {@inheritDoc I}
* @param <D> {@inheritDoc I}
*
* @param cObj {@inheritDoc I}
* @return {@inheritDoc I}
*
* @throws D {@inheritDoc I}
*/
<C, D extends RuntimeException> int m(C cObj);
}
""", """
package x;
public interface E2 extends I {
/**
* {@inheritDoc I}
*
* @param <E> {@inheritDoc I}
* @param <F> {@inheritDoc I}
*
* @param eObj {@inheritDoc I}
* @return {@inheritDoc I}
*
* @throws F {@inheritDoc I}
*/
<E, F extends RuntimeException> int m1(E eObj);
}
""");
javadoc("-Xdoclint:none", // turn off DocLint
"-d", base.resolve("out").toString(),
"--source-path", src.toString(),
"x");
checkExit(Exit.ERROR);
new OutputChecker(Output.OUT).setExpectOrdered(false).check("""
E1.java:4: error: cannot find the overridden method
* {@inheritDoc I}
^""", """
E1.java:6: error: cannot find the overridden method
* @param <C> {@inheritDoc I}
^""", """
E1.java:7: error: cannot find the overridden method
* @param <D> {@inheritDoc I}
^""", """
E1.java:9: error: cannot find the overridden method
* @param cObj {@inheritDoc I}
^""", """
E1.java:10: error: cannot find the overridden method
* @return {@inheritDoc I}
^""", """
E1.java:12: error: cannot find the overridden method
* @throws D {@inheritDoc I}
^""");
new OutputChecker(Output.OUT).setExpectOrdered(false).check("""
E2.java:4: error: cannot find the overridden method
* {@inheritDoc I}
^""", """
E2.java:6: error: cannot find the overridden method
* @param <E> {@inheritDoc I}
^""", """
E2.java:7: error: cannot find the overridden method
* @param <F> {@inheritDoc I}
^""", """
E2.java:9: error: cannot find the overridden method
* @param eObj {@inheritDoc I}
^""", """
E2.java:10: error: cannot find the overridden method
* @return {@inheritDoc I}
^""", """
E2.java:12: error: cannot find the overridden method
* @throws F {@inheritDoc I}
^""");
new OutputChecker(Output.OUT).setExpectFound(false)
.check("warning: not a direct supertype"); // no unexpected warnings
}
/*
* Cannot inherit documentation from a subtype.
*/
@Test
public void testInvalidSupertype2(Path base) throws Exception {
Path src = base.resolve("src");
tb.writeJavaFiles(src, """
package x;
public interface E extends I {
/**
* E: main description
*
* @param <A> E: first type parameter
* @param <B> E: second type parameter
*
* @param aObj E: parameter
* @return E: return
*
* @throws B E: first description of an exception
* @throws B E: second description of an exception
*/
<A, B extends RuntimeException> int m(A aObj);
}
""", """
package x;
public interface I {
/**
* {@inheritDoc E}
*
* @param <C> {@inheritDoc E}
* @param <D> {@inheritDoc E}
*
* @param cObj {@inheritDoc E}
* @return {@inheritDoc E}
*
* @throws D {@inheritDoc E}
*/
<C, D extends RuntimeException> int m(C cObj);
}
""");
javadoc("-Xdoclint:none", // turn off DocLint
"-d", base.resolve("out").toString(),
"--source-path", src.toString(),
"x");
checkExit(Exit.ERROR);
new OutputChecker(Output.OUT).setExpectOrdered(false).check("""
I.java:4: error: cannot find the overridden method
* {@inheritDoc E}
^""", """
I.java:6: error: cannot find the overridden method
* @param <C> {@inheritDoc E}
^""", """
I.java:7: error: cannot find the overridden method
* @param <D> {@inheritDoc E}
^""", """
I.java:9: error: cannot find the overridden method
* @param cObj {@inheritDoc E}
^""", """
I.java:10: error: cannot find the overridden method
* @return {@inheritDoc E}
^""", """
I.java:12: error: cannot find the overridden method
* @throws D {@inheritDoc E}
^""");
}
@Test
public void testUnknownSupertype(Path base) throws Exception {
Path src = base.resolve("src");
tb.writeJavaFiles(src, """
package x;
public class A {
/** {@inheritDoc MySuperType} */
public Integer m(Integer i) { return -i; }
}
""", """
package x;
public class B {
/** @param i {@inheritDoc MySuperType} */
public Integer minus(Integer i) { return -i; }
}
""", """
package x;
public class C {
/** @param <T> {@inheritDoc MySuperType} */
public <T> Integer minus(Integer i) { return -i; }
}
""", """
package x;
public class D {
/** @return {@inheritDoc MySuperType} */
public Integer minus(Integer i) { return -i; }
}
""", """
package x;
public class E {
/** @throws NullPointerException {@inheritDoc MySuperType} */
public Integer minus(Integer i) { return -i; }
}
""", """
package x;
public class F {
/** @throws T NullPointerException {@inheritDoc MySuperType} */
public <T extends RuntimeException> Integer minus(Integer i) { return -i; }
}
""");
javadoc("-Xdoclint:none", // turn off DocLint
"-d", base.resolve("out").toString(),
"--source-path", src.toString(),
"x");
checkExit(Exit.ERROR);
new OutputChecker(Output.OUT).setExpectOrdered(false).check("""
A.java:3: error: cannot find the overridden method
/** {@inheritDoc MySuperType} */
^""", """
B.java:3: error: cannot find the overridden method
/** @param i {@inheritDoc MySuperType} */
^""", """
C.java:3: error: cannot find the overridden method
/** @param <T> {@inheritDoc MySuperType} */
^""", """
D.java:3: error: cannot find the overridden method
/** @return {@inheritDoc MySuperType} */
^""", """
E.java:3: error: cannot find the overridden method
/** @throws NullPointerException {@inheritDoc MySuperType} */
^""", """
F.java:3: error: cannot find the overridden method
/** @throws T NullPointerException {@inheritDoc MySuperType} */
^""");
new OutputChecker(Output.OUT).setExpectFound(false)
.check("warning: not a direct supertype"); // no unexpected warnings
}
}

View File

@ -23,7 +23,7 @@
/*
* @test
* @bug 8284299 8287379 8298525
* @bug 8284299 8287379 8298525 6934301
* @library /tools/lib ../../lib
* @modules jdk.javadoc/jdk.javadoc.internal.tool
* @build toolbox.ToolBox javadoc.tester.*
@ -72,11 +72,29 @@ public class TestInheritDocWithinInappropriateTag extends JavadocTester {
@Override
public void x() { }
}
""",
"""
public class C extends A {
/**
* {@summary {@inheritDoc A}}
*
* {@link Object#hashCode() {@inheritDoc A}}
* {@linkplain Object#hashCode() {@inheritDoc A}}
*
* {@index term {@inheritDoc A}}
*
* @see A {@inheritDoc A}
* @spec http://example.com {@inheritDoc A}
*/
@Override
public void x() { }
}
""");
javadoc("-Xdoclint:none",
"-d", base.resolve("out").toString(),
src.resolve("A.java").toString(),
src.resolve("B.java").toString());
src.resolve("B.java").toString(),
src.resolve("C.java").toString());
checkExit(Exit.OK);
new OutputChecker(Output.OUT).setExpectOrdered(false).check(
"""
@ -108,6 +126,35 @@ public class TestInheritDocWithinInappropriateTag extends JavadocTester {
warning: @inheritDoc cannot be used within this tag
* @spec http://example.com {@inheritDoc}
^
""",
"""
warning: @inheritDoc cannot be used within this tag
* {@summary {@inheritDoc A}}
^
""",
"""
warning: @inheritDoc cannot be used within this tag
* {@link Object#hashCode() {@inheritDoc A}}
^
""",
"""
warning: @inheritDoc cannot be used within this tag
* {@linkplain Object#hashCode() {@inheritDoc A}}
^
""",
"""
warning: @inheritDoc cannot be used within this tag
* {@index term {@inheritDoc A}}
^
""", """
warning: @inheritDoc cannot be used within this tag
* @see A {@inheritDoc A}
^
""",
"""
warning: @inheritDoc cannot be used within this tag
* @spec http://example.com {@inheritDoc A}
^
""");
}

View File

@ -0,0 +1,530 @@
/*
* Copyright (c) 2022, 2023, 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 8285368
* @library /tools/lib ../../lib /test/lib
* @modules jdk.javadoc/jdk.javadoc.internal.tool
* @build toolbox.ToolBox javadoc.tester.*
* @build jtreg.SkippedException
* @run main TestMethodCommentsAlgorithm
*/
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.Modifier;
import javax.lang.model.type.TypeKind;
import javax.tools.ToolProvider;
import com.sun.source.doctree.DocCommentTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.PrimitiveTypeTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.DocTrees;
import com.sun.source.util.JavacTask;
import com.sun.source.util.TreePath;
import com.sun.source.util.TreePathScanner;
import javadoc.tester.JavadocTester;
import jtreg.SkippedException;
import toolbox.ToolBox;
import static javadoc.tester.JavadocTester.Exit.OK;
/*
* These tests assert search order for _undirected_ documentation inheritance by
* following a series of javadoc runs on a progressively undocumented hierarchy
* of supertypes.
*
* Design
* ======
*
* Each test creates a hierarchy consisting of N types (T1, T2, ..., Tn) for
* which the search order is to be asserted. N-1 types are created (T1, T2,
* ..., T(n-1)) and one type, Tn, is implicitly present java.lang.Object.
* T1 is a type under test; T2, T3, ..., T(n-1) are direct or indirect
* supertypes of T1.
*
* By design, the index of a type is evocative of the order in which that type
* should be considered for documentation inheritance. If T1 lacks a doc
* comment, T2 should be considered next. If in turn T2 lacks a doc comment,
* T3 should be considered after that, and so on. Finally, Tn, which is
* java.lang.Object, whose documentation is ever-present, is considered.
*
* The test then runs javadoc N-1 times. Each run one fewer type has a doc
* comment: for the i-th run (1 <= i < N), type Tj has a doc comment if and
* only if j > i. So, for the i-th run, i comments are missing and N-i are
* present. In particular, for the first run (i = 1) the only _missing_ doc
* comment is that of T1 and for the last run (i = N-1) the only _available_
* doc comment is that of java.lang.Object.
*
* The test challenges javadoc by asking the following question:
*
* Whose documentation will T1 inherit if Tj (1 <= j <= i)
* do not have doc comments, but Tk (i < k <= N) do?
*
* For the i-th run the test checks that T1 inherits documentation of T(i+1).
*
* Technicalities
* ==============
*
* 1. To follow search order up to and including java.lang.Object, these tests
* need to be able to inherit documentation for java.lang.Object. For that,
* the tests access doc comments of java.lang.Object. To get such access,
* the tests patch the java.base module.
*
* 2. The documentation for java.lang.Object is slightly amended for
* uniformity with test documentation and for additional test
* coverage.
*
* 3. While documentation for java.lang.Object is currently inaccessible outside
* of the JDK, these test mimic what happens when the JDK documentation is
* built.
*/
public class TestMethodCommentsAlgorithm extends JavadocTester {
private final ToolBox tb = new ToolBox();
public static void main(String... args) throws Exception {
new TestMethodCommentsAlgorithm().runTests();
}
/*
* Tests that the documentation search order is as shown:
*
* (5)
* ^
* * /
* [7] (3) (4)
* ^ ^ ^
* \ | /
* \ | /
* [2] (6)
* ^ ^
* | /
* | /
* [1]
*/
@Test
public void testMixedHierarchyEquals(Path base) throws Exception {
Path p = Path.of(System.getProperty("test.src", ".")).toAbsolutePath();
while (!Files.exists(p.resolve("TEST.ROOT"))) {
p = p.getParent();
if (p == null) {
throw new SkippedException("can't find TEST.ROOT");
}
}
out.println("Test suite root: " + p);
Path javaBase = p.resolve("../../src/java.base").normalize();
if (!Files.exists(javaBase)) {
throw new SkippedException("can't find java.base");
}
out.println("java.base: " + javaBase);
for (int i = 1; i < 7; i++) {
mixedHierarchyI(base, javaBase, i);
new OutputChecker("mymodule/x/T1.html").check("""
<div class="block">T%s: main description</div>
""".formatted(i + 1), """
<dt>Parameters:</dt>
<dd><code>obj</code> - T%1$s: parameter description</dd>
<dt>Returns:</dt>
<dd>T%1$s: return description</dd>""".formatted(i + 1));
}
}
/*
* Generates source for the i-th run such that types whose index is less
* than i provide no documentation and those whose index is greater or
* equal to i provide documentation.
*/
private void mixedHierarchyI(Path base, Path javaBase, int i) throws IOException {
Path src = base.resolve("src-" + i);
Path mod = base.resolve("src-" + i).resolve("mymodule");
tb.writeJavaFiles(mod, """
package x;
public class T1 extends T2 implements T6 {
%s
@Override public boolean equals(Object obj) { return super.equals(obj); }
}
""".formatted(generateDocComment(1, i)), """
package x;
public class T2 /* extends Object */ implements T3, T4 {
%s
@Override public boolean equals(Object obj) { return super.equals(obj); }
}
""".formatted(generateDocComment(2, i)), """
package x;
public interface T3 {
%s
@Override boolean equals(Object obj);
}
""".formatted(generateDocComment(3, i)), """
package x;
public interface T4 extends T5 {
%s
@Override boolean equals(Object obj);
}
""".formatted(generateDocComment(4, i)), """
package x;
public interface T5 {
%s
@Override boolean equals(Object obj);
}
""".formatted(generateDocComment(5, i)), """
package x;
public interface T6 {
%s
@Override boolean equals(Object obj);
}
""".formatted(generateDocComment(6, i)), """
module mymodule { }
""");
createPatchedJavaLangObject(javaBase.resolve("share").resolve("classes").toAbsolutePath(),
Files.createDirectories(src.resolve("java.base")).toAbsolutePath(),
generateDocComment(7, i, false));
javadoc("-d", base.resolve("out-" + i).toAbsolutePath().toString(),
"-tag", "apiNote:a:API Note:",
"-tag", "implSpec:a:Implementation Requirements:",
"-tag", "implNote:a:Implementation Note:",
"--patch-module", "java.base=" + src.resolve("java.base").toAbsolutePath().toString(),
"--module-source-path", src.toAbsolutePath().toString(),
"mymodule/x");
checkExit(OK);
}
private static String generateDocComment(int index, int run) {
return generateDocComment(index, run, true);
}
/*
* Provides a doc comment for an override of Object.equals in a type with
* the specified index for the specified run.
*/
private static String generateDocComment(int index, int run, boolean includeCommentMarkers) {
if (index > run) {
String s = """
T%s: main description
*
* @param obj T%1$s: parameter description
* @return T%1$s: return description""";
if (includeCommentMarkers)
s = "/**\n* " + s + "\n*/";
return s.formatted(index).indent(4);
} else {
return "";
}
}
/*
* Tests that the documentation search order is as shown:
*
* (3) (4)
* ^ ^
* \ /
* (2) (5)
* ^ ^
* \ /
* (1)
* |
* v
* [6]
* *
*/
@Test
public void testInterfaceHierarchy(Path base) throws Exception {
Path p = Path.of(System.getProperty("test.src", ".")).toAbsolutePath();
while (!Files.exists(p.resolve("TEST.ROOT"))) {
p = p.getParent();
if (p == null) {
throw new SkippedException("can't find TEST.ROOT");
}
}
System.err.println("Test suite root: " + p);
Path javaBase = p.resolve("../../src/java.base").normalize();
if (!Files.exists(javaBase)) {
throw new SkippedException("can't find java.base");
}
System.err.println("java.base: " + javaBase);
for (int i = 1; i < 6; i++) {
interfaceHierarchyI(base, javaBase, i);
new OutputChecker("mymodule/x/T1.html").check("""
<div class="block">T%s: main description</div>
""".formatted(i + 1), """
<dt>Parameters:</dt>
<dd><code>obj</code> - T%1$s: parameter description</dd>
<dt>Returns:</dt>
<dd>T%1$s: return description</dd>""".formatted(i + 1));
}
}
/*
* Nested/recursive `{@inheritDoc}` are processed before the comments that
* refer to them. This test highlights that a lone `{@inheritDoc}` is
* different from a missing/empty comment part.
*
* Whenever doclet sees `{@inheritDoc}` or `{@inheritDoc <supertype>}`
* while searching for a comment to inherit from up the hierarchy, it
* considers the comment found. A separate and unrelated search is
* then performed for that found `{@inheritDoc}`.
*
* The test case is wrapped in a module in order to be able to patch
* java.base (otherwise it doesn't seem to work).
*/
@Test
public void testRecursiveInheritDocTagsAreProcessedFirst(Path base) throws Exception {
Path p = Path.of(System.getProperty("test.src", ".")).toAbsolutePath();
while (!Files.exists(p.resolve("TEST.ROOT"))) {
p = p.getParent();
if (p == null) {
throw new SkippedException("can't find TEST.ROOT");
}
}
System.err.println("Test suite root: " + p);
Path javaBase = p.resolve("../../src/java.base").normalize();
if (!Files.exists(javaBase)) {
throw new SkippedException("can't find java.base");
}
System.err.println("java.base: " + javaBase);
Path src = base.resolve("src");
tb.writeJavaFiles(src.resolve("mymodule"), """
package x;
public class S {
/** {@inheritDoc} */
public boolean equals(Object obj) { return super.equals(obj); }
}
""", """
package x;
public interface I {
/** I::equals */
boolean equals(Object obj);
}
""", """
package x;
public class T extends S implements I {
public boolean equals(Object obj) { return super.equals(obj); }
}
""", """
module mymodule {}
""");
createPatchedJavaLangObject(javaBase.resolve("share").resolve("classes").toAbsolutePath(),
Files.createDirectories(src.resolve("java.base")).toAbsolutePath(),
"Object::equals");
javadoc("-d", base.resolve("out").toString(),
"-tag", "apiNote:a:API Note:",
"-tag", "implSpec:a:Implementation Requirements:",
"-tag", "implNote:a:Implementation Note:",
"--patch-module", "java.base=" + src.resolve("java.base").toAbsolutePath().toString(),
"--module-source-path", src.toAbsolutePath().toString(),
"mymodule/x");
checkExit(Exit.OK);
new OutputChecker("mymodule/x/T.html").check("""
<div class="block">Object::equals</div>""");
}
/*
* Generates source for the i-th run such that types whose index is less
* than i provide no documentation and those whose index is greater or
* equal to i provide documentation.
*/
private void interfaceHierarchyI(Path base, Path javaBase, int i) throws IOException {
Path src = base.resolve("src-" + i);
Path mod = base.resolve("src-" + i).resolve("mymodule");
tb.writeJavaFiles(mod, """
package x;
public interface T1 extends T2, T5 {
%s
@Override boolean equals(Object obj);
}
""".formatted(generateDocComment(1, i)), """
package x;
public interface T2 extends T3, T4 {
%s
@Override boolean equals(Object obj);
}
""".formatted(generateDocComment(2, i)), """
package x;
public interface T3 {
%s
@Override boolean equals(Object obj);
}
""".formatted(generateDocComment(3, i)), """
package x;
public interface T4 {
%s
@Override boolean equals(Object obj);
}
""".formatted(generateDocComment(4, i)), """
package x;
public interface T5 {
%s
@Override boolean equals(Object obj);
}
""".formatted(generateDocComment(5, i)), """
module mymodule { }
""");
createPatchedJavaLangObject(javaBase.resolve("share").resolve("classes").toAbsolutePath(),
Files.createDirectories(src.resolve("java.base")).toAbsolutePath(),
generateDocComment(6, i, false));
javadoc("-d", base.resolve("out-" + i).toAbsolutePath().toString(),
"-tag", "apiNote:a:API Note:",
"-tag", "implSpec:a:Implementation Requirements:",
"-tag", "implNote:a:Implementation Note:",
"--patch-module", "java.base=" + src.resolve("java.base").toAbsolutePath().toString(),
"--module-source-path", src.toAbsolutePath().toString(),
"mymodule/x");
checkExit(OK);
}
/*
* Takes a path to the java.base module, finds the Object.java file in
* there, creates a copy of that file _with the modified doc comment_
* for Object.equals in the provided destination directory and returns
* the path to that created copy.
*/
private Path createPatchedJavaLangObject(Path src, Path dst, String newComment)
throws IOException {
if (!Files.isDirectory(src) || !Files.isDirectory(dst)) {
throw new IllegalArgumentException();
}
var obj = Path.of("java/lang/Object.java");
List<Path> files;
// ensure Object.java is found and unique
try (var s = Files.find(src, Integer.MAX_VALUE,
(p, attr) -> attr.isRegularFile() && p.endsWith(obj))) {
files = s.limit(2).toList(); // 2 is enough to deduce non-uniqueness
}
if (files.size() != 1) {
throw new IllegalStateException(Arrays.toString(files.toArray()));
}
var original = files.get(0);
out.println("found " + original.toAbsolutePath());
var source = Files.readString(original);
var region = findDocCommentRegion(original);
var newSource = source.substring(0, region.start)
+ newComment
+ source.substring(region.end);
// create intermediate directories in the destination first, otherwise
// writeString will throw java.nio.file.NoSuchFileException
var copy = dst.resolve(src.relativize(original));
out.println("to be copied to " + copy);
if (Files.notExists(copy.getParent())) {
Files.createDirectories(copy.getParent());
}
return Files.writeString(copy, newSource, StandardOpenOption.CREATE);
}
private static SourceRegion findDocCommentRegion(Path src) throws IOException {
// to _reliably_ find the doc comment, parse the file and find source
// position of the doc tree corresponding to that comment
var compiler = ToolProvider.getSystemJavaCompiler();
var fileManager = compiler.getStandardFileManager(null, null, null);
var fileObject = fileManager.getJavaFileObjects(src).iterator().next();
var task = (JavacTask) compiler.getTask(null, null, null, null, null, List.of(fileObject));
Iterator<? extends CompilationUnitTree> iterator = task.parse().iterator();
if (!iterator.hasNext()) {
throw new AssertionError();
}
var tree = iterator.next();
var pathToEqualsMethod = findMethod(tree);
var trees = DocTrees.instance(task);
DocCommentTree docCommentTree = trees.getDocCommentTree(pathToEqualsMethod);
if (docCommentTree == null)
throw new AssertionError("cannot find the doc comment for java.lang.Object#equals");
var positions = trees.getSourcePositions();
long start = positions.getStartPosition(null, docCommentTree, docCommentTree);
long end = positions.getEndPosition(null, docCommentTree, docCommentTree);
return new SourceRegion((int) start, (int) end);
}
private static TreePath findMethod(Tree src) {
class Result extends RuntimeException {
final TreePath p;
Result(TreePath p) {
super("", null, false, false); // lightweight exception to short-circuit scan
this.p = p;
}
}
var scanner = new TreePathScanner<Void, Void>() {
@Override
public Void visitMethod(MethodTree m, Void unused) {
boolean solelyPublic = m.getModifiers().getFlags().equals(Set.of(Modifier.PUBLIC));
if (!solelyPublic) {
return null;
}
var returnType = m.getReturnType();
boolean returnsBoolean = returnType != null
&& returnType.getKind() == Tree.Kind.PRIMITIVE_TYPE
&& ((PrimitiveTypeTree) returnType).getPrimitiveTypeKind() == TypeKind.BOOLEAN;
if (!returnsBoolean) {
return null;
}
boolean hasNameEquals = m.getName().toString().equals("equals");
if (!hasNameEquals) {
return null;
}
List<? extends VariableTree> params = m.getParameters();
if (params.size() != 1)
return null;
var parameterType = params.get(0).getType();
if (parameterType.getKind() == Tree.Kind.IDENTIFIER &&
((IdentifierTree) parameterType).getName().toString().equals("Object")) {
throw new Result(getCurrentPath());
}
return null;
}
};
try {
scanner.scan(src, null);
return null; // not found
} catch (Result e) {
return e.p; // found
}
}
record SourceRegion(int start, int end) { }
}

View File

@ -495,7 +495,12 @@ public class DocCommentTester {
}
public Void visitInheritDoc(InheritDocTree node, Void p) {
header(node, "");
header(node);
indent(+1);
print("supertype", node.getSupertype());
indent(-1);
indent();
out.println("]");
return null;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2023, 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
@ -23,7 +23,7 @@
/*
* @test
* @bug 7021614 8273244
* @bug 7021614 8273244 6934301
* @summary extend com.sun.source API to support parsing javadoc comments
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.file
@ -40,7 +40,9 @@ class InheritDocTest {
DocComment[DOC_COMMENT, pos:0
firstSentence: 2
Text[TEXT, pos:0, abc_]
InheritDoc[INHERIT_DOC, pos:4]
InheritDoc[INHERIT_DOC, pos:4
supertype: null
]
body: empty
block tags: empty
]
@ -52,7 +54,9 @@ DocComment[DOC_COMMENT, pos:0
DocComment[DOC_COMMENT, pos:0
firstSentence: 2
Text[TEXT, pos:0, abc_]
InheritDoc[INHERIT_DOC, pos:4]
InheritDoc[INHERIT_DOC, pos:4
supertype: null
]
body: empty
block tags: empty
]
@ -64,21 +68,53 @@ DocComment[DOC_COMMENT, pos:0
DocComment[DOC_COMMENT, pos:0
firstSentence: 2
Text[TEXT, pos:0, abc_]
InheritDoc[INHERIT_DOC, pos:4]
InheritDoc[INHERIT_DOC, pos:4
supertype: null
]
body: empty
block tags: empty
]
*/
/** abc {@inheritDoc junk} */
void error() { }
/** abc {@inheritDoc String} */
void simple() { }
/*
DocComment[DOC_COMMENT, pos:0
firstSentence: 2
Text[TEXT, pos:0, abc_]
Erroneous[ERRONEOUS, pos:4, prefPos:17
InheritDoc[INHERIT_DOC, pos:4
supertype:
Reference[REFERENCE, pos:17, String]
]
body: empty
block tags: empty
]
*/
/** abc {@inheritDoc java.util.List<E>} */
void fullyQualifiedTypeWithWildCardUpperBound() { }
/*
DocComment[DOC_COMMENT, pos:0
firstSentence: 2
Text[TEXT, pos:0, abc_]
InheritDoc[INHERIT_DOC, pos:4
supertype:
Reference[REFERENCE, pos:17, java.util.List<E>]
]
body: empty
block tags: empty
]
*/
/** abc {@inheritDoc Integer Number} */
void unexpectedContentAfterReference() { }
/*
DocComment[DOC_COMMENT, pos:0
firstSentence: 2
Text[TEXT, pos:0, abc_]
Erroneous[ERRONEOUS, pos:4, prefPos:27
code: compiler.err.dc.unexpected.content
body: {@inheritDoc_junk}
body: {@inheritDoc_Integer___Number}
]
body: empty
block tags: empty