Enable merging of partial signature files am: 23df0a11a2 am: e2e98838ca am: f3d7c9144d am: 9d95426825
Original change: https://android-review.googlesource.com/c/platform/tools/metalava/+/2380672
Change-Id: I48f638e6edf9d4ef0414d7de05b8881a4ec535d8
Signed-off-by: Automerger Merge Worker <[email protected]>
diff --git a/src/main/java/com/android/tools/metalava/model/text/ApiFile.kt b/src/main/java/com/android/tools/metalava/model/text/ApiFile.kt
index 8fe3dde..20aaa97 100644
--- a/src/main/java/com/android/tools/metalava/model/text/ApiFile.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/ApiFile.kt
@@ -276,9 +276,6 @@
assertIdent(tokenizer, token)
val name: String = token
qualifiedName = qualifiedName(pkg.name(), name)
- if (api.findClass(qualifiedName) != null) {
- throw ApiParseException("Duplicate class found: $qualifiedName", tokenizer)
- }
val typeInfo = api.obtainTypeFromString(qualifiedName)
// Simple type info excludes the package name (but includes enclosing class names)
var rawName = name
@@ -287,11 +284,22 @@
rawName = rawName.substring(0, variableIndex)
}
token = tokenizer.requireToken()
- cl = TextClassItem(
+ val cls = TextClassItem(
api, tokenizer.pos(), modifiers, isInterface, isEnum, isAnnotation,
typeInfo.toErasedTypeString(null), typeInfo.qualifiedTypeName(),
rawName, annotations
)
+ cl = when (val foundClass = api.findClass(qualifiedName)) {
+ null -> cls
+ else -> {
+ if (!foundClass.isCompatible(cls)) {
+ throw ApiParseException("Incompatible $foundClass definitions")
+ } else {
+ foundClass
+ }
+ }
+ }
+
cl.setContainingPackage(pkg)
cl.setTypeInfo(typeInfo)
cl.deprecated = modifiers.isDeprecated()
@@ -556,7 +564,9 @@
if (";" != token) {
throw ApiParseException("expected ; found $token", tokenizer)
}
- cl.addMethod(method)
+ if (!cl.methods().contains(method)) {
+ cl.addMethod(method)
+ }
}
private fun mergeAnnotations(
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 c0a133f..98a3257 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
@@ -225,6 +225,22 @@
innerClasses.add(cls)
}
+ fun isCompatible(cls: TextClassItem): Boolean {
+ if (this === cls) {
+ return true
+ }
+ if (fullName != cls.fullName) {
+ return false
+ }
+
+ return modifiers.toString() == cls.modifiers.toString() &&
+ isInterface == cls.isInterface &&
+ isEnum == cls.isEnum &&
+ isAnnotation == cls.isAnnotation &&
+ superClass == cls.superClass &&
+ allInterfaces().toSet() == cls.allInterfaces().toSet()
+ }
+
override fun filteredSuperClassType(predicate: Predicate<Item>): TypeItem? {
// No filtering in signature files: we assume signature APIs
// have already been filtered and all items should match.
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 815d688..94ed894 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
@@ -104,7 +104,9 @@
if (!mClassToInterface.containsKey(classInfo)) {
mClassToInterface[classInfo] = ArrayList()
}
- mClassToInterface[classInfo]?.add(iface)
+ mClassToInterface[classInfo]?.let {
+ if (!it.contains(iface)) it.add(iface)
+ }
}
fun implementsInterface(classInfo: TextClassItem, iface: String): Boolean {
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextPackageItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextPackageItem.kt
index d3bfc4d..7b126c3 100644
--- a/src/main/java/com/android/tools/metalava/model/text/TextPackageItem.kt
+++ b/src/main/java/com/android/tools/metalava/model/text/TextPackageItem.kt
@@ -31,10 +31,17 @@
private val classes = ArrayList<TextClassItem>(100)
+ private val classesNames = HashSet<String>(100)
+
fun name() = name
fun addClass(classInfo: TextClassItem) {
+ val classFullName = classInfo.fullName()
+ if (classFullName in classesNames) {
+ return
+ }
classes.add(classInfo)
+ classesNames.add(classFullName)
}
internal fun pruneClassList() {
diff --git a/src/test/java/com/android/tools/metalava/ApiFileTest.kt b/src/test/java/com/android/tools/metalava/ApiFileTest.kt
index e3c571c..23944c6 100644
--- a/src/test/java/com/android/tools/metalava/ApiFileTest.kt
+++ b/src/test/java/com/android/tools/metalava/ApiFileTest.kt
@@ -3802,7 +3802,7 @@
}
@Test
- fun `Test cannot merging API signature files with duplicate class`() {
+ fun `Test can merge API signature files with duplicate class`() {
val source1 = """
package Test.pkg {
public final class Class1 {
@@ -3817,9 +3817,38 @@
}
}
"""
+ val expected = """
+ package Test.pkg {
+ public final class Class1 {
+ method public void method1();
+ }
+ }
+ """
check(
signatureSources = arrayOf(source1, source2),
- expectedFail = "Aborting: Unable to parse signature file: TESTROOT/project/load-api2.txt:2: Duplicate class found: Test.pkg.Class1"
+ api = expected
+ )
+ }
+
+ @Test
+ fun `Test cannot merge API signature files with incompatible class definitions`() {
+ val source1 = """
+ package Test.pkg {
+ public class Class1 {
+ method public void method2();
+ }
+ }
+ """
+ val source2 = """
+ package Test.pkg {
+ public final class Class1 {
+ method public void method1();
+ }
+ }
+ """
+ check(
+ signatureSources = arrayOf(source1, source2),
+ expectedFail = "Aborting: Unable to parse signature file: Incompatible class Test.pkg.Class1 definitions"
)
}
diff --git a/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt b/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt
index c206082..4a5c6d1 100644
--- a/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt
+++ b/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt
@@ -3355,7 +3355,7 @@
// Regression test for 130567941
check(
expectedIssues = """
- TESTROOT/load-api.txt:7: error: Method test.pkg.sample.SampleClass.convert has changed return type from Number to java.lang.Number [ChangedType]
+ TESTROOT/load-api.txt:7: error: Method test.pkg.sample.SampleClass.convert1 has changed return type from Number to java.lang.Number [ChangedType]
""",
inputKotlinStyleNulls = true,
outputKotlinStyleNulls = true,
@@ -3364,7 +3364,7 @@
package test.pkg.sample {
public abstract class SampleClass {
method public <Number> Number! convert(Number);
- method public <Number> Number! convert(Number);
+ method public <Number> Number! convert1(Number);
}
}
""",
@@ -3375,7 +3375,7 @@
// Here the generic type parameter applies to both the function argument and the function return type
method public <Number> Number! convert(Number);
// Here the generic type parameter applies to the function argument but not the function return type
- method public <Number> java.lang.Number! convert(Number);
+ method public <Number> java.lang.Number! convert1(Number);
}
}
"""