diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/VisibleMemberTable.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/VisibleMemberTable.java index 93268943290b8..58f010fb967ca 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/VisibleMemberTable.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/VisibleMemberTable.java @@ -678,22 +678,16 @@ private boolean allowInheritedMethod(ExecutableElement inheritedMethod, return false; } - // Multiple-Inheritance: remove the interface method that may have - // been overridden by another interface method in the hierarchy - // - // Note: The following approach is very simplistic and is compatible - // with old VMM. A future enhancement, may include a contention breaker, - // to correctly eliminate those methods that are merely definitions - // in favor of concrete overriding methods, for instance those that have - // API documentation and are not abstract OR default methods. + // Multiple-Inheritance: No Contention. In Java's method resolution, + // any override of a signature (whether by a subclass or by a subinterface, + // including when it is final from superclasses) always takes precedence + // over the original interface definition. All interface methods have low resolution priority. + // Therefore, when considering an interface inherited method, as soon as + // at least one overrider exists in the inheritance chain, + // we do not inherit the older interface definition. if (inInterface) { List list = overriddenByTable.get(inheritedMethod); - if (list != null) { - boolean found = list.stream() - .anyMatch(this::isDeclaredInInterface); - if (found) - return false; - } + if (list != null && !list.isEmpty()) return false; } Elements elementUtils = config.docEnv.getElementUtils(); diff --git a/test/langtools/jdk/javadoc/doclet/testDuplicateMethodsWarn/TestDuplicateMethods.java b/test/langtools/jdk/javadoc/doclet/testDuplicateMethodsWarn/TestDuplicateMethods.java new file mode 100644 index 0000000000000..7e191290f8a25 --- /dev/null +++ b/test/langtools/jdk/javadoc/doclet/testDuplicateMethodsWarn/TestDuplicateMethods.java @@ -0,0 +1,220 @@ +/* + * Copyright (c) 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 8177100 + * @summary Test to check for duplicate methods across different inheritance patterns + * @library /tools/lib ../../lib + * @modules jdk.javadoc/jdk.javadoc.internal.tool + * @build toolbox.ToolBox javadoc.tester.* + * @run main TestDuplicateMethods + */ + +import java.io.IOException; +import java.nio.file.Path; + +import javadoc.tester.JavadocTester; +import toolbox.ToolBox; + +public class TestDuplicateMethods extends JavadocTester { + + public static void main(String... args) throws Exception { + var tester = new TestDuplicateMethods(); + tester.runTests(); + } + + ToolBox tb = new ToolBox(); + Path src = Path.of("src"); + + + TestDuplicateMethods() throws IOException { + // Diamond class inheritance + tb.writeJavaFiles(src, """ + package p; + interface A { + /** + * JavaDoc for method in interface A. + */ + abstract void testA( ); + }""", """ + package p; + interface B extends A { + /** + * JavaDoc for method in interface B. + */ + abstract void testB( ); + }""", """ + package p; + abstract class C implements A { + /** + * Inherited JavaDoc for method in class C. + */ + public final void testA( ) { + // Do nothing. + } + }""",""" + package p; + public final class D extends C implements B { + /** + * Inherited JavaDoc. + */ + public final void testB() { + // Do nothing. + } + } + """); + + // Mirrors the implementation of StringBuilder + tb.writeJavaFiles(src, + """ + package sb; + public interface I { + /** + * JavaDoc for method in public interface I. + */ + void testI(); + } + """, """ + package sb; + abstract class P implements I { + /** + * Inherited JavaDoc for method in class P. + */ + public final void testI() { + // Do nothing. + } + } + """, """ + package sb; + public class U extends P implements I { + // No overrides + } + """ + ); + + // Mirrors the implementation of HashMap + tb.writeJavaFiles(src, + """ + package hm; + public interface J { + /** + * JavaDoc for method in public interface J. + */ + void testJ(); + } + """, + """ + package hm; + public abstract class PubJ implements J { + /** + * Inherited JavaDoc for method in public abstract class PubJ. + */ + public final void testJ() { + // Do nothing. + } + } + """, + """ + package hm; + public class V extends PubJ implements J { + // No override + } + """ + ); + } + + @Test + public void testDiamondInheritance(Path base) { + javadoc("-d", base.resolve("out").toString(), + "-sourcepath", src.toString(), + "p"); + checkExit(Exit.OK); + checkOutput("p/D.html", true, + """ +
Inherited JavaDoc for method in class C.
+ """, """ +
public final void testA()
+
Inherited JavaDoc for method in class C.
+ """ + ); + + checkOutput("p/D.html", false, """ +
JavaDoc for method in Interface A.
""", """ +
void testA()
+
JavaDoc for method in Interface A.
"""); + + + checkOutput("p/D.html", false, + """ +
JavaDoc for method in interface A.
+ """); + } + + @Test + public void testStringBuilderInheritance(Path base) { + javadoc("-d", base.resolve("out").toString(), + "-sourcepath", src.toString(), + "sb"); + checkExit(Exit.OK); + + checkOutput("sb/U.html", false, + """ +
+

Methods inherited from interface I

+ testI
+ """); + + checkOutput("sb/U.html", true, + """ +

testI

+
+
public final void testI()
+
Inherited JavaDoc for method in class P.
+
+
Specified by:
+
testI in interface I
+
"""); + } + + @Test + public void testHashMapInheritance(Path base) { + javadoc("-d", base.resolve("out").toString(), + "-sourcepath", src.toString(), + "hm"); + checkExit(Exit.OK); + + checkOutput("hm/V.html", false, + """ +
+

Methods inherited from interface J

+ testJ
"""); + + checkOutput("hm/V.html", true, + """ +
+

Methods inherited from class PubJ

+ testJ
+ """); + } +}