/* * Copyright (c) 2022, 2025, 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 } }