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:
parent
3eeb681a0d
commit
3e0bbd290c
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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>
|
||||
* {@inheritDoc}
|
||||
* {@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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 \
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 */);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 */);
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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> <span class="return-type">void</span>\
|
||||
<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> <span class="return-type">void</span>\
|
||||
<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
|
||||
}
|
||||
}
|
@ -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}
|
||||
^
|
||||
""");
|
||||
}
|
||||
|
||||
|
@ -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) { }
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user