Merge "Add converter APIs in XProcessing" into androidx-main
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeElement.kt
index 9293a8b..90fe22c 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeElement.kt
@@ -108,6 +108,12 @@
     fun isCompanionObject(): Boolean
 
     /**
+     * Fields declared in this type
+     *  includes all instance/static fields in this
+     */
+    fun getDeclaredFields(): List<XFieldElement>
+
+    /**
      * All fields, including private supers.
      * Room only ever reads fields this way.
      */
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/compat/XConverters.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/compat/XConverters.kt
new file mode 100644
index 0000000..4d5e1ee
--- /dev/null
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/compat/XConverters.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2021 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.compiler.processing.compat
+
+import androidx.room.compiler.processing.XElement
+import androidx.room.compiler.processing.XExecutableElement
+import androidx.room.compiler.processing.XProcessingEnv
+import androidx.room.compiler.processing.XType
+import androidx.room.compiler.processing.XTypeElement
+import androidx.room.compiler.processing.XVariableElement
+import androidx.room.compiler.processing.javac.JavacElement
+import androidx.room.compiler.processing.javac.JavacExecutableElement
+import androidx.room.compiler.processing.javac.JavacProcessingEnv
+import androidx.room.compiler.processing.javac.JavacType
+import androidx.room.compiler.processing.javac.JavacTypeElement
+import androidx.room.compiler.processing.javac.JavacVariableElement
+import javax.lang.model.element.Element
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.TypeElement
+import javax.lang.model.element.VariableElement
+import javax.lang.model.type.TypeMirror
+
+// Migration APIs for converting between Javac and XProcessing types.
+object XConverters {
+
+    @JvmStatic
+    fun XElement.toJavac(): Element = (this as JavacElement).element
+
+    @JvmStatic
+    fun XTypeElement.toJavac(): TypeElement = (this as JavacTypeElement).element
+
+    @JvmStatic
+    fun XExecutableElement.toJavac(): ExecutableElement = (this as JavacExecutableElement).element
+
+    @JvmStatic
+    fun XVariableElement.toJavac(): VariableElement = (this as JavacVariableElement).element
+
+    @JvmStatic
+    fun XType.toJavac(): TypeMirror = (this as JavacType).typeMirror
+
+    @JvmStatic
+    fun Element.toXProcessing(env: XProcessingEnv): XElement {
+        return when (this) {
+            is TypeElement -> this.toXProcessing(env)
+            is ExecutableElement -> this.toXProcessing(env)
+            is VariableElement -> this.toXProcessing(env)
+            else -> error(
+                "Don't know how to convert element of type '${this::class}' to a XElement"
+            )
+        }
+    }
+
+    @JvmStatic
+    fun TypeElement.toXProcessing(env: XProcessingEnv): XTypeElement =
+        (env as JavacProcessingEnv).wrapTypeElement(this)
+
+    @JvmStatic
+    fun ExecutableElement.toXProcessing(env: XProcessingEnv): XExecutableElement =
+        (env as JavacProcessingEnv).wrapExecutableElement(this)
+
+    @JvmStatic
+    fun VariableElement.toXProcessing(env: XProcessingEnv): XVariableElement =
+        (env as JavacProcessingEnv).wrapVariableElement(this)
+
+    // TODO: TypeMirror to XType, this will be more complicated since location context is lost...
+}
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
index fa5ad96..3aca458 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
@@ -57,6 +57,20 @@
         element.enclosingType(env)
     }
 
+    private val _declaredFields by lazy {
+        ElementFilter.fieldsIn(element.enclosedElements).map {
+            JavacFieldElement(
+                env = env,
+                element = it,
+                containing = this
+            )
+        }
+    }
+
+    override fun getDeclaredFields(): List<XFieldElement> {
+        return _declaredFields
+    }
+
     private val _allFieldsIncludingPrivateSupers by lazy {
         element.getAllFieldsIncludingPrivateSupers(
             env.elementUtils
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
index 1d21c53..24dffb3 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
@@ -134,13 +134,7 @@
 
     private val _declaredFieldsIncludingSupers by lazy {
         // Read all properties from all supers and select the ones that are not overridden.
-        val myPropertyFields = if (declaration.classKind == ClassKind.INTERFACE) {
-            _declaredProperties.filter {
-                it.isStatic()
-            }
-        } else {
-            _declaredProperties.filter { !it.isAbstract() }
-        }
+        val myPropertyFields = getDeclaredFields()
         val selectedNames = myPropertyFields.mapTo(mutableSetOf()) {
             it.name
         }
@@ -268,6 +262,18 @@
         return !isInterface() && !declaration.isOpen()
     }
 
+    private val _declaredFields by lazy {
+        if (declaration.classKind == ClassKind.INTERFACE) {
+            _declaredProperties.filter { it.isStatic() }
+        } else {
+            _declaredProperties.filter { !it.isAbstract() }
+        }
+    }
+
+    override fun getDeclaredFields(): List<XFieldElement> {
+        return _declaredFields
+    }
+
     override fun getAllFieldsIncludingPrivateSupers(): List<XFieldElement> {
         return _declaredFieldsIncludingSupers
     }
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
index e6fc8e1..342c4ad 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
@@ -336,8 +336,12 @@
         runProcessorTest(sources = listOf(src)) { invocation ->
             val baseClass = invocation.processingEnv.requireTypeElement("BaseClass")
             assertThat(baseClass.getAllFieldNames()).containsExactly("genericProp")
+            assertThat(baseClass.getDeclaredFields().map { it.name })
+                .containsExactly("genericProp")
             val subClass = invocation.processingEnv.requireTypeElement("SubClass")
             assertThat(subClass.getAllFieldNames()).containsExactly("genericProp", "subClassProp")
+            assertThat(subClass.getDeclaredFields().map { it.name })
+                .containsExactly("subClassProp")
 
             val baseMethod = baseClass.getMethod("baseMethod")
             baseMethod.asMemberOf(subClass.type).let { methodType ->
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/compat/XConvertersTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/compat/XConvertersTest.kt
new file mode 100644
index 0000000..3c4b793
--- /dev/null
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/compat/XConvertersTest.kt
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2021 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.compiler.processing.compat
+
+import androidx.room.compiler.processing.compat.XConverters.toJavac
+import androidx.room.compiler.processing.compat.XConverters.toXProcessing
+import androidx.room.compiler.processing.javac.JavacProcessingEnv
+import androidx.room.compiler.processing.util.Source
+import androidx.room.compiler.processing.util.XTestInvocation
+import androidx.room.compiler.processing.util.getDeclaredField
+import androidx.room.compiler.processing.util.getDeclaredMethod
+import androidx.room.compiler.processing.util.runKaptTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import javax.lang.model.util.ElementFilter
+
+class XConvertersTest {
+
+    val kotlinSrc = Source.kotlin(
+        "KotlinClass.kt",
+        """
+        class KotlinClass {
+          var field = 1
+          fun foo(param: Int) {
+          }
+        }
+        """.trimIndent()
+    )
+    val javaSrc = Source.java(
+        "JavaClass",
+        """
+        public class JavaClass {
+          public int field = 1;
+          public void foo(int param) {
+          }
+        }
+        """.trimIndent()
+    )
+
+    @Test
+    fun typeElement() {
+        runKaptTest(
+            sources = listOf(kotlinSrc, javaSrc)
+        ) { invocation ->
+            val kotlinClass = invocation.processingEnv.requireTypeElement("KotlinClass")
+            val javaClass = invocation.processingEnv.requireTypeElement("JavaClass")
+
+            assertThat(kotlinClass.toJavac())
+                .isEqualTo(invocation.getJavacTypeElement("KotlinClass"))
+            assertThat(javaClass.toJavac())
+                .isEqualTo(invocation.getJavacTypeElement("JavaClass"))
+
+            assertThat(
+                invocation.getJavacTypeElement("KotlinClass")
+                    .toXProcessing(invocation.processingEnv)
+            ).isEqualTo(kotlinClass)
+            assertThat(
+                invocation.getJavacTypeElement("JavaClass")
+                    .toXProcessing(invocation.processingEnv)
+            ).isEqualTo(javaClass)
+        }
+    }
+
+    @Test
+    fun executableElement() {
+        runKaptTest(
+            sources = listOf(kotlinSrc, javaSrc)
+        ) { invocation ->
+            val kotlinClass = invocation.processingEnv.requireTypeElement("KotlinClass")
+            val javaClass = invocation.processingEnv.requireTypeElement("JavaClass")
+
+            assertThat(
+                kotlinClass.getDeclaredMethods().map { it.toJavac() }
+            ).containsExactlyElementsIn(
+                ElementFilter.methodsIn(
+                    invocation.getJavacTypeElement("KotlinClass").enclosedElements
+                )
+            )
+            assertThat(
+                javaClass.getDeclaredMethods().map { it.toJavac() }
+            ).containsExactlyElementsIn(
+                ElementFilter.methodsIn(
+                    invocation.getJavacTypeElement("JavaClass").enclosedElements
+                )
+            )
+
+            val kotlinFoo = ElementFilter.methodsIn(
+                invocation.getJavacTypeElement("KotlinClass").enclosedElements
+            ).first { it.simpleName.toString() == "foo" }
+            assertThat(kotlinFoo.toXProcessing(invocation.processingEnv))
+                .isEqualTo(kotlinClass.getDeclaredMethod("foo"))
+            val javaFoo = ElementFilter.methodsIn(
+                invocation.getJavacTypeElement("JavaClass").enclosedElements
+            ).first { it.simpleName.toString() == "foo" }
+            assertThat(javaFoo.toXProcessing(invocation.processingEnv))
+                .isEqualTo(javaClass.getDeclaredMethod("foo"))
+        }
+    }
+
+    @Test
+    fun variableElement_field() {
+        runKaptTest(
+            sources = listOf(kotlinSrc, javaSrc)
+        ) { invocation ->
+            val kotlinClass = invocation.processingEnv.requireTypeElement("KotlinClass")
+            val javaClass = invocation.processingEnv.requireTypeElement("JavaClass")
+
+            assertThat(
+                kotlinClass.getDeclaredFields().map { it.toJavac() }
+            ).containsExactlyElementsIn(
+                ElementFilter.fieldsIn(
+                    invocation.getJavacTypeElement("KotlinClass").enclosedElements
+                )
+            )
+            assertThat(
+                javaClass.getDeclaredFields().map { it.toJavac() }
+            ).containsExactlyElementsIn(
+                ElementFilter.fieldsIn(
+                    invocation.getJavacTypeElement("JavaClass").enclosedElements
+                )
+            )
+
+            val kotlinField = ElementFilter.fieldsIn(
+                invocation.getJavacTypeElement("KotlinClass").enclosedElements
+            ).first { it.simpleName.toString() == "field" }
+            assertThat(kotlinField.toXProcessing(invocation.processingEnv))
+                .isEqualTo(kotlinClass.getDeclaredField("field"))
+            val javaField = ElementFilter.fieldsIn(
+                invocation.getJavacTypeElement("JavaClass").enclosedElements
+            ).first { it.simpleName.toString() == "field" }
+            assertThat(javaField.toXProcessing(invocation.processingEnv))
+                .isEqualTo(javaClass.getDeclaredField("field"))
+        }
+    }
+
+    @Test
+    fun variableElement_parameter() {
+        runKaptTest(
+            sources = listOf(kotlinSrc, javaSrc)
+        ) { invocation ->
+            val kotlinClass = invocation.processingEnv.requireTypeElement("KotlinClass")
+            val javaClass = invocation.processingEnv.requireTypeElement("JavaClass")
+
+            assertThat(
+                kotlinClass.getDeclaredMethod("foo").parameters.map { it.toJavac() }
+            ).containsExactlyElementsIn(
+                ElementFilter.methodsIn(
+                    invocation.getJavacTypeElement("KotlinClass").enclosedElements
+                ).first { it.simpleName.toString() == "foo" }.parameters
+            )
+            assertThat(
+                javaClass.getDeclaredMethod("foo").parameters.map { it.toJavac() }
+            ).containsExactlyElementsIn(
+                ElementFilter.methodsIn(
+                    invocation.getJavacTypeElement("JavaClass").enclosedElements
+                ).first { it.simpleName.toString() == "foo" }.parameters
+            )
+
+            val kotlinParam = ElementFilter.methodsIn(
+                invocation.getJavacTypeElement("KotlinClass").enclosedElements
+            ).first { it.simpleName.toString() == "foo" }.parameters.first()
+            assertThat(kotlinParam.toXProcessing(invocation.processingEnv))
+                .isEqualTo(kotlinClass.getDeclaredMethod("foo").parameters.first())
+            val javaParam = ElementFilter.methodsIn(
+                invocation.getJavacTypeElement("JavaClass").enclosedElements
+            ).first { it.simpleName.toString() == "foo" }.parameters.first()
+            assertThat(javaParam.toXProcessing(invocation.processingEnv))
+                .isEqualTo(javaClass.getDeclaredMethod("foo").parameters.first())
+        }
+    }
+
+    private fun XTestInvocation.getJavacTypeElement(fqn: String) =
+        (this.processingEnv as JavacProcessingEnv).delegate.elementUtils.getTypeElement(fqn)
+}
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/util/TestExtensions.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/util/TestExtensions.kt
index 0e8f322..59ceb02 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/util/TestExtensions.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/util/TestExtensions.kt
@@ -23,6 +23,10 @@
     it.name
 }
 
+fun XTypeElement.getDeclaredField(name: String) = getDeclaredFields().first {
+    it.name == name
+}
+
 fun XTypeElement.getField(name: String) = getAllFieldsIncludingPrivateSupers().first {
     it.name == name
 }