From 3e0bbd290c534b0f9729c54cd45308d505907797 Mon Sep 17 00:00:00 2001 From: Pavel Rappo Date: Thu, 15 Jun 2023 17:47:41 +0000 Subject: [PATCH] 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 --- .../share/classes/java/util/TreeMap.java | 2 + .../util/concurrent/ConcurrentHashMap.java | 10 +- .../concurrent/ConcurrentSkipListMap.java | 10 +- .../util/concurrent/LinkedBlockingDeque.java | 10 + .../stream/FileCacheImageOutputStream.java | 5 + .../stream/MemoryCacheImageOutputStream.java | 5 + .../swing/plaf/basic/BasicDesktopIconUI.java | 8 + .../plaf/basic/BasicInternalFrameUI.java | 20 + .../sun/source/doctree/InheritDocTree.java | 17 +- .../com/sun/source/util/DocTreeFactory.java | 11 + .../com/sun/source/util/DocTreeScanner.java | 4 +- .../tools/javac/parser/DocCommentParser.java | 12 +- .../com/sun/tools/javac/tree/DCTree.java | 11 + .../com/sun/tools/javac/tree/DocPretty.java | 4 + .../sun/tools/javac/tree/DocTreeMaker.java | 7 +- .../toolkit/resources/doclets.properties | 1 + .../toolkit/taglets/InheritDocTaglet.java | 81 ++- .../toolkit/taglets/InheritableTaglet.java | 8 +- .../doclets/toolkit/taglets/ParamTaglet.java | 26 +- .../doclets/toolkit/taglets/ReturnTaglet.java | 13 +- .../doclets/toolkit/taglets/SeeTaglet.java | 4 +- .../doclets/toolkit/taglets/SimpleTaglet.java | 18 +- .../doclets/toolkit/taglets/SpecTaglet.java | 4 +- .../doclets/toolkit/taglets/ThrowsTaglet.java | 76 +- .../doclets/toolkit/util/CommentHelper.java | 6 + .../doclets/toolkit/util/DocFinder.java | 57 +- .../internal/doclets/toolkit/util/Utils.java | 133 +++- .../TestDirectedInheritance.java | 665 ++++++++++++++++++ .../TestInheritDocWithinInappropriateTag.java | 51 +- .../TestMethodCommentsAlgorithm.java | 530 ++++++++++++++ .../tools/javac/doctree/DocCommentTester.java | 7 +- .../tools/javac/doctree/InheritDocTest.java | 54 +- 32 files changed, 1736 insertions(+), 134 deletions(-) create mode 100644 test/langtools/jdk/javadoc/doclet/testDirectedInheritance/TestDirectedInheritance.java create mode 100644 test/langtools/jdk/javadoc/doclet/testMethodCommentAlgorithm/TestMethodCommentsAlgorithm.java diff --git a/src/java.base/share/classes/java/util/TreeMap.java b/src/java.base/share/classes/java/util/TreeMap.java index a91a56a5900..ce2a2f72c34 100644 --- a/src/java.base/share/classes/java/util/TreeMap.java +++ b/src/java.base/share/classes/java/util/TreeMap.java @@ -1195,6 +1195,8 @@ public class TreeMap * {@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> entrySet() { EntrySet es = entrySet; diff --git a/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java b/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java index 024390f26d9..8986bb5486c 100644 --- a/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java +++ b/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java @@ -1531,7 +1531,7 @@ public class ConcurrentHashMap extends AbstractMap // 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 extends AbstractMap } /** - * {@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 extends AbstractMap } /** - * {@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 extends AbstractMap } /** - * {@inheritDoc} + * {@inheritDoc ConcurrentMap} * * @return the previous value associated with the specified key, * or {@code null} if there was no mapping for the key diff --git a/src/java.base/share/classes/java/util/concurrent/ConcurrentSkipListMap.java b/src/java.base/share/classes/java/util/concurrent/ConcurrentSkipListMap.java index bccbccf5725..ced890f3b51 100644 --- a/src/java.base/share/classes/java/util/concurrent/ConcurrentSkipListMap.java +++ b/src/java.base/share/classes/java/util/concurrent/ConcurrentSkipListMap.java @@ -1773,7 +1773,7 @@ public class ConcurrentSkipListMap extends AbstractMap /* ------ 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 extends AbstractMap } /** - * {@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 extends AbstractMap } /** - * {@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 extends AbstractMap } /** - * {@inheritDoc} + * {@inheritDoc ConcurrentMap} * * @return the previous value associated with the specified key, * or {@code null} if there was no mapping for the key diff --git a/src/java.base/share/classes/java/util/concurrent/LinkedBlockingDeque.java b/src/java.base/share/classes/java/util/concurrent/LinkedBlockingDeque.java index 926b4b8cfb6..ba576ef8c74 100644 --- a/src/java.base/share/classes/java/util/concurrent/LinkedBlockingDeque.java +++ b/src/java.base/share/classes/java/util/concurrent/LinkedBlockingDeque.java @@ -628,7 +628,9 @@ public class LinkedBlockingDeque } /** + * {@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 return removeFirst(); } + /** + * {@inheritDoc BlockingDeque} + * @return {@inheritDoc BlockingDeque} + */ public E poll() { return pollFirst(); } @@ -691,6 +697,10 @@ public class LinkedBlockingDeque return getFirst(); } + /** + * {@inheritDoc BlockingDeque} + * @return {@inheritDoc BlockingDeque} + */ public E peek() { return peekFirst(); } diff --git a/src/java.desktop/share/classes/javax/imageio/stream/FileCacheImageOutputStream.java b/src/java.desktop/share/classes/javax/imageio/stream/FileCacheImageOutputStream.java index 904515b6f82..5886831d6e9 100644 --- a/src/java.desktop/share/classes/javax/imageio/stream/FileCacheImageOutputStream.java +++ b/src/java.desktop/share/classes/javax/imageio/stream/FileCacheImageOutputStream.java @@ -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 diff --git a/src/java.desktop/share/classes/javax/imageio/stream/MemoryCacheImageOutputStream.java b/src/java.desktop/share/classes/javax/imageio/stream/MemoryCacheImageOutputStream.java index cdec1d40007..dd7049689b4 100644 --- a/src/java.desktop/share/classes/javax/imageio/stream/MemoryCacheImageOutputStream.java +++ b/src/java.desktop/share/classes/javax/imageio/stream/MemoryCacheImageOutputStream.java @@ -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 diff --git a/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicDesktopIconUI.java b/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicDesktopIconUI.java index 2b2c74536c4..7bd96a9ed47 100644 --- a/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicDesktopIconUI.java +++ b/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicDesktopIconUI.java @@ -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); diff --git a/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicInternalFrameUI.java b/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicInternalFrameUI.java index 4e2e0fac305..a94a051f554 100644 --- a/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicInternalFrameUI.java +++ b/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicInternalFrameUI.java @@ -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(); } diff --git a/src/jdk.compiler/share/classes/com/sun/source/doctree/InheritDocTree.java b/src/jdk.compiler/share/classes/com/sun/source/doctree/InheritDocTree.java index 304bde3908f..7d3624413ed 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/doctree/InheritDocTree.java +++ b/src/jdk.compiler/share/classes/com/sun/source/doctree/InheritDocTree.java @@ -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; * *
  *    {@inheritDoc}
+ *    {@inheritDoc supertype}
  * 
* * @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; + } +} diff --git a/src/jdk.compiler/share/classes/com/sun/source/util/DocTreeFactory.java b/src/jdk.compiler/share/classes/com/sun/source/util/DocTreeFactory.java index 571db71546e..391acfdfb6e 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/util/DocTreeFactory.java +++ b/src/jdk.compiler/share/classes/com/sun/source/util/DocTreeFactory.java @@ -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 diff --git a/src/jdk.compiler/share/classes/com/sun/source/util/DocTreeScanner.java b/src/jdk.compiler/share/classes/com/sun/source/util/DocTreeScanner.java index 28cc82e7378..cde78ea467c 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/util/DocTreeScanner.java +++ b/src/jdk.compiler/share/classes/com/sun/source/util/DocTreeScanner.java @@ -340,7 +340,7 @@ public class DocTreeScanner implements DocTreeVisitor { /** * {@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 implements DocTreeVisitor { */ @Override public R visitInheritDoc(InheritDocTree node, P p) { - return null; + return scan(node.getSupertype(), p); } /** diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/DocCommentParser.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/DocCommentParser.java index 2b0deb66b02..603e7c42807 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/DocCommentParser.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/DocCommentParser.java @@ -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 diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DCTree.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DCTree.java index 85c729e951f..b1f9802354d 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DCTree.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DCTree.java @@ -748,11 +748,22 @@ public abstract class DCTree implements DocTree { } public static class DCInheritDoc extends DCInlineTag 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 accept(DocTreeVisitor v, D d) { return v.visitInheritDoc(this, d); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocPretty.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocPretty.java index a810fcd7c1f..e4f7cf096eb 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocPretty.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocPretty.java @@ -310,6 +310,10 @@ public class DocPretty implements DocTreeVisitor { try { print('{'); printTagName(node); + if (node.getSupertype() != null) { + print(" "); + print(node.getSupertype()); + } print('}'); } catch (IOException e) { throw new UncheckedIOException(e); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocTreeMaker.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocTreeMaker.java index a4884165f6f..afa590240e2 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocTreeMaker.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocTreeMaker.java @@ -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; } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets.properties b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets.properties index c4169216ee8..680c1877471 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets.properties +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets.properties @@ -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 \ diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/InheritDocTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/InheritDocTaglet.java index 194b78a3fc8..149770b177f 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/InheritDocTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/InheritDocTaglet.java @@ -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 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 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 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 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 mainDescription, ExecutableElement method) { } - private static Optional extractMainDescription(ExecutableElement m, + private static Result extractMainDescription(ExecutableElement m, boolean extractFirstSentenceOnly, Utils utils) { - List 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); } } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/InheritableTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/InheritableTaglet.java index 48d25b64199..531f5264f87 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/InheritableTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/InheritableTaglet.java @@ -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, diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/ParamTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/ParamTaglet.java index d98923d7c5b..98800a3bccc 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/ParamTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/ParamTaglet.java @@ -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 parameterElements; @@ -77,7 +77,7 @@ public class ParamTaglet extends BaseTaglet implements InheritableTaglet { parameterElements = method.getParameters(); } Map 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 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); } } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/ReturnTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/ReturnTaglet.java index e372b79cbef..9ad94ae7944 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/ReturnTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/ReturnTaglet.java @@ -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 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); } } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/SeeTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/SeeTaglet.java index 4b5ffc2545b..c4c68f21827 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/SeeTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/SeeTaglet.java @@ -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 */); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/SimpleTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/SimpleTaglet.java index e8c0a831bbd..cef474f05c0 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/SimpleTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/SimpleTaglet.java @@ -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 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); } } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/SpecTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/SpecTaglet.java index 017af410ca0..edefc1968e4 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/SpecTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/SpecTaglet.java @@ -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 */); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/ThrowsTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/ThrowsTaglet.java index d5ec82041dd..72cc98de572 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/ThrowsTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/ThrowsTaglet.java @@ -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 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 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 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 expandShallowly(Element exceptionType, - ExecutableElement holder) + ExecutableElement holder, + Optional src) throws Failure.ExceptionTypeNotFound, Failure.NotExceptionType, Failure.Invalid, Failure.UnsupportedTypeParameter, - DocFinder.NoOverriddenMethodsFound + DocFinder.NoOverriddenMethodFound { ElementKind kind = exceptionType.getKind(); DocFinder.Criterion, Failure> criterion; @@ -562,7 +606,11 @@ public class ThrowsTaglet extends BaseTaglet implements InheritableTaglet { } Result> 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) { diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/CommentHelper.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/CommentHelper.java index 008ce5ab80d..37965f56c5f 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/CommentHelper.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/CommentHelper.java @@ -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); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/DocFinder.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/DocFinder.java index b34f6cf4162..73e70866d07 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/DocFinder.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/DocFinder.java @@ -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 apply(ExecutableElement method) throws X; } - private final Function overriddenMethodLookup; - private final BiFunction> implementedMethodsLookup; + private final Function> overriddenMethodLookup; - DocFinder(Function overriddenMethodLookup, - BiFunction> implementedMethodsLookup) { + DocFinder(Function> 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 Result search(ExecutableElement method, Criterion criterion) throws X - { - return search(method, true, criterion); - } - - public Result search(ExecutableElement method, - boolean includeMethod, - Criterion 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 Result trySearch(ExecutableElement method, - Criterion criterion) - throws NoOverriddenMethodsFound, X + public Result find(ExecutableElement method, + Criterion criterion) + throws NoOverriddenMethodFound, X { return search0(method, false, true, criterion); } @@ -114,13 +100,13 @@ public class DocFinder { boolean includeMethodInSearch, boolean throwExceptionIfDoesNotOverride, Criterion 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 methods = methodsOverriddenBy(method); + Iterator methods = overriddenMethodLookup.apply(method).iterator(); if (throwExceptionIfDoesNotOverride && !methods.hasNext() ) { - throw new NoOverriddenMethodsFound(); + throw new NoOverriddenMethodFound(); } Result r = includeMethodInSearch ? criterion.apply(method) : Result.CONTINUE(); if (!(r instanceof Result.Continue)) { @@ -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) { 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 methodsOverriddenBy(ExecutableElement method) { - // TODO: create a lazy iterator if required - var list = new ArrayList(); - 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. diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java index c2ae5d6a24f..dee07a50b7b 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java @@ -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 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 overriddenMethods(ExecutableElement method) { + return () -> new Overrides(method); + } + + private class Overrides implements Iterator { + + // prefer java.util.Deque to java.util.Stack API for stacks + final Deque searchStack = new ArrayDeque<>(); + final Set 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(); + + // + 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); + // + + // 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 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); } } diff --git a/test/langtools/jdk/javadoc/doclet/testDirectedInheritance/TestDirectedInheritance.java b/test/langtools/jdk/javadoc/doclet/testDirectedInheritance/TestDirectedInheritance.java new file mode 100644 index 00000000000..87586e5deb4 --- /dev/null +++ b/test/langtools/jdk/javadoc/doclet/testDirectedInheritance/TestDirectedInheritance.java @@ -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 I1: first type parameter + * @param 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 + */ + int m(A bObj); + } + """, """ + package x; + public interface I2 { + /** + * I2: main description + * + * @param I2: first type parameter + * @param 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 + */ + int m(C cObj); + } + """, """ + package x; + public interface E1 extends I1, I2 { + /** + * {@inheritDoc I2} + * + * @param {@inheritDoc I2} + * @param {@inheritDoc I2} + * + * @param eObj {@inheritDoc I2} + * @return {@inheritDoc I2} + * + * @throws F {@inheritDoc I2} + */ + int m(E eObj); + } + """); + javadoc("-d", base.resolve("out").toString(), + "--source-path", src.toString(), + "x"); + checkExit(Exit.OK); + new OutputChecker("x/E1.html").check(""" +
I2: main description
+ """, """ +
Type Parameters:
+
E - I2: first type parameter
+
F - I2: second type parameter
+
Parameters:
+
eObj - I2: parameter
+
Returns:
+
I2: return
+
Throws:
+
F - I2: first description of an exception
+
F - I2: second description of an exception
+ """); + 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 + */ + 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 + */ + 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} + */ + int m(E eObj); + } + """); + javadoc("-d", base.resolve("out").toString(), + "--source-path", src.toString(), + "x"); + checkExit(Exit.OK); + new OutputChecker("x/E1.html").check(""" +
E1: main description + I2: main description + I1: main description
""", """ +
Throws:
+
F - E1: description of an exception
+
F - I2: first description of an exception
+
F - I2: second description of an exception
+
F - I1: first description of an exception
+
F - I1: second description of an exception
+ """); + 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 = """ +
+

m

+
\ + public void\ +  m()
+
A.m()
"""; + 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 = """ +
+

m

+
\ + public void\ +  m()
+
A.m()
"""; + 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 {@inheritDoc C} */ + public 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 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 {@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
I: first type parameter + * @param 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 + */ + int m(A bObj); + } + """, """ + package x; + public interface E1 { + /** + * {@inheritDoc I} + * + * @param {@inheritDoc I} + * @param {@inheritDoc I} + * + * @param cObj {@inheritDoc I} + * @return {@inheritDoc I} + * + * @throws D {@inheritDoc I} + */ + int m(C cObj); + } + """, """ + package x; + public interface E2 extends I { + /** + * {@inheritDoc I} + * + * @param {@inheritDoc I} + * @param {@inheritDoc I} + * + * @param eObj {@inheritDoc I} + * @return {@inheritDoc I} + * + * @throws F {@inheritDoc I} + */ + 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 {@inheritDoc I} + ^""", """ + E1.java:7: error: cannot find the overridden method + * @param {@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 {@inheritDoc I} + ^""", """ + E2.java:7: error: cannot find the overridden method + * @param {@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 E: first type parameter + * @param 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 + */ + int m(A aObj); + } + """, """ + package x; + public interface I { + /** + * {@inheritDoc E} + * + * @param {@inheritDoc E} + * @param {@inheritDoc E} + * + * @param cObj {@inheritDoc E} + * @return {@inheritDoc E} + * + * @throws D {@inheritDoc E} + */ + 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 {@inheritDoc E} + ^""", """ + I.java:7: error: cannot find the overridden method + * @param {@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 {@inheritDoc MySuperType} */ + public 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 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 {@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 + } +} diff --git a/test/langtools/jdk/javadoc/doclet/testInheritDocWithinInappropriateTag/TestInheritDocWithinInappropriateTag.java b/test/langtools/jdk/javadoc/doclet/testInheritDocWithinInappropriateTag/TestInheritDocWithinInappropriateTag.java index 49e1c406a16..253d0b6b232 100644 --- a/test/langtools/jdk/javadoc/doclet/testInheritDocWithinInappropriateTag/TestInheritDocWithinInappropriateTag.java +++ b/test/langtools/jdk/javadoc/doclet/testInheritDocWithinInappropriateTag/TestInheritDocWithinInappropriateTag.java @@ -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} + ^ """); } diff --git a/test/langtools/jdk/javadoc/doclet/testMethodCommentAlgorithm/TestMethodCommentsAlgorithm.java b/test/langtools/jdk/javadoc/doclet/testMethodCommentAlgorithm/TestMethodCommentsAlgorithm.java new file mode 100644 index 00000000000..2931ae40f9d --- /dev/null +++ b/test/langtools/jdk/javadoc/doclet/testMethodCommentAlgorithm/TestMethodCommentsAlgorithm.java @@ -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(""" +
T%s: main description
+ """.formatted(i + 1), """ +
Parameters:
+
obj - T%1$s: parameter description
+
Returns:
+
T%1$s: return description
""".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(""" +
T%s: main description
+ """.formatted(i + 1), """ +
Parameters:
+
obj - T%1$s: parameter description
+
Returns:
+
T%1$s: return description
""".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 }` + * 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(""" +
Object::equals
"""); + } + + /* + * 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 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 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() { + @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 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) { } +} diff --git a/test/langtools/tools/javac/doctree/DocCommentTester.java b/test/langtools/tools/javac/doctree/DocCommentTester.java index 1ae6fa768ee..ca50d883417 100644 --- a/test/langtools/tools/javac/doctree/DocCommentTester.java +++ b/test/langtools/tools/javac/doctree/DocCommentTester.java @@ -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; } diff --git a/test/langtools/tools/javac/doctree/InheritDocTest.java b/test/langtools/tools/javac/doctree/InheritDocTest.java index d5bc56cb6ef..8413d181be2 100644 --- a/test/langtools/tools/javac/doctree/InheritDocTest.java +++ b/test/langtools/tools/javac/doctree/InheritDocTest.java @@ -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} */ + 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] + ] + 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