Copy JvmDescriptorUtils and KotlinMetadata utils

This is an almost 1-1 copy of JvmDescriptorUtils and kotlin metadata utils from
room:compiler to xprocessing.
Once room is migrated, they'll be deleted from Room.
Right now these classes are not used but they'll be used in a followup CL when I
add support for executable elements.

Bug: 160322705
Bug: 160323720
Test: compiler-xprocessing tests
Change-Id: I12c3468ac7a69ba0b964700bc8f8343dcd263552
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/kotlin/JvmDescriptorUtils.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/kotlin/JvmDescriptorUtils.kt
new file mode 100644
index 0000000..cabec27
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/kotlin/JvmDescriptorUtils.kt
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2020 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 androidx.room.processing.javac.kotlin
+
+import com.google.auto.common.MoreTypes
+import javax.lang.model.element.Element
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.NestingKind
+import javax.lang.model.element.QualifiedNameable
+import javax.lang.model.element.TypeElement
+import javax.lang.model.element.VariableElement
+import javax.lang.model.type.ArrayType
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.ErrorType
+import javax.lang.model.type.ExecutableType
+import javax.lang.model.type.IntersectionType
+import javax.lang.model.type.NoType
+import javax.lang.model.type.NullType
+import javax.lang.model.type.PrimitiveType
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+import javax.lang.model.type.TypeVariable
+import javax.lang.model.type.UnionType
+import javax.lang.model.type.WildcardType
+import javax.lang.model.util.AbstractTypeVisitor8
+
+/**
+ * Returns the method descriptor of this [ExecutableElement].
+ *
+ * For reference, see the [JVM specification, section 4.3.2](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3.2)
+ */
+internal fun VariableElement.descriptor() = "$simpleName:${asType().descriptor()}"
+
+/**
+ * Returns the method descriptor of this [ExecutableElement].
+ *
+ * For reference, see the [JVM specification, section 4.3.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3.3)
+ */
+internal fun ExecutableElement.descriptor() =
+    "$simpleName${MoreTypes.asExecutable(asType()).descriptor()}"
+
+/**
+ * Returns the name of this [TypeElement] in its "internal form".
+ *
+ * For reference, see the [JVM specification, section 4.2](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.2).
+ */
+internal val Element.internalName: String
+    get() = when (this) {
+        is TypeElement ->
+            when (nestingKind) {
+                NestingKind.TOP_LEVEL ->
+                    qualifiedName.toString().replace('.', '/')
+                NestingKind.MEMBER ->
+                    enclosingElement.internalName + "$" + simpleName
+                NestingKind.LOCAL, NestingKind.ANONYMOUS ->
+                    error("Unsupported nesting $nestingKind")
+                else ->
+                    error("Unsupported, nestingKind == null")
+            }
+        is QualifiedNameable -> qualifiedName.toString().replace('.', '/')
+        else -> simpleName.toString()
+    }
+
+@Suppress("unused")
+internal val NoType.descriptor: String
+    get() = "V"
+
+internal val DeclaredType.descriptor: String
+    get() = "L" + asElement().internalName + ";"
+
+internal val PrimitiveType.descriptor: String
+    get() = when (this.kind) {
+        TypeKind.BYTE -> "B"
+        TypeKind.CHAR -> "C"
+        TypeKind.DOUBLE -> "D"
+        TypeKind.FLOAT -> "F"
+        TypeKind.INT -> "I"
+        TypeKind.LONG -> "J"
+        TypeKind.SHORT -> "S"
+        TypeKind.BOOLEAN -> "Z"
+        else -> error("Unknown primitive type $this")
+    }
+
+internal fun TypeMirror.descriptor(): String = accept(JvmDescriptorTypeVisitor, Unit)
+
+@Suppress("unused")
+internal fun WildcardType.descriptor(): String = ""
+
+// The erasure of a type variable is the erasure of its leftmost bound. - JVM Spec Sec 4.6
+internal fun TypeVariable.descriptor(): String = this.upperBound.descriptor()
+
+// For a type variable with multiple bounds: "the erasure of a type variable is determined by
+// the first type in its bound" - JVM Spec Sec 4.4
+internal fun IntersectionType.descriptor(): String =
+    this.bounds[0].descriptor()
+
+internal fun ArrayType.descriptor(): String =
+    "[" + componentType.descriptor()
+
+internal fun ExecutableType.descriptor(): String {
+    val parameterDescriptors =
+        parameterTypes.joinToString(separator = "") { it.descriptor() }
+    val returnDescriptor = returnType.descriptor()
+    return "($parameterDescriptors)$returnDescriptor"
+}
+
+/**
+ * When applied over a type, it returns either:
+ * + a "field descriptor", for example: `Ljava/lang/Object;`
+ * + a "method descriptor", for example: `(Ljava/lang/Object;)Z`
+ *
+ * The easiest way to use this is through [TypeMirror.descriptor]
+ *
+ * For reference, see the [JVM specification, section 4.3](http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3).
+ */
+@Suppress("DEPRECATION")
+internal object JvmDescriptorTypeVisitor : AbstractTypeVisitor8<String, Unit>() {
+
+    override fun visitNoType(t: NoType, u: Unit): String = t.descriptor
+
+    override fun visitDeclared(t: DeclaredType, u: Unit): String = t.descriptor
+
+    override fun visitPrimitive(t: PrimitiveType, u: Unit): String = t.descriptor
+
+    override fun visitArray(t: ArrayType, u: Unit): String = t.descriptor()
+
+    override fun visitWildcard(t: WildcardType, u: Unit): String = t.descriptor()
+
+    override fun visitExecutable(t: ExecutableType, u: Unit): String = t.descriptor()
+
+    override fun visitTypeVariable(t: TypeVariable, u: Unit): String = t.descriptor()
+
+    override fun visitNull(t: NullType, u: Unit): String = visitUnknown(t, u)
+
+    override fun visitError(t: ErrorType, u: Unit): String = visitUnknown(t, u)
+
+    override fun visitIntersection(t: IntersectionType, u: Unit) = t.descriptor()
+
+    override fun visitUnion(t: UnionType, u: Unit) = visitUnknown(t, u)
+
+    override fun visitUnknown(t: TypeMirror, u: Unit): String = error("Unsupported type $t")
+}
\ No newline at end of file
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/kotlin/KotlinClassMetadataUtils.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/kotlin/KotlinClassMetadataUtils.kt
new file mode 100644
index 0000000..fefd8e0
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/kotlin/KotlinClassMetadataUtils.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2020 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 androidx.room.processing.javac.kotlin
+
+import kotlinx.metadata.ClassName
+import kotlinx.metadata.Flag
+import kotlinx.metadata.Flags
+import kotlinx.metadata.KmClassVisitor
+import kotlinx.metadata.KmConstructorExtensionVisitor
+import kotlinx.metadata.KmConstructorVisitor
+import kotlinx.metadata.KmExtensionType
+import kotlinx.metadata.KmFunctionExtensionVisitor
+import kotlinx.metadata.KmFunctionVisitor
+import kotlinx.metadata.KmValueParameterVisitor
+import kotlinx.metadata.jvm.JvmConstructorExtensionVisitor
+import kotlinx.metadata.jvm.JvmFunctionExtensionVisitor
+import kotlinx.metadata.jvm.JvmMethodSignature
+import kotlinx.metadata.jvm.KotlinClassMetadata
+
+/**
+ * Represents the kotlin metadata of a function
+ */
+internal data class KmFunction(
+    val descriptor: String,
+    private val flags: Flags,
+    val parameters: List<KmValueParameter>
+) {
+    fun isSuspend() = Flag.Function.IS_SUSPEND(flags)
+}
+
+/**
+ * Represents the kotlin metadata of a constructor
+ */
+internal data class KmConstructor(
+    val descriptor: String,
+    private val flags: Flags,
+    val parameters: List<KmValueParameter>
+) {
+    fun isPrimary() = Flag.Constructor.IS_PRIMARY(flags)
+}
+
+/**
+ * Represents the kotlin metadata of a parameter
+ */
+internal data class KmValueParameter(val name: String, private val flags: Flags)
+
+internal fun KotlinClassMetadata.Class.readFunctions(): List<KmFunction> =
+    mutableListOf<KmFunction>().apply { accept(FunctionReader(this)) }
+
+private class FunctionReader(val result: MutableList<KmFunction>) : KmClassVisitor() {
+    override fun visitFunction(flags: Flags, name: String): KmFunctionVisitor? {
+        return object : KmFunctionVisitor() {
+
+            lateinit var descriptor: String
+            val parameters = mutableListOf<KmValueParameter>()
+
+            override fun visitValueParameter(
+                flags: Flags,
+                name: String
+            ): KmValueParameterVisitor? {
+                parameters.add(KmValueParameter(name, flags))
+                return super.visitValueParameter(flags, name)
+            }
+
+            override fun visitExtensions(type: KmExtensionType): KmFunctionExtensionVisitor? {
+                if (type != JvmFunctionExtensionVisitor.TYPE) {
+                    error("Unsupported extension type: $type")
+                }
+                return object : JvmFunctionExtensionVisitor() {
+                    override fun visit(signature: JvmMethodSignature?) {
+                        descriptor = signature!!.asString()
+                    }
+                }
+            }
+
+            override fun visitEnd() {
+                result.add(KmFunction(descriptor, flags, parameters))
+            }
+        }
+    }
+}
+
+internal fun KotlinClassMetadata.Class.readConstructors(): List<KmConstructor> =
+    mutableListOf<KmConstructor>().apply { accept(ConstructorReader(this)) }
+
+private class ConstructorReader(val result: MutableList<KmConstructor>) : KmClassVisitor() {
+    override fun visitConstructor(flags: Flags): KmConstructorVisitor? {
+        return object : KmConstructorVisitor() {
+
+            lateinit var descriptor: String
+            val parameters = mutableListOf<KmValueParameter>()
+
+            override fun visitValueParameter(
+                flags: Flags,
+                name: String
+            ): KmValueParameterVisitor? {
+                parameters.add(KmValueParameter(name, flags))
+                return super.visitValueParameter(flags, name)
+            }
+
+            override fun visitExtensions(type: KmExtensionType): KmConstructorExtensionVisitor? {
+                if (type != JvmConstructorExtensionVisitor.TYPE) {
+                    error("Unsupported extension type: $type")
+                }
+                return object : JvmConstructorExtensionVisitor() {
+                    override fun visit(signature: JvmMethodSignature?) {
+                        descriptor = signature!!.asString()
+                    }
+                }
+            }
+
+            override fun visitEnd() {
+                result.add(KmConstructor(descriptor, flags, parameters))
+            }
+        }
+    }
+}
+
+internal fun KotlinClassMetadata.Class.isObject(): Boolean = ObjectReader().let {
+    this@isObject.accept(it)
+    it.isObject
+}
+
+private class ObjectReader() : KmClassVisitor() {
+    var isObject: Boolean = false
+    override fun visit(flags: Flags, name: ClassName) {
+        isObject = Flag.Class.IS_OBJECT(flags)
+    }
+}
\ No newline at end of file
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/kotlin/KotlinMetadataElement.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/kotlin/KotlinMetadataElement.kt
new file mode 100644
index 0000000..5e822a2
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/kotlin/KotlinMetadataElement.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2020 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 androidx.room.processing.javac.kotlin
+
+import kotlinx.metadata.jvm.KotlinClassHeader
+import kotlinx.metadata.jvm.KotlinClassMetadata
+import javax.lang.model.element.Element
+import javax.lang.model.element.ExecutableElement
+
+/**
+ * Utility class for processors that wants to run kotlin specific code.
+ */
+internal class KotlinMetadataElement(
+    val element: Element,
+    private val classMetadata: KotlinClassMetadata.Class
+) {
+
+    private val functionList: List<KmFunction> by lazy { classMetadata.readFunctions() }
+    private val constructorList: List<KmConstructor> by lazy { classMetadata.readConstructors() }
+
+    private val ExecutableElement.descriptor: String
+        get() = descriptor()
+
+    /**
+     * Returns the parameter names of the function or constructor if all have names embedded in the
+     * metadata.
+     */
+    fun getParameterNames(method: ExecutableElement): List<String>? {
+        val methodSignature = method.descriptor
+        val paramList =
+            functionList.firstOrNull { it.descriptor == methodSignature }?.parameters
+                ?: constructorList.firstOrNull { it.descriptor == methodSignature }?.parameters
+        return paramList?.map { it.name }
+    }
+
+    /**
+     * Finds the primary constructor descriptor of the class.
+     */
+    fun findPrimaryConstructorSignature() = constructorList.first { it.isPrimary() }.descriptor
+
+    /**
+     * Checks if a method is a suspend function.
+     */
+    fun isSuspendFunction(method: ExecutableElement) = functionList.firstOrNull {
+        it.descriptor == method.descriptor
+    }?.isSuspend() ?: false
+
+    fun isObject(): Boolean = classMetadata.isObject()
+
+    companion object {
+        /**
+         * Creates a [KotlinMetadataElement] for the given element if it contains Kotlin metadata,
+         * otherwise this method returns null.
+         *
+         * Usually the [element] passed must represent a class. For example, if kotlin metadata is
+         * desired for a method, then the containing class should be used as parameter.
+         */
+        fun createFor(element: Element): KotlinMetadataElement? {
+            val metadata = getMetadataAnnotation(element)?.run {
+                KotlinClassHeader(
+                    kind = kind,
+                    metadataVersion = metadataVersion,
+                    bytecodeVersion = bytecodeVersion,
+                    data1 = data1,
+                    data2 = data2,
+                    extraString = extraString,
+                    packageName = packageName,
+                    extraInt = extraInt
+                ).let {
+                    // TODO: Support more metadata kind (file facade, synthetic class, etc...)
+                    KotlinClassMetadata.read(it) as? KotlinClassMetadata.Class
+                }
+            }
+            return if (metadata != null) {
+                KotlinMetadataElement(element, metadata)
+            } else {
+                null
+            }
+        }
+
+        /**
+         * Search for Kotlin's Metadata annotation across the element's hierarchy.
+         */
+        private fun getMetadataAnnotation(element: Element?): Metadata? =
+            if (element != null) {
+                element.getAnnotation(Metadata::class.java)
+                    ?: getMetadataAnnotation(element.enclosingElement)
+            } else {
+                null
+            }
+    }
+}
\ No newline at end of file
diff --git a/room/compiler-xprocessing/src/test/java/androidx/room/processing/javac/kotlin/JvmDescriptorUtilsTest.kt b/room/compiler-xprocessing/src/test/java/androidx/room/processing/javac/kotlin/JvmDescriptorUtilsTest.kt
new file mode 100644
index 0000000..4bb6c67
--- /dev/null
+++ b/room/compiler-xprocessing/src/test/java/androidx/room/processing/javac/kotlin/JvmDescriptorUtilsTest.kt
@@ -0,0 +1,369 @@
+/*
+ * Copyright 2020 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 androidx.room.processing.javac.kotlin
+
+import com.google.auto.common.MoreElements
+import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import javax.annotation.processing.AbstractProcessor
+import javax.annotation.processing.RoundEnvironment
+import javax.lang.model.element.ElementKind.CONSTRUCTOR
+import javax.lang.model.element.ElementKind.FIELD
+import javax.lang.model.element.ElementKind.METHOD
+import javax.lang.model.element.TypeElement
+import javax.tools.JavaFileObject
+
+@RunWith(JUnit4::class)
+class JvmDescriptorUtilsTest {
+
+    private val describeAnnotation =
+        """
+        package androidx.room.test;
+
+        import java.lang.annotation.ElementType;
+        import java.lang.annotation.Target;
+
+        @Target({ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR})
+        public @interface Describe { }
+        """.toJFO("androidx.room.test.Describe")
+
+    @Test
+    fun descriptor_method_simple() {
+        singleRun(
+            """
+            package androidx.room.test;
+
+            public class DummyClass {
+                @Describe
+                public void emptyMethod() {
+                }
+            }
+            """.toJFO("androidx.room.test.DummyClass")
+        ) { descriptors ->
+            assertThat(descriptors.first())
+                .isEqualTo("emptyMethod()V")
+        }
+    }
+
+    @Test
+    fun descriptor_field() {
+        singleRun(
+            """
+            package androidx.room.test;
+
+            import java.util.List;
+
+            class DummyClass<T> {
+                @Describe
+                int field1;
+
+                @Describe
+                String field2;
+
+                @Describe
+                T field3;
+
+                @Describe
+                List<String> field4;
+            }
+            """.toJFO("androidx.room.test.DummyClass")
+        ) { descriptors ->
+            assertThat(descriptors).isEqualTo(
+                setOf(
+                    "field1:I",
+                    "field2:Ljava/lang/String;",
+                    "field3:Ljava/lang/Object;",
+                    "field4:Ljava/util/List;"
+                )
+            )
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun descriptor_method_erasured() {
+        singleRun(
+            """
+            package androidx.room.test;
+
+            import java.util.ArrayList;
+            import java.util.Collection;
+            import java.util.List;
+            import java.util.Map;
+
+            class DummyClass<T> {
+                @Describe
+                void method1(T something) { }
+
+                @Describe
+                T method2() { return null; }
+
+                @Describe
+                List<? extends String> method3() { return null; }
+
+                @Describe
+                Map<T, String> method4() { return null; }
+
+                @Describe
+                ArrayList<Map<T, String>> method5() { return null; }
+
+                @Describe
+                static <I, O extends I> O method6(I input) { return null; }
+
+                @Describe
+                static <I, O extends String> O method7(I input) { return null; }
+
+                @Describe
+                static <P extends Collection & Comparable> P method8() { return null; }
+
+                @Describe
+                static <P extends String & List<Character>> P method9() { return null; }
+            }
+            """.toJFO("androidx.room.test.DummyClass")
+        ) { descriptors ->
+            assertThat(descriptors).isEqualTo(
+                setOf(
+                    "method1(Ljava/lang/Object;)V",
+                    "method2()Ljava/lang/Object;",
+                    "method3()Ljava/util/List;",
+                    "method4()Ljava/util/Map;",
+                    "method5()Ljava/util/ArrayList;",
+                    "method6(Ljava/lang/Object;)Ljava/lang/Object;",
+                    "method7(Ljava/lang/Object;)Ljava/lang/String;",
+                    "method8()Ljava/util/Collection;",
+                    "method9()Ljava/lang/String;"
+                )
+            )
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun descriptor_method_primitiveParams() {
+        singleRun(
+            """
+            package androidx.room.test;
+
+            class DummyClass {
+                @Describe
+                void method1(boolean yesOrNo, int number) { }
+
+                @Describe
+                byte method2(char letter) { return 0; }
+
+                @Describe
+                void method3(double realNumber1, float realNummber2) { }
+
+                @Describe
+                void method4(long bigNumber, short littlerNumber) { }
+            }
+            """.toJFO("androidx.room.test.DummyClass")
+        ) { descriptors ->
+            assertThat(descriptors)
+                .isEqualTo(setOf("method1(ZI)V", "method2(C)B", "method3(DF)V", "method4(JS)V"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun descriptor_method_classParam_javaTypes() {
+        singleRun(
+            """
+            package androidx.room.test;
+
+            import java.util.ArrayList;
+            import java.util.List;
+            import java.util.Map;
+
+            class DummyClass {
+                @Describe
+                void method1(Object something) { }
+
+                @Describe
+                Object method2() { return null; }
+
+                @Describe
+                List<String> method3(ArrayList<Integer> list) { return null; }
+
+                @Describe
+                Map<String, Object> method4() { return null; }
+            }
+            """.toJFO("androidx.room.test.DummyClass")
+        ) { descriptors ->
+            assertThat(descriptors).isEqualTo(
+                setOf(
+                    "method1(Ljava/lang/Object;)V",
+                    "method2()Ljava/lang/Object;",
+                    "method3(Ljava/util/ArrayList;)Ljava/util/List;",
+                    "method4()Ljava/util/Map;"
+                )
+            )
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun descriptor_method_classParam_testClass() {
+        val extraJfo =
+            """
+            package androidx.room.test;
+
+            class DataClass { }
+            """.toJFO("androidx.room.test.DataClass")
+
+        singleRun(
+            """
+            package androidx.room.test;
+
+            class DummyClass {
+                @Describe
+                void method1(DataClass data) { }
+
+                @Describe
+                DataClass method2() { return null; }
+            }
+            """.toJFO("androidx.room.test.DummyClass"), extraJfo
+        ) { descriptors ->
+            assertThat(descriptors).isEqualTo(
+                setOf(
+                    "method1(Landroidx/room/test/DataClass;)V",
+                    "method2()Landroidx/room/test/DataClass;"
+                )
+            )
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun descriptor_method_classParam_innerTestClass() {
+        val extraJfo =
+            """
+            package androidx.room.test;
+
+            class DataClass {
+
+                class MemberInnerData { }
+
+                static class StaticInnerData { }
+
+                enum EnumData {
+                    VALUE1, VALUE2
+                }
+            }
+            """.toJFO("androidx.room.test.DataClass")
+
+        singleRun(
+            """
+            package androidx.room.test;
+
+            class DummyClass {
+                @Describe
+                void method1(DataClass.MemberInnerData data) { }
+
+                @Describe
+                void method2(DataClass.StaticInnerData data) { }
+
+                @Describe
+                void method3(DataClass.EnumData enumData) { }
+
+                @Describe
+                DataClass.StaticInnerData method4() { return null; }
+            }
+            """.toJFO("androidx.room.test.DummyClass"), extraJfo
+        ) { descriptors ->
+            assertThat(descriptors).isEqualTo(
+                setOf(
+                    "method1(Landroidx/room/test/DataClass\$MemberInnerData;)V",
+                    "method2(Landroidx/room/test/DataClass\$StaticInnerData;)V",
+                    "method3(Landroidx/room/test/DataClass\$EnumData;)V",
+                    "method4()Landroidx/room/test/DataClass\$StaticInnerData;"
+                )
+            )
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun descriptor_method_arrayParams() {
+        val extraJfo =
+            """
+            package androidx.room.test;
+
+            class DataClass { }
+            """.toJFO("androidx.room.test.DataClass")
+
+        singleRun(
+            """
+            package androidx.room.test;
+
+            class DummyClass {
+                @Describe
+                void method1(DataClass[] data) { }
+
+                @Describe
+                DataClass[] method2() { return null; }
+
+                @Describe
+                void method3(int[] array) { }
+
+                @Describe
+                void method4(int... array) { }
+            }
+            """.toJFO("androidx.room.test.DummyClass"), extraJfo
+        ) { descriptors ->
+            assertThat(descriptors).isEqualTo(
+                setOf(
+                    "method1([Landroidx/room/test/DataClass;)V",
+                    "method2()[Landroidx/room/test/DataClass;",
+                    "method3([I)V",
+                    "method4([I)V"
+                )
+            )
+        }.compilesWithoutError()
+    }
+
+    private fun String.toJFO(qName: String): JavaFileObject =
+        JavaFileObjects.forSourceLines(qName, this)
+
+    @Suppress("UnstableApiUsage")
+    private fun singleRun(
+        vararg jfo: JavaFileObject,
+        handler: (Set<String>) -> Unit
+    ): CompileTester {
+        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+            .that(listOf(describeAnnotation) + jfo)
+            .processedWith(object : AbstractProcessor() {
+                override fun process(
+                    annotations: Set<TypeElement>,
+                    roundEnv: RoundEnvironment
+                ): Boolean {
+                    roundEnv.getElementsAnnotatedWith(annotations.first()).map { element ->
+                        when (element.kind) {
+                            FIELD -> MoreElements.asVariable(element).descriptor()
+                            METHOD, CONSTRUCTOR -> MoreElements.asExecutable(element).descriptor()
+                            else -> error("Unsupported element to describe.")
+                        }
+                    }.toSet().let(handler)
+                    return true
+                }
+
+                override fun getSupportedOptions(): Set<String> {
+                    return setOf("androidx.room.test.Describe")
+                }
+            })
+    }
+}
\ No newline at end of file
diff --git a/room/compiler-xprocessing/src/test/java/androidx/room/processing/javac/kotlin/KotlinMetadataElementTest.kt b/room/compiler-xprocessing/src/test/java/androidx/room/processing/javac/kotlin/KotlinMetadataElementTest.kt
new file mode 100644
index 0000000..d60932e
--- /dev/null
+++ b/room/compiler-xprocessing/src/test/java/androidx/room/processing/javac/kotlin/KotlinMetadataElementTest.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2020 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 androidx.room.processing.javac.kotlin
+
+import androidx.room.processing.javac.JavacProcessingEnv
+import androidx.room.processing.util.runProcessorTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.AssumptionViolatedException
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.element.TypeElement
+import javax.lang.model.util.ElementFilter
+import kotlin.reflect.KClass
+
+@RunWith(JUnit4::class)
+class KotlinMetadataElementTest {
+
+    @Test
+    fun getParameterNames() {
+        simpleRun { processingEnv ->
+            val (testClassElement, metadataElement) = getMetadataElement(
+                processingEnv,
+                TestData::class
+            )
+            assertThat(testClassElement.getDeclaredMethods()
+                .first { it.simpleName.toString() == "functionWithParams" }
+                .let { metadataElement.getParameterNames(it) }
+            ).isEqualTo(
+                listOf("param1", "yesOrNo", "number")
+            )
+        }
+    }
+
+    @Test
+    fun findPrimaryConstructorSignature() {
+        simpleRun { invocation ->
+            val (testClassElement, metadataElement) = getMetadataElement(
+                invocation,
+                TestData::class
+            )
+            assertThat(
+                testClassElement.getConstructors().map {
+                    val desc = it.descriptor()
+                    desc to (desc == metadataElement.findPrimaryConstructorSignature())
+                }
+            ).containsExactly(
+                "<init>(Ljava/lang/String;)V" to true,
+                "<init>()V" to false
+            )
+        }
+    }
+
+    @Test
+    fun isSuspendFunction() {
+        simpleRun { invocation ->
+            val (testClassElement, metadataElement) = getMetadataElement(
+                invocation,
+                TestData::class
+            )
+            assertThat(testClassElement.getDeclaredMethods().map {
+                it.simpleName.toString() to metadataElement.isSuspendFunction(it)
+            }).containsExactly(
+                "emptyFunction" to false,
+                "suspendFunction" to true,
+                "functionWithParams" to false,
+                "getConstructorParam" to false
+            )
+        }
+    }
+
+    @Test
+    fun isObject() {
+        simpleRun { invocation ->
+            val (_, objectTypeMetadata) = getMetadataElement(invocation, ObjectType::class)
+            assertThat(objectTypeMetadata.isObject()).isTrue()
+            val (_, testDataMetadata) = getMetadataElement(invocation, TestData::class)
+            assertThat(testDataMetadata.isObject()).isFalse()
+        }
+    }
+
+    private fun TypeElement.getDeclaredMethods() = ElementFilter.methodsIn(enclosedElements)
+
+    private fun TypeElement.getConstructors() = ElementFilter.constructorsIn(enclosedElements)
+
+    private fun simpleRun(
+        handler: (ProcessingEnvironment) -> Unit
+    ) {
+        runProcessorTest {
+            if (it.processingEnv !is JavacProcessingEnv) {
+                throw AssumptionViolatedException("This test only works for java/kapt compilation")
+            }
+            handler(it.processingEnv.delegate)
+        }
+    }
+
+    private fun getMetadataElement(processingEnv: ProcessingEnvironment, klass: KClass<*>) =
+        processingEnv.elementUtils.getTypeElement(klass.java.canonicalName).let {
+            it to KotlinMetadataElement.createFor(it)!!
+        }
+
+    @Suppress("unused")
+    private class TestData(val constructorParam: String) {
+
+        constructor() : this("anything")
+
+        fun emptyFunction() {}
+
+        @Suppress("RedundantSuspendModifier")
+        suspend fun suspendFunction() {
+        }
+
+        @Suppress("UNUSED_PARAMETER")
+        fun functionWithParams(param1: String, yesOrNo: Boolean, number: Int) {
+        }
+    }
+
+    object ObjectType {
+        val foo: String = ""
+    }
+}
\ No newline at end of file