Add overridden methods to text codebase

Currently, Metalava removes overridden methods in API text files if it
has equal signatures as the abstract method. However, not implementing
parent classes' abstract methods leads to compile error (not overriding
abstract method error). Thus, iterate through hierarchy and find any
unimplemented abstract methods in the hierarchy chain and add them to
the text based codebase.

Test: ./gradlew && m && m --build-from-text-stub
Bug: 269537741
Change-Id: I18c1116f6ab80355c0e5e31bf04b34508e8c6619
diff --git a/src/main/java/com/android/tools/metalava/model/ClassItem.kt b/src/main/java/com/android/tools/metalava/model/ClassItem.kt
index 920c19d..073cf76 100644
--- a/src/main/java/com/android/tools/metalava/model/ClassItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/ClassItem.kt
@@ -230,6 +230,10 @@
         return qualifiedName() == JAVA_LANG_OBJECT
     }
 
+    fun isAbstractClass(): Boolean {
+        return this.modifiers.isAbstract()
+    }
+
     // Mutation APIs: Used to "fix up" the API hierarchy (in [ApiAnalyzer]) to only expose
     // visible parts of the API)
 
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextClassItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextClassItem.kt
index 859b45f..7c3a121 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextClassItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextClassItem.kt
@@ -293,10 +293,11 @@
 
     private var allSuperClassesAndInterfaces: List<TextClassItem>? = null
 
-    // Returns all super classes and interfaces in the class hierarchy the class item inherits.
-    // The returned list is sorted by the proximity of the classes to the class item in the hierarchy chain.
-    // If an interface appears multiple time in the hierarchy chain,
-    // it is ordered based on the furthest distance to the class item.
+    /** Returns all super classes and interfaces in the class hierarchy the class item inherits.
+     * The returned list is sorted by the proximity of the classes to the class item in the hierarchy chain.
+     * If an interface appears multiple time in the hierarchy chain,
+     * it is ordered based on the furthest distance to the class item.
+     */
     fun getAllSuperClassesAndInterfaces(): List<TextClassItem> {
         allSuperClassesAndInterfaces?.let { return it }
 
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextCodebase.kt b/src/main/java/com/android/tools/metalava/model/text/TextCodebase.kt
index b816632..384f8f3 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextCodebase.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextCodebase.kt
@@ -258,6 +258,37 @@
                     }
                 }
             }
+
+            // If class is a concrete class, iterate through all hierarchy and
+            // find all missing abstract methods.
+            // Only add methods that are not implemented in the hierarchy and not included
+            else if (!cl.isAbstractClass() && !cl.isEnum()) {
+                val superMethodsToBeOverridden = mutableListOf<TextMethodItem>()
+                val hierarchyClassesList = cl.getAllSuperClassesAndInterfaces().toMutableList()
+                while (hierarchyClassesList.isNotEmpty()) {
+                    val ancestorClass = hierarchyClassesList.removeLast()
+                    val abstractMethods = ancestorClass.methods().filter { it.modifiers.isAbstract() }
+                    for (method in abstractMethods) {
+                        // We do not compare this against all ancestors of cl,
+                        // because an abstract method cannot be overridden at its ancestor class.
+                        // Thus, we compare against hierarchyClassesList.
+                        if (hierarchyClassesList.all { !it.containsMethodInClassContext(method) } &&
+                            !cl.containsMethodInClassContext(method)
+                        ) {
+                            superMethodsToBeOverridden.add(method as TextMethodItem)
+                        }
+                    }
+                }
+                for (superMethod in superMethodsToBeOverridden) {
+                    // MethodItem.duplicate() sets the containing class of
+                    // the duplicated method item as the input parameter.
+                    // Thus, the method items to be overridden are duplicated here after the
+                    // ancestor classes iteration so that the method items are correctly compared.
+                    val m = superMethod.duplicate(cl) as TextMethodItem
+                    m.modifiers.setAbstract(false)
+                    cl.addMethod(m)
+                }
+            }
         }
     }
 
diff --git a/src/test/java/com/android/tools/metalava/stub/StubsTest.kt b/src/test/java/com/android/tools/metalava/stub/StubsTest.kt
index 54cd9d8..5fddbfc 100644
--- a/src/test/java/com/android/tools/metalava/stub/StubsTest.kt
+++ b/src/test/java/com/android/tools/metalava/stub/StubsTest.kt
@@ -1170,6 +1170,88 @@
     }
 
     @Test
+    fun `Check overridden method added for complex hierarchy`() {
+        checkStubs(
+            sourceFiles = arrayOf(
+                java(
+                    """
+                package test.pkg;
+                public final class A extends C implements B<String> {
+                    @Override public void method2() { }
+                }
+                """
+                ),
+                java(
+                    """
+                package test.pkg;
+                public interface B<T> {
+                    void method1(T arg1);
+                }
+                """
+                ),
+                java(
+                    """
+                package test.pkg;
+                public abstract class C extends D {
+                    public abstract void method2();
+                }
+                """
+                ),
+                java(
+                    """
+                package test.pkg;
+                public abstract class D implements B<String> {
+                    @Override public void method1(String arg1) { }
+                }
+                """
+                )
+            ),
+            stubFiles = arrayOf(
+                java(
+                    """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public final class A extends test.pkg.C implements test.pkg.B<java.lang.String> {
+                public A() { throw new RuntimeException("Stub!"); }
+                public void method2() { throw new RuntimeException("Stub!"); }
+                }
+                """
+                ),
+                java(
+                    """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public interface B<T> {
+                public void method1(T arg1);
+                }
+                """
+                ),
+                java(
+                    """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public abstract class C extends test.pkg.D {
+                public C() { throw new RuntimeException("Stub!"); }
+                public abstract void method2();
+                }
+                """
+                ),
+                java(
+                    """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public abstract class D implements test.pkg.B<java.lang.String> {
+                public D() { throw new RuntimeException("Stub!"); }
+                public void method1(java.lang.String arg1) { throw new RuntimeException("Stub!"); }
+                }
+                """
+                )
+            ),
+            checkTextStubEquivalence = true
+        )
+    }
+
+    @Test
     fun `Check generating classes with generics`() {
         checkStubs(
             sourceFiles = arrayOf(