Add Executable element support
This CL adds executable element support to xprocessing.
Unlike the current implementation in room, most of the kotlin
handling is abstracted in XProcessing. We still leak it
via the API to check things for suspend functions or being
able to see suspend parameter names in class vs in kotlin metadata
but they are fairly limited.
Bug: 160322705
Bug: 160323720
Test: compiler-xprocessing tests
Change-Id: I375d7c5e5f1616000d231900a4c89796a6e09332
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/XElement.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XElement.kt
index f6d62dc..04b8180 100644
--- a/room/compiler-xprocessing/src/main/java/androidx/room/processing/XElement.kt
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XElement.kt
@@ -67,6 +67,8 @@
fun asVariableElement() = this as XVariableElement
+ fun asExecutableElement() = this as XExecutableElement
+
fun asDeclaredType(): XDeclaredType {
return asTypeElement().type
}
@@ -86,3 +88,10 @@
}
return this is XVariableElement
}
+
+fun XElement.isMethod(): Boolean {
+ contract {
+ returns(true) implies (this@isMethod is XExecutableElement)
+ }
+ return this is XExecutableElement
+}
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/XExecutableElement.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XExecutableElement.kt
new file mode 100644
index 0000000..64ab3ff
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XExecutableElement.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 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
+
+interface XExecutableElement : XElement {
+ override val enclosingElement: XElement
+
+ val parameters: List<XVariableElement>
+
+ val returnType: XType
+
+ val executableType: XExecutableType
+
+ fun isJavaDefault(): Boolean
+
+ fun isVarArgs(): Boolean
+
+ fun asMemberOf(other: XDeclaredType): XExecutableType
+
+ fun findKotlinDefaultImpl(): XExecutableElement?
+
+ fun isSuspendFunction(): Boolean
+
+ fun isOverrideableIgnoringContainer(): Boolean {
+ return !isFinal() && !isPrivate() && !isStatic()
+ }
+
+ fun isConstructor(): Boolean
+}
\ No newline at end of file
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/XExecutableType.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XExecutableType.kt
new file mode 100644
index 0000000..8a26e3a
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XExecutableType.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 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
+
+import com.squareup.javapoet.TypeVariableName
+
+/**
+ * Represents a type information for an executable.
+ *
+ * It is not an XType as it does not represent a class or primitive.
+ */
+interface XExecutableType {
+ val returnType: XType
+
+ val parameterTypes: List<XType>
+
+ val typeVariableNames: List<TypeVariableName>
+
+ fun getSuspendFunctionReturnType(): XType
+}
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/XTypeElement.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XTypeElement.kt
index 4d1f65d..320821c 100644
--- a/room/compiler-xprocessing/src/main/java/androidx/room/processing/XTypeElement.kt
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XTypeElement.kt
@@ -29,9 +29,38 @@
fun isInterface(): Boolean
+ fun isKotlinObject(): Boolean
+
/**
* All fields, including private supers.
* Room only ever reads fields this way.
*/
fun getAllFieldsIncludingPrivateSupers(): List<XVariableElement>
+
+ // only in kotlin
+ fun findPrimaryConstructor(): XExecutableElement?
+
+ /**
+ * methods declared in this type
+ * includes all instance/static methods in this
+ */
+ fun getDeclaredMethods(): List<XExecutableElement>
+
+ /**
+ * Methods declared in this type and its parents
+ * includes all instance/static methods in this
+ * includes all instance/static methods in parent CLASS if they are accessible from this (e.g. not
+ * private).
+ * does not include static methods in parent interfaces
+ */
+ fun getAllMethods(): List<XExecutableElement>
+
+ /**
+ * Instance methods declared in this and supers
+ * include non private instance methods
+ * also includes non-private instance methods from supers
+ */
+ fun getAllNonPrivateInstanceMethods(): List<XExecutableElement>
+
+ fun getConstructors(): List<XExecutableElement>
}
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacExecutableElement.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacExecutableElement.kt
new file mode 100644
index 0000000..675fa8e
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacExecutableElement.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 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
+
+import androidx.room.processing.XDeclaredType
+import androidx.room.processing.XExecutableElement
+import androidx.room.processing.XExecutableType
+import androidx.room.processing.XTypeElement
+import androidx.room.processing.XVariableElement
+import androidx.room.processing.javac.kotlin.KotlinMetadataElement
+import androidx.room.processing.javac.kotlin.descriptor
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import javax.lang.model.element.ElementKind
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.Modifier
+import javax.lang.model.element.TypeElement
+
+internal class JavacExecutableElement(
+ env: JavacProcessingEnv,
+ val containing: JavacTypeElement,
+ override val element: ExecutableElement
+) : JavacElement(
+ env,
+ element
+), XExecutableElement {
+ private val kotlinMetadata by lazy {
+ KotlinMetadataElement.createFor(element)
+ }
+
+ val descriptor by lazy {
+ element.descriptor()
+ }
+
+ private val isSuspend by lazy {
+ kotlinMetadata?.isSuspendFunction(element) == true
+ }
+
+ override val enclosingElement: XTypeElement
+ get() = super.enclosingElement as XTypeElement
+
+ override val parameters: List<JavacVariableElement> by lazy {
+ val kotlinParamNames = kotlinMetadata?.getParameterNames(element)
+ element.parameters.mapIndexed { index, variable ->
+ JavacMethodParameter(
+ env = env,
+ containing = containing,
+ element = variable,
+ kotlinName = kotlinParamNames?.getOrNull(index)
+ )
+ }
+ }
+
+ override val returnType: JavacType by lazy {
+ val asMember = env.typeUtils.asMemberOf(containing.type.typeMirror, element)
+ val asExec = MoreTypes.asExecutable(asMember)
+ env.wrap<JavacType>(asExec.returnType)
+ }
+
+ override val equalityItems: Array<out Any?> by lazy {
+ arrayOf(element, containing)
+ }
+
+ @Suppress("UnstableApiUsage")
+ private val kotlinDefaultImplClass by lazy {
+ val parent = element.enclosingElement as? TypeElement
+ val defaultImplElement = parent?.enclosedElements?.find {
+ MoreElements.isType(it) && it.simpleName.contentEquals(DEFAULT_IMPLS_CLASS_NAME)
+ } as? TypeElement
+ defaultImplElement?.let {
+ env.wrapTypeElement(it)
+ }
+ }
+
+ override fun findKotlinDefaultImpl(): XExecutableElement? {
+ fun paramsMatch(
+ ourParams: List<XVariableElement>,
+ theirParams: List<XVariableElement>
+ ): Boolean {
+ if (ourParams.size != theirParams.size - 1) {
+ return false
+ }
+ ourParams.forEachIndexed { i, variableElement ->
+ // Plus 1 to their index because their first param is a self object.
+ if (!theirParams[i + 1].type.isSameType(
+ variableElement.type
+ )
+ ) {
+ return false
+ }
+ }
+ return true
+ }
+ return kotlinDefaultImplClass?.getDeclaredMethods()?.find {
+ it.name == this.name && paramsMatch(parameters, it.parameters)
+ }
+ }
+
+ override fun isSuspendFunction() = isSuspend
+
+ override val executableType: JavacExecutableType by lazy {
+ val asMemberOf = env.typeUtils.asMemberOf(containing.type.typeMirror, element)
+ JavacExecutableType(
+ env = env,
+ executableType = MoreTypes.asExecutable(asMemberOf)
+ )
+ }
+
+ override fun isJavaDefault() = element.modifiers.contains(Modifier.DEFAULT)
+
+ override fun isVarArgs(): Boolean {
+ return element.isVarArgs
+ }
+
+ override fun asMemberOf(other: XDeclaredType): XExecutableType {
+ return if (containing.type.isSameType(other)) {
+ executableType
+ } else {
+ check(other is JavacDeclaredType)
+ val asMemberOf = env.typeUtils.asMemberOf(other.typeMirror, element)
+ JavacExecutableType(
+ env = env,
+ executableType = MoreTypes.asExecutable(asMemberOf)
+ )
+ }
+ }
+
+ override fun isConstructor(): Boolean {
+ return element.kind == ElementKind.CONSTRUCTOR
+ }
+
+ companion object {
+ internal const val DEFAULT_IMPLS_CLASS_NAME = "DefaultImpls"
+ }
+}
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacExecutableType.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacExecutableType.kt
new file mode 100644
index 0000000..a321b23
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacExecutableType.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 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
+
+import androidx.room.processing.XExecutableType
+import androidx.room.processing.XType
+import com.google.auto.common.MoreTypes
+import com.squareup.javapoet.TypeVariableName
+import javax.lang.model.type.ExecutableType
+
+internal class JavacExecutableType(
+ val env: JavacProcessingEnv,
+ val executableType: ExecutableType
+) : XExecutableType {
+ override val returnType: JavacType by lazy {
+ env.wrap<JavacType>(executableType.returnType)
+ }
+
+ override val typeVariableNames by lazy {
+ executableType.typeVariables.map {
+ TypeVariableName.get(it)
+ }
+ }
+
+ override val parameterTypes: List<JavacType> by lazy {
+ executableType.parameterTypes.map {
+ env.wrap<JavacType>(it)
+ }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other !is JavacExecutableType) return false
+ return executableType == other.executableType
+ }
+
+ override fun hashCode(): Int {
+ return executableType.hashCode()
+ }
+
+ override fun getSuspendFunctionReturnType(): XType {
+ // the continuation parameter is always the last parameter of a suspend function and it only
+ // has one type parameter, e.g Continuation<? super T>
+ val typeParam =
+ MoreTypes.asDeclared(executableType.parameterTypes.last()).typeArguments.first()
+ return env.wrap<JavacType>(typeParam).extendsBoundOrSelf() // reduce the type param
+ }
+
+ override fun toString(): String {
+ return executableType.toString()
+ }
+}
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacMethodParameter.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacMethodParameter.kt
new file mode 100644
index 0000000..afef7ff
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacMethodParameter.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 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
+
+import javax.lang.model.element.VariableElement
+
+internal class JavacMethodParameter(
+ env: JavacProcessingEnv,
+ containing: JavacTypeElement,
+ element: VariableElement,
+ val kotlinName: String? = null
+) : JavacVariableElement(env, containing, element) {
+ override val name: String
+ get() = kotlinName ?: super.name
+}
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacTypeElement.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacTypeElement.kt
index 12eaadd..731793e 100644
--- a/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacTypeElement.kt
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacTypeElement.kt
@@ -16,18 +16,26 @@
package androidx.room.processing.javac
+import androidx.room.processing.XExecutableElement
import androidx.room.processing.XTypeElement
import androidx.room.processing.XVariableElement
+import androidx.room.processing.javac.kotlin.KotlinMetadataElement
+import com.google.auto.common.MoreElements
import com.squareup.javapoet.ClassName
import javax.lang.model.element.ElementKind
import javax.lang.model.element.TypeElement
import javax.lang.model.type.TypeKind
+import javax.lang.model.util.ElementFilter
internal class JavacTypeElement(
env: JavacProcessingEnv,
override val element: TypeElement
) : JavacElement(env, element), XTypeElement {
+ private val kotlinMetadata by lazy {
+ KotlinMetadataElement.createFor(element)
+ }
+
override val qualifiedName by lazy {
element.qualifiedName.toString()
}
@@ -54,6 +62,59 @@
return _allFieldsIncludingPrivateSupers
}
+ override fun isKotlinObject() = kotlinMetadata?.isObject() == true
+
+ override fun findPrimaryConstructor(): XExecutableElement? {
+ val primarySignature = kotlinMetadata?.findPrimaryConstructorSignature() ?: return null
+ return getConstructors().firstOrNull {
+ primarySignature == it.descriptor
+ }
+ }
+
+ override fun getDeclaredMethods(): List<JavacExecutableElement> {
+ return ElementFilter.methodsIn(element.enclosedElements).map {
+ JavacExecutableElement(
+ env = env,
+ containing = this,
+ element = it
+ )
+ }
+ }
+
+ override fun getAllMethods(): List<JavacExecutableElement> {
+ return ElementFilter.methodsIn(env.elementUtils.getAllMembers(element)).map {
+ JavacExecutableElement(
+ env = env,
+ containing = this,
+ element = it
+ )
+ }
+ }
+
+ override fun getAllNonPrivateInstanceMethods(): List<JavacExecutableElement> {
+ return MoreElements.getLocalAndInheritedMethods(
+ element,
+ env.typeUtils,
+ env.elementUtils
+ ).map {
+ JavacExecutableElement(
+ env = env,
+ containing = this,
+ element = it
+ )
+ }
+ }
+
+ override fun getConstructors(): List<JavacExecutableElement> {
+ return ElementFilter.constructorsIn(element.enclosedElements).map {
+ JavacExecutableElement(
+ env = env,
+ containing = this,
+ element = it
+ )
+ }
+ }
+
override val type: JavacDeclaredType by lazy {
env.wrap<JavacDeclaredType>(element.asType())
}
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacVariableElement.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacVariableElement.kt
index f0218df..4c5aee9 100644
--- a/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacVariableElement.kt
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacVariableElement.kt
@@ -22,7 +22,7 @@
import com.google.auto.common.MoreTypes
import javax.lang.model.element.VariableElement
-internal class JavacVariableElement(
+internal open class JavacVariableElement(
env: JavacProcessingEnv,
val containing: JavacTypeElement,
override val element: VariableElement
diff --git a/room/compiler-xprocessing/src/test/java/androidx/room/processing/KotlinMetadataTest.kt b/room/compiler-xprocessing/src/test/java/androidx/room/processing/KotlinMetadataTest.kt
new file mode 100644
index 0000000..8e0df4d
--- /dev/null
+++ b/room/compiler-xprocessing/src/test/java/androidx/room/processing/KotlinMetadataTest.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 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
+
+import androidx.room.processing.testcode.KotlinTestClass
+import androidx.room.processing.util.Source
+import androidx.room.processing.util.getMethod
+import androidx.room.processing.util.getParameter
+import androidx.room.processing.util.runProcessorTest
+import com.google.common.truth.Truth.assertThat
+import com.squareup.javapoet.TypeName
+import org.junit.Test
+
+class KotlinMetadataTest {
+ @Test
+ fun readWithMetadata() {
+ val source = Source.kotlin(
+ "Dummy.kt", """
+ class Dummy
+ """.trimIndent()
+ )
+ runProcessorTest(
+ sources = listOf(source)
+ ) {
+ val element = it.processingEnv.requireTypeElement(KotlinTestClass::class)
+ element.getMethod("mySuspendMethod").apply {
+ assertThat(parameters).hasSize(2)
+ assertThat(getParameter("param1").type.typeName)
+ .isEqualTo(TypeName.get(String::class.java))
+ assertThat(isSuspendFunction()).isTrue()
+ }
+ }
+ }
+}
diff --git a/room/compiler-xprocessing/src/test/java/androidx/room/processing/XElementTest.kt b/room/compiler-xprocessing/src/test/java/androidx/room/processing/XElementTest.kt
index f308dad..6cf6f2e 100644
--- a/room/compiler-xprocessing/src/test/java/androidx/room/processing/XElementTest.kt
+++ b/room/compiler-xprocessing/src/test/java/androidx/room/processing/XElementTest.kt
@@ -19,9 +19,13 @@
import androidx.room.processing.testcode.OtherAnnotation
import androidx.room.processing.util.Source
import androidx.room.processing.util.getField
+import androidx.room.processing.util.getMethod
+import androidx.room.processing.util.getParameter
import androidx.room.processing.util.runProcessorTest
import com.google.common.truth.Truth.assertThat
import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeVariableName
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -42,6 +46,14 @@
public int publicField;
transient int transientField;
static int staticField;
+
+ private void privateMethod() {}
+ void packagePrivateMethod() {}
+ public void publicMethod() {}
+ protected void protectedMethod() {}
+ final void finalMethod() {}
+ abstract void abstractMethod();
+ static void staticMethod() {}
}
""".trimIndent()
)
@@ -72,6 +84,98 @@
element.getField("publicField").assertModifiers("public")
element.getField("transientField").assertModifiers("transient")
element.getField("staticField").assertModifiers("static")
+
+ element.getMethod("privateMethod").assertModifiers("private")
+ element.getMethod("packagePrivateMethod").assertModifiers()
+ element.getMethod("publicMethod").assertModifiers("public")
+ element.getMethod("protectedMethod").assertModifiers("protected")
+ element.getMethod("finalMethod").assertModifiers("final")
+ element.getMethod("abstractMethod").assertModifiers("abstract")
+ element.getMethod("staticMethod").assertModifiers("static")
+
+ assertThat(
+ element.getMethod("privateMethod").isOverrideableIgnoringContainer()
+ ).isFalse()
+ assertThat(
+ element.getMethod("packagePrivateMethod").isOverrideableIgnoringContainer()
+ ).isTrue()
+ assertThat(element.getMethod("publicMethod").isOverrideableIgnoringContainer()).isTrue()
+ assertThat(
+ element.getMethod("protectedMethod").isOverrideableIgnoringContainer()
+ ).isTrue()
+ assertThat(element.getMethod("finalMethod").isOverrideableIgnoringContainer()).isFalse()
+ assertThat(
+ element.getMethod("abstractMethod").isOverrideableIgnoringContainer()
+ ).isTrue()
+ assertThat(
+ element.getMethod("staticMethod").isOverrideableIgnoringContainer()
+ ).isFalse()
+ }
+ }
+
+ @Test
+ fun typeParams() {
+ val genericBase = Source.java(
+ "foo.bar.Base", """
+ package foo.bar;
+ public class Base<T> {
+ protected T returnT() {
+ throw new RuntimeException("Stub");
+ }
+ public int receiveT(T param1) {
+ return 3;
+ }
+ public <R> int receiveR(R param1) {
+ return 3;
+ }
+ public <R> R returnR() {
+ throw new RuntimeException("Stub");
+ }
+ }
+ """.trimIndent()
+ )
+ val boundedChild = Source.java(
+ "foo.bar.Child", """
+ package foo.bar;
+ public class Child extends Base<String> {
+ }
+ """.trimIndent()
+ )
+ runProcessorTest(
+ listOf(genericBase, boundedChild)
+ ) {
+ fun validateElement(element: XTypeElement, tTypeName: TypeName, rTypeName: TypeName) {
+ element.getMethod("returnT").let { method ->
+ assertThat(method.parameters).isEmpty()
+ assertThat(method.returnType.typeName).isEqualTo(tTypeName)
+ }
+ element.getMethod("receiveT").let { method ->
+ method.getParameter("param1").let { param ->
+ assertThat(param.type.typeName).isEqualTo(tTypeName)
+ }
+ assertThat(method.returnType.typeName).isEqualTo(TypeName.INT)
+ }
+ element.getMethod("receiveR").let { method ->
+ method.getParameter("param1").let { param ->
+ assertThat(param.type.typeName).isEqualTo(rTypeName)
+ }
+ assertThat(method.returnType.typeName).isEqualTo(TypeName.INT)
+ }
+ element.getMethod("returnR").let { method ->
+ assertThat(method.parameters).isEmpty()
+ assertThat(method.returnType.typeName).isEqualTo(rTypeName)
+ }
+ }
+ validateElement(
+ element = it.processingEnv.requireTypeElement("foo.bar.Base"),
+ tTypeName = TypeVariableName.get("T"),
+ rTypeName = TypeVariableName.get("R")
+ )
+ validateElement(
+ element = it.processingEnv.requireTypeElement("foo.bar.Child"),
+ tTypeName = ClassName.get(String::class.java),
+ rTypeName = TypeVariableName.get("R")
+ )
}
}
@@ -89,6 +193,9 @@
class Baz {
@OtherAnnotation(value="xx")
String testField;
+
+ @org.junit.Test
+ void testMethod() {}
}
""".trimIndent()
)
@@ -98,6 +205,15 @@
val element = it.processingEnv.requireTypeElement("foo.bar.Baz")
assertThat(element.hasAnnotation(RunWith::class)).isTrue()
assertThat(element.hasAnnotation(Test::class)).isFalse()
+ element.getMethod("testMethod").let { method ->
+ assertThat(method.hasAnnotation(Test::class)).isTrue()
+ assertThat(method.hasAnnotation(Override::class)).isFalse()
+ assertThat(
+ method.hasAnnotationInPackage(
+ "org.junit"
+ )
+ ).isTrue()
+ }
element.getField("testField").let { field ->
assertThat(field.hasAnnotation(OtherAnnotation::class)).isTrue()
assertThat(field.hasAnnotation(Test::class)).isFalse()
@@ -146,6 +262,7 @@
class Baz {
int field;
+ void method() {}
static interface Inner {}
}
""".trimIndent()
@@ -165,6 +282,13 @@
assertThat(field.isType()).isFalse()
assertThat(field.isAbstract()).isFalse()
assertThat(field.isField()).isTrue()
+ assertThat(field.isMethod()).isFalse()
+ }
+ element.getMethod("method").let { method ->
+ assertThat(method.isType()).isFalse()
+ assertThat(method.isAbstract()).isFalse()
+ assertThat(method.isField()).isFalse()
+ assertThat(method.isMethod()).isTrue()
}
}
}
diff --git a/room/compiler-xprocessing/src/test/java/androidx/room/processing/XExecutableElementTest.kt b/room/compiler-xprocessing/src/test/java/androidx/room/processing/XExecutableElementTest.kt
new file mode 100644
index 0000000..4181d68
--- /dev/null
+++ b/room/compiler-xprocessing/src/test/java/androidx/room/processing/XExecutableElementTest.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 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
+
+import androidx.room.processing.util.Source
+import androidx.room.processing.util.getDeclaredMethod
+import androidx.room.processing.util.getMethod
+import androidx.room.processing.util.getParameter
+import androidx.room.processing.util.runProcessorTest
+import com.google.common.truth.Truth.assertThat
+import com.squareup.javapoet.TypeName
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class XExecutableElementTest {
+ @Test
+ fun basic() {
+ runProcessorTest(
+ listOf(
+ Source.java(
+ "foo.bar.Baz", """
+ package foo.bar;
+ public class Baz {
+ private void foo() {}
+ public int bar(int param1) {
+ return 3;
+ }
+ }
+ """.trimIndent()
+ )
+ )
+ ) {
+ val element = it.processingEnv.requireTypeElement("foo.bar.Baz")
+ element.getDeclaredMethod("foo").let { method ->
+ assertThat(method.isJavaDefault()).isFalse()
+ assertThat(method.isVarArgs()).isFalse()
+ assertThat(method.isOverrideableIgnoringContainer()).isFalse()
+ assertThat(method.parameters).isEmpty()
+ val returnType = method.returnType
+ assertThat(returnType.isVoid()).isTrue()
+ assertThat(returnType.defaultValue()).isEqualTo("null")
+ }
+ element.getDeclaredMethod("bar").let { method ->
+ assertThat(method.isOverrideableIgnoringContainer()).isTrue()
+ assertThat(method.parameters).hasSize(1)
+ method.getParameter("param1").let { param ->
+ assertThat(param.type.isPrimitiveInt()).isTrue()
+ }
+ assertThat(method.returnType.isPrimitiveInt()).isTrue()
+ }
+ }
+ }
+ @Test
+ fun isVarArgs() {
+ val subject = Source.java(
+ "foo.bar.Baz", """
+ package foo.bar;
+ interface Baz {
+ void method(String... inputs);
+ }
+ """.trimIndent()
+ )
+ runProcessorTest(
+ sources = listOf(subject)
+ ) {
+ val element = it.processingEnv.requireTypeElement("foo.bar.Baz")
+ assertThat(element.getMethod("method").isVarArgs()).isTrue()
+ }
+ }
+
+ @Test
+ fun kotlinDefaultImpl() {
+ val subject = Source.kotlin(
+ "Baz.kt", """
+ package foo.bar;
+ import java.util.List;
+ interface Baz {
+ fun noDefault()
+ fun withDefault(): Int {
+ return 3;
+ }
+ fun nameMatch()
+ fun nameMatch(param:Int) {}
+ fun withDefaultWithParams(param1:Int, param2:String) {}
+ fun withDefaultWithTypeArgs(param1: List<String>): String {
+ return param1.first();
+ }
+ }
+ """.trimIndent()
+ )
+ runProcessorTest(
+ sources = listOf(subject)
+ ) {
+ val element = it.processingEnv.requireTypeElement("foo.bar.Baz")
+ element.getDeclaredMethod("noDefault").let { method ->
+ assertThat(method.findKotlinDefaultImpl()).isNull()
+ }
+ element.getDeclaredMethod("withDefault").let { method ->
+ val defaultImpl = method.findKotlinDefaultImpl()
+ assertThat(defaultImpl).isNotNull()
+ assertThat(defaultImpl!!.returnType.typeName).isEqualTo(TypeName.INT)
+ // default impl gets self as first parameter
+ assertThat(defaultImpl.parameters).hasSize(1)
+ assertThat(defaultImpl.parameters.first().type)
+ .isEqualTo(element.type)
+ }
+ element.getDeclaredMethods().first {
+ it.name == "nameMatch" && it.parameters.isEmpty()
+ }.let { nameMatchWithoutDefault ->
+ assertThat(nameMatchWithoutDefault.findKotlinDefaultImpl()).isNull()
+ }
+
+ element.getDeclaredMethods().first {
+ it.name == "nameMatch" && it.parameters.size == 1
+ }.let { nameMatchWithoutDefault ->
+ assertThat(nameMatchWithoutDefault.findKotlinDefaultImpl()).isNotNull()
+ }
+
+ element.getDeclaredMethod("withDefaultWithParams").let { method ->
+ val defaultImpl = method.findKotlinDefaultImpl()
+ assertThat(defaultImpl).isNotNull()
+ assertThat(defaultImpl!!.parameters.drop(1).map {
+ it.name
+ }).containsExactly("param1", "param2")
+ }
+
+ element.getDeclaredMethod("withDefaultWithTypeArgs").let { method ->
+ val defaultImpl = method.findKotlinDefaultImpl()
+ assertThat(defaultImpl).isNotNull()
+ assertThat(defaultImpl!!.parameters.drop(1).map {
+ it.name
+ }).containsExactly("param1")
+ }
+ }
+ }
+}
diff --git a/room/compiler-xprocessing/src/test/java/androidx/room/processing/XProcessingEnvTest.kt b/room/compiler-xprocessing/src/test/java/androidx/room/processing/XProcessingEnvTest.kt
index 4b1e4f8..7305f37 100644
--- a/room/compiler-xprocessing/src/test/java/androidx/room/processing/XProcessingEnvTest.kt
+++ b/room/compiler-xprocessing/src/test/java/androidx/room/processing/XProcessingEnvTest.kt
@@ -111,6 +111,8 @@
assertThat(element.name).isEqualTo("Baz")
assertThat(element.asDeclaredType().typeName)
.isEqualTo(ClassName.get("foo.bar", "Baz"))
+ assertThat(element.getConstructors()).hasSize(1)
+ assertThat(element.getDeclaredMethods()).hasSize(2)
assertThat(element.kindName()).isEqualTo("class")
assertThat(element.isInterface()).isFalse()
assertThat(element.superType?.typeName).isEqualTo(TypeName.OBJECT)
diff --git a/room/compiler-xprocessing/src/test/java/androidx/room/processing/XTypeTest.kt b/room/compiler-xprocessing/src/test/java/androidx/room/processing/XTypeTest.kt
index 9ffd25e..9ef38f7 100644
--- a/room/compiler-xprocessing/src/test/java/androidx/room/processing/XTypeTest.kt
+++ b/room/compiler-xprocessing/src/test/java/androidx/room/processing/XTypeTest.kt
@@ -17,7 +17,9 @@
package androidx.room.processing
import androidx.room.processing.util.Source
+import androidx.room.processing.util.getDeclaredMethod
import androidx.room.processing.util.getField
+import androidx.room.processing.util.getMethod
import androidx.room.processing.util.runProcessorTest
import androidx.room.processing.util.runProcessorTestForFailedCompilation
import com.google.common.truth.Truth.assertThat
@@ -66,6 +68,15 @@
)
assertThat(firstType.typeName).isEqualTo(expected)
}
+
+ type.asTypeElement().getMethod("wildcardParam").let { method ->
+ val wildcardParam = method.parameters.first()
+ val extendsBoundOrSelf = wildcardParam.type.extendsBoundOrSelf()
+ assertThat(extendsBoundOrSelf.erasure())
+ .isEqualTo(
+ it.processingEnv.requireType("java.util.Set").erasure()
+ )
+ }
}
}
@@ -77,6 +88,9 @@
package foo.bar;
public class Baz {
NotExistingType badField;
+ NotExistingType badMethod() {
+ throw new RuntimeException("Stub");
+ }
}
""".trimIndent()
)
@@ -90,6 +104,12 @@
ClassName.get("", "NotExistingType")
)
}
+ element.getDeclaredMethod("badMethod").let { method ->
+ assertThat(method.returnType.isError()).isTrue()
+ assertThat(method.returnType.typeName).isEqualTo(
+ ClassName.get("", "NotExistingType")
+ )
+ }
}
}
diff --git a/room/compiler-xprocessing/src/test/java/androidx/room/processing/testcode/KotlinTestClass.kt b/room/compiler-xprocessing/src/test/java/androidx/room/processing/testcode/KotlinTestClass.kt
new file mode 100644
index 0000000..b3b17a7
--- /dev/null
+++ b/room/compiler-xprocessing/src/test/java/androidx/room/processing/testcode/KotlinTestClass.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 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.
+ */
+
+@file:Suppress("unused")
+
+package androidx.room.processing.testcode
+
+@Suppress("UNUSED_PARAMETER", "RedundantSuspendModifier")
+class KotlinTestClass {
+ suspend fun mySuspendMethod(
+ param1: String
+ ) {
+ }
+}
+
+object KotlinTestObject
diff --git a/room/compiler-xprocessing/src/test/java/androidx/room/processing/util/TestExtensions.kt b/room/compiler-xprocessing/src/test/java/androidx/room/processing/util/TestExtensions.kt
index bcea0d0..22e2859 100644
--- a/room/compiler-xprocessing/src/test/java/androidx/room/processing/util/TestExtensions.kt
+++ b/room/compiler-xprocessing/src/test/java/androidx/room/processing/util/TestExtensions.kt
@@ -16,8 +16,21 @@
package androidx.room.processing.util
+import androidx.room.processing.XExecutableElement
import androidx.room.processing.XTypeElement
fun XTypeElement.getField(name: String) = getAllFieldsIncludingPrivateSupers().first {
it.name == name
}
+
+fun XTypeElement.getDeclaredMethod(name: String) = getDeclaredMethods().firstOrNull {
+ it.name == name
+} ?: throw AssertionError("cannot find method with name $name")
+
+fun XTypeElement.getMethod(name: String) = getAllMethods().firstOrNull {
+ it.name == name
+} ?: throw AssertionError("cannot find method with name $name")
+
+fun XExecutableElement.getParameter(name: String) = parameters.firstOrNull {
+ it.name == name
+} ?: throw AssertionError("cannot find parameter with name $name")