check-flagged-apis: add more details to Symbol class

Change Symbol from a wrapper around a String to a more fleshed out data
class; symbols now encode if they represent a class, or a class member
(including a reference to the containing class).

Bug: 334870672
Test: atest --host check-flagged-apis-test
Test: croot && ./build/tools/check-flagged-apis/check-flagged-apis.sh # with and without this CL; the output should be the same
Change-Id: I003535c721c45d559d00fb3e008325e1db0e18c0
diff --git a/tools/check-flagged-apis/src/com/android/checkflaggedapis/Main.kt b/tools/check-flagged-apis/src/com/android/checkflaggedapis/Main.kt
index 4e73357..4ff9880 100644
--- a/tools/check-flagged-apis/src/com/android/checkflaggedapis/Main.kt
+++ b/tools/check-flagged-apis/src/com/android/checkflaggedapis/Main.kt
@@ -54,29 +54,41 @@
  *
  * 1. https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3.2
  */
-@JvmInline
-internal value class Symbol(val name: String) {
+internal sealed class Symbol {
   companion object {
     private val FORBIDDEN_CHARS = listOf('#', '$', '.')
 
-    /** Create a new Symbol from a String that may include delimiters other than dot. */
-    fun create(name: String): Symbol {
-      var sanitizedName = name
+    fun createClass(clazz: String): Symbol {
+      return ClassSymbol(toInternalFormat(clazz))
+    }
+
+    fun createField(clazz: String, field: String): Symbol {
+      require(!field.contains("(") && !field.contains(")"))
+      return MemberSymbol(toInternalFormat(clazz), toInternalFormat(field))
+    }
+
+    fun createMethod(clazz: String, method: String): Symbol {
+      return MemberSymbol(toInternalFormat(clazz), toInternalFormat(method))
+    }
+
+    protected fun toInternalFormat(name: String): String {
+      var internalName = name
       for (ch in FORBIDDEN_CHARS) {
-        sanitizedName = sanitizedName.replace(ch, '/')
+        internalName = internalName.replace(ch, '/')
       }
-      return Symbol(sanitizedName)
+      return internalName
     }
   }
 
-  init {
-    require(!name.isEmpty()) { "empty string" }
-    for (ch in FORBIDDEN_CHARS) {
-      require(!name.contains(ch)) { "$name: contains $ch" }
-    }
-  }
+  abstract fun toPrettyString(): String
+}
 
-  override fun toString(): String = name.toString()
+internal data class ClassSymbol(val clazz: String) : Symbol() {
+  override fun toPrettyString(): String = "$clazz"
+}
+
+internal data class MemberSymbol(val clazz: String, val member: String) : Symbol() {
+  override fun toPrettyString(): String = "$clazz/$member"
 }
 
 /**
@@ -102,7 +114,7 @@
     override val flag: Flag
 ) : ApiError() {
   override fun toString(): String {
-    return "error: enabled @FlaggedApi not present in built artifact: symbol=$symbol flag=$flag"
+    return "error: enabled @FlaggedApi not present in built artifact: symbol=${symbol.toPrettyString()} flag=$flag"
   }
 }
 
@@ -111,14 +123,14 @@
     override val flag: Flag
 ) : ApiError() {
   override fun toString(): String {
-    return "error: disabled @FlaggedApi is present in built artifact: symbol=$symbol flag=$flag"
+    return "error: disabled @FlaggedApi is present in built artifact: symbol=${symbol.toPrettyString()} flag=$flag"
   }
 }
 
 internal data class UnknownFlagError(override val symbol: Symbol, override val flag: Flag) :
     ApiError() {
   override fun toString(): String {
-    return "error: unknown flag: symbol=$symbol flag=$flag"
+    return "error: unknown flag: symbol=${symbol.toPrettyString()} flag=$flag"
   }
 }
 
@@ -183,29 +195,28 @@
       object : BaseItemVisitor() {
         override fun visitClass(cls: ClassItem) {
           getFlagOrNull(cls)?.let { flag ->
-            val symbol = Symbol.create(cls.baselineElementId())
+            val symbol = Symbol.createClass(cls.baselineElementId())
             output.add(Pair(symbol, flag))
           }
         }
 
         override fun visitField(field: FieldItem) {
           getFlagOrNull(field)?.let { flag ->
-            val symbol = Symbol.create(field.baselineElementId())
+            val symbol =
+                Symbol.createField(field.containingClass().baselineElementId(), field.name())
             output.add(Pair(symbol, flag))
           }
         }
 
         override fun visitMethod(method: MethodItem) {
           getFlagOrNull(method)?.let { flag ->
-            val name = buildString {
-              append(method.containingClass().qualifiedName())
-              append(".")
+            val methodName = buildString {
               append(method.name())
               append("(")
               method.parameters().joinTo(this, separator = "") { it.type().internalName() }
               append(")")
             }
-            val symbol = Symbol.create(name)
+            val symbol = Symbol.createMethod(method.containingClass().qualifiedName(), methodName)
             output.add(Pair(symbol, flag))
           }
         }
@@ -246,7 +257,7 @@
         requireNotNull(cls.getAttribute("name")) {
           "Bad XML: <class> element without name attribute"
         }
-    output.add(Symbol.create(className.replace("/", ".")))
+    output.add(Symbol.createClass(className))
   }
 
   val fields = document.getElementsByTagName("field")
@@ -261,7 +272,7 @@
         requireNotNull(field.getParentNode()?.getAttribute("name")) {
           "Bad XML: top level <field> element"
         }
-    output.add(Symbol.create("${className.replace("/", ".")}.$fieldName"))
+    output.add(Symbol.createField(className, fieldName))
   }
 
   val methods = document.getElementsByTagName("method")
@@ -285,7 +296,7 @@
     if (methodName == "<init>") {
       methodName = packageAndClassName.split("/").last()
     }
-    output.add(Symbol.create("$packageAndClassName/$methodName($methodArgs)"))
+    output.add(Symbol.createMethod(packageAndClassName, "$methodName($methodArgs)"))
   }
 
   return output