Merge "Reenable running metalava integration builds"
diff --git a/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt b/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt
index f9be836..18efeae 100644
--- a/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt
+++ b/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt
@@ -402,14 +402,20 @@
                         item1.qualifiedName().compareTo((item2 as ClassItem).qualifiedName())
                     }
                     is MethodItem -> {
+                        // Try to incrementally match aspects of the method until you can conclude
+                        // whether they are the same or different.
+                        // delta is 0 when the methods are the same, else not 0
+                        // Start by comparing the names
                         var delta = item1.name().compareTo((item2 as MethodItem).name())
                         if (delta == 0) {
+                            // If the names are the same then compare the number of parameters
                             val parameters1 = item1.parameters()
                             val parameters2 = item2.parameters()
                             val parameterCount1 = parameters1.size
                             val parameterCount2 = parameters2.size
                             delta = parameterCount1 - parameterCount2
                             if (delta == 0) {
+                                // If the parameter count is the same, compare the parameter types
                                 for (i in 0 until parameterCount1) {
                                     val parameter1 = parameters1[i]
                                     val parameter2 = parameters2[i]
@@ -417,7 +423,7 @@
                                     val type2 = parameter2.type().toTypeString(context = parameter2)
                                     delta = type1.compareTo(type2)
                                     if (delta != 0) {
-                                        // Try a little harder:
+                                        // If the parameter types aren't the same, try a little harder:
                                         //  (1) treat varargs and arrays the same, and
                                         //  (2) drop java.lang. prefixes from comparisons in wildcard
                                         //      signatures since older signature files may have removed
@@ -426,15 +432,21 @@
                                         val simpleType2 = parameter2.type().toCanonicalType(parameter2)
                                         delta = simpleType1.compareTo(simpleType2)
                                         if (delta != 0) {
-                                            // Special case: Kotlin coroutines
+                                            // If still not the same, check the special case for
+                                            // Kotlin coroutines: It's possible one has "experimental"
+                                            // when fully qualified while the other does not.
+                                            // We treat these the same, so strip the prefix and strip
+                                            // "experimental", then compare.
                                             if (simpleType1.startsWith("kotlin.coroutines.") && simpleType2.startsWith("kotlin.coroutines.")) {
                                                 val t1 = simpleType1.removePrefix("kotlin.coroutines.").removePrefix("experimental.")
                                                 val t2 = simpleType2.removePrefix("kotlin.coroutines.").removePrefix("experimental.")
                                                 delta = t1.compareTo(t2)
                                                 if (delta != 0) {
+                                                    // They're not the same
                                                     break
                                                 }
                                             } else {
+                                                // They're not the same
                                                 break
                                             }
                                         }
@@ -442,6 +454,7 @@
                                 }
                             }
                         }
+                        // The method names are different, return the result of the compareTo
                         delta
                     }
                     is FieldItem -> {
diff --git a/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt b/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt
index 326a777..a6bdeda 100644
--- a/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt
+++ b/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt
@@ -503,7 +503,9 @@
                 // and (b) the method is not already inferred to be 'final' by virtue of its class.
                 if (!old.isEffectivelyFinal() && new.isEffectivelyFinal()) {
                     report(
-                        Issues.ADDED_FINAL, new, "${describe(new, capitalize = true)} has added 'final' qualifier"
+                        Issues.ADDED_FINAL,
+                        new,
+                        "${describe(new, capitalize = true)} has added 'final' qualifier"
                     )
                 }
             }
diff --git a/src/main/java/com/android/tools/metalava/model/MemberItem.kt b/src/main/java/com/android/tools/metalava/model/MemberItem.kt
index f96d49f..95985f8 100644
--- a/src/main/java/com/android/tools/metalava/model/MemberItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/MemberItem.kt
@@ -35,6 +35,8 @@
      * be final because its containing class is final
      */
     fun isEffectivelyFinal(): Boolean {
-        return modifiers.isFinal() || containingClass().modifiers.isFinal()
+        return modifiers.isFinal() ||
+            containingClass().modifiers.isFinal() ||
+            containingClass().modifiers.isSealed()
     }
 }
diff --git a/src/main/resources/version.properties b/src/main/resources/version.properties
index 0302221..9829871 100644
--- a/src/main/resources/version.properties
+++ b/src/main/resources/version.properties
@@ -2,4 +2,4 @@
 # Version definition
 # This file is read by gradle build scripts, but also packaged with metalava
 # as a resource for the Version classes to read.
-metalavaVersion=1.0.0-alpha05
+metalavaVersion=1.0.0-alpha06
diff --git a/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt b/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt
index 499ca7f..678dea6 100644
--- a/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt
+++ b/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt
@@ -3826,6 +3826,26 @@
                     method default public void bar(Int);
                     }
                   }
+              """
+        )
+    }
+
+    fun `Allow change from non-final to final in sealed class`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  sealed class Foo {
+                    method final public void bar(Int);
+                  }
+                }
+            """,
+            format = FileFormat.V4,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  sealed class Foo {
+                    method public void bar(Int);
+                  }
+                }
             """
         )
     }
diff --git a/src/test/java/com/android/tools/metalava/binarycompatibility/BinaryCompatibilityClassMethodsAndConstructors.kt b/src/test/java/com/android/tools/metalava/binarycompatibility/BinaryCompatibilityClassMethodsAndConstructors.kt
new file mode 100644
index 0000000..b7abbf3
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/binarycompatibility/BinaryCompatibilityClassMethodsAndConstructors.kt
@@ -0,0 +1,622 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.metalava.binarycompatibility
+
+import com.android.tools.metalava.DriverTest
+import org.junit.Test
+
+class BinaryCompatibilityClassMethodsAndConstructors : DriverTest() {
+    @Test
+    fun `Change method name`() {
+        check(
+            expectedIssues = """
+                TESTROOT/released-api.txt:3: error: Removed method test.pkg.Foo.bar(Int) [RemovedMethod]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method public void baz(Int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method public void bar(Int);
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    // Note: This is reversed from the eclipse wiki because of kotlin named parameters
+    fun `Change formal parameter name (Incompatible)`() {
+        check(
+            expectedIssues = """
+                TESTROOT/load-api.txt:3: error: Attempted to change parameter name from bread to toast in method test.pkg.Foo.bar [ParameterNameChange]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method public void bar(Int toast);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method public void bar(Int bread);
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Add or delete formal parameter (Incompatible)`() {
+        check(
+            expectedIssues = """
+                TESTROOT/released-api.txt:3: error: Removed method test.pkg.Foo.bar(Int) [RemovedMethod]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method public void bar();
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method public void bar(Int);
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Change type of a formal parameter (Incompatible)`() {
+        check(
+            expectedIssues = """
+                TESTROOT/released-api.txt:3: error: Removed method test.pkg.Foo.bar(Int) [RemovedMethod]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method public void bar(Float);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method public void bar(Int);
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Change result type (including void) (Incompatible)`() {
+        check(
+            expectedIssues = """
+                TESTROOT/load-api.txt:3: error: Method test.pkg.Foo.bar has changed return type from void to Int [ChangedType]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method public Int bar(Int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method public void bar(Int);
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Add checked exceptions thrown (Incompatible)`() {
+        check(
+            expectedIssues = """
+                TESTROOT/load-api.txt:3: error: Method test.pkg.Foo.bar added thrown exception java.lang.Throwable [ChangedThrows]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method public void bar(Int) throws java.lang.Throwable;
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method public void bar(Int);
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Delete checked exceptions thrown (Incompatible)`() {
+        check(
+            expectedIssues = """
+                TESTROOT/load-api.txt:3: error: Method test.pkg.Foo.bar no longer throws exception java.lang.Throwable [ChangedThrows]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method public void bar(Int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method public void bar(Int) throws java.lang.Throwable;
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Re-order list of exceptions thrown (Compatible)`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method public void bar(Int) throws java.lang.Exception, java.lang.Throwable;
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method public void bar(Int) throws java.lang.Throwable, java.lang.Exception;
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    /*
+    Decrease access; that is, from protected access to default or private access,
+    or from public access to protected, default, or private access
+     */
+    fun `Decrease access(Incompatible)`() {
+        check(
+            expectedIssues = """
+               TESTROOT/load-api.txt:3: error: Method test.pkg.Foo.bar changed visibility from public to protected [ChangedScope]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method protected void bar(Int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method public void bar(Int);
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Increase access, that is, from protected access to public access (Compatible)`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method public void bar(Int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method protected void bar(Int);
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Change abstract to non-abstract (Compatible)`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  abstract class Foo {
+                    method public void bar(Int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  abstract class Foo {
+                    method abstract public void bar(Int);
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Change non-abstract to abstract (Incompatible)`() {
+        check(
+            expectedIssues = """
+               TESTROOT/load-api.txt:3: error: Method test.pkg.Foo.bar has changed 'abstract' qualifier [ChangedAbstract]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  abstract class Foo {
+                    method abstract public void bar(Int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  abstract class Foo {
+                    method public void bar(Int);
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Change final to non-final (Compatible)`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method public void bar(Int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method final public void bar(Int);
+                  }
+                }
+            """
+        )
+    }
+
+    @Test
+    fun `Change non-final to final (method not re-implementable) (Compatible)`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  sealed class Foo {
+                    method final public void bar(Int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  sealed class Foo {
+                    method public void bar(Int);
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Change non-final to final (method re-implementable) (Incompatible)`() {
+        check(
+            expectedIssues = """
+               TESTROOT/load-api.txt:3: error: Method test.pkg.Foo.bar has added 'final' qualifier [AddedFinal]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method final public void bar(Int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method public void bar(Int);
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Change static to non-static (Incompatible)`() {
+        check(
+            expectedIssues = """
+                TESTROOT/load-api.txt:3: error: Method test.pkg.Foo.bar has changed 'static' qualifier [ChangedStatic]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method public void bar(Int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method static public void bar(Int);
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Change non-static to static (Incompatible)`() {
+        check(
+            expectedIssues = """
+                TESTROOT/load-api.txt:3: error: Method test.pkg.Foo.bar has changed 'static' qualifier [ChangedStatic]
+            """,
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method static public void bar(Int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method public void bar(Int);
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Change native to non-native (Compatible)`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method native public void bar(Int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method public void bar(Int);
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Change non-native to native (Compatible)`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method public void bar(Int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method native public void bar(Int);
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Change synchronized to non-synchronized (Compatible)`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method public void bar(Int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method synchronized public void bar(Int);
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Change non-synchronized to synchronized (Compatible)`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method synchronized public void bar(Int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method public void bar(Int);
+                  }
+                }
+            """
+        )
+    }
+
+    /*
+    TODO: Fix b/217229076 and uncomment this block of tests
+
+    @Test
+    fun `Add type parameter (existing type parameters) (Incompatible)`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method public <T, K> void bar();
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method public <T> void bar();
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Add type parameter (no existing type parameters) (Compatible)`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method public <T> void bar(Int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method public void bar(Int);
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Delete type parameter (Incompatible)`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method public void bar(Int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method public <T> void bar(Int);
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Re-order type parameters (Incompatible)`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method public <T, K> void bar(Int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method public <K, T> void bar(Int);
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Rename type parameter (Compatible)`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method public <T> void bar(Int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method public <K> void bar(Int);
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Add, delete, or change type bounds of parameter (Incompatible)`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method public <T extends Foo> void bar(Int);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method public <T> void bar(Int);
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Change last parameter from array type T(array) to variable arity T(elipse) (Compatible)`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method public <T> void bar(T...);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method public <T> void bar(T[]);
+                  }
+                }
+            """
+        )
+    }
+    @Test
+    fun `Change last parameter from variable arity T(elipse) to array type T(array) (Incompatible)`() {
+        check(
+            signatureSource = """
+                package test.pkg {
+                  class Foo {
+                    method public <T> void bar(T[]);
+                  }
+                }
+            """,
+            checkCompatibilityApiReleased = """
+                package test.pkg {
+                  class Foo {
+                    method public <T> void bar(T...);
+                  }
+                }
+            """
+        )
+    }
+
+    */
+}