diff --git a/epoxy-processor/build.gradle b/epoxy-processor/build.gradle index d96420ce62..b2d60a29aa 100755 --- a/epoxy-processor/build.gradle +++ b/epoxy-processor/build.gradle @@ -20,14 +20,16 @@ for (File file : sdkHandler.sdkLoader.repositories) { } dependencies { - compile deps.kotlin - compile deps.squareJavaPoet - compile deps.squareKotlinPoet + implementation deps.kotlin + implementation deps.squareJavaPoet + implementation deps.squareKotlinPoet /** Provides the sun javac tools for looking up the R class trees. */ compileOnly files(Jvm.current().getToolsJar()) - compile project(':epoxy-annotations') + implementation project(':epoxy-annotations') + + testImplementation rootProject.deps.junit } checkstyle { diff --git a/epoxy-processor/src/main/java/com/airbnb/epoxy/PoetExtensions.kt b/epoxy-processor/src/main/java/com/airbnb/epoxy/PoetExtensions.kt index 8de7c4be16..5c99bec111 100644 --- a/epoxy-processor/src/main/java/com/airbnb/epoxy/PoetExtensions.kt +++ b/epoxy-processor/src/main/java/com/airbnb/epoxy/PoetExtensions.kt @@ -1,5 +1,6 @@ package com.airbnb.epoxy +import com.squareup.javapoet.CodeBlock import com.squareup.javapoet.TypeName import com.squareup.kotlinpoet.ANY import com.squareup.kotlinpoet.BOOLEAN @@ -13,6 +14,7 @@ import com.squareup.kotlinpoet.LONG import com.squareup.kotlinpoet.SHORT import com.squareup.kotlinpoet.UNIT import javax.lang.model.element.Modifier +import kotlin.reflect.jvm.internal.impl.types.KotlinType typealias JavaClassName = com.squareup.javapoet.ClassName typealias JavaTypeName = com.squareup.javapoet.TypeName @@ -102,6 +104,19 @@ private fun JavaClassName.getSimpleNamesInKotlin(): List { return originalNames } +// Does not support transferring complex annotations which +// have parameters and values associated with them. +fun JavaAnnotationSpec.toKPoet(): KotlinAnnotationSpec? { + // If the annotation has any members (params), then we + // return null since we don't yet support translating + // params from Java annotation to Kotlin annotation. + if (members.isNotEmpty()) { + return null + } + val annotationClass = KotlinClassName.bestGuess(type.toString()) + return KotlinAnnotationSpec.builder(annotationClass).build() +} + fun JavaClassName.setPackage(packageName: String) = JavaClassName.get(packageName, simpleName(), *simpleNames().drop(1).toTypedArray())!! @@ -189,6 +204,8 @@ fun JavaParameterSpec.toKPoet(): KotlinParameterSpec { val nullable = annotations.any { (it.type as? JavaClassName)?.simpleName() == "Nullable" } + val kotlinAnnotations: List = annotations.mapNotNull { it.toKPoet() } + return KotlinParameterSpec.builder( paramName, type.toKPoet(nullable), @@ -197,6 +214,7 @@ fun JavaParameterSpec.toKPoet(): KotlinParameterSpec { if (isLambda(type)) { addModifiers(KModifier.NOINLINE) } + addAnnotations(kotlinAnnotations) }.build() } diff --git a/epoxy-processor/src/test/java/com/airbnb/epoxy/PoetExtensionsTest.kt b/epoxy-processor/src/test/java/com/airbnb/epoxy/PoetExtensionsTest.kt new file mode 100644 index 0000000000..b2ad6cfb19 --- /dev/null +++ b/epoxy-processor/src/test/java/com/airbnb/epoxy/PoetExtensionsTest.kt @@ -0,0 +1,94 @@ +package com.airbnb.epoxy + +import android.support.annotation.FloatRange +import android.support.annotation.NonNull +import com.squareup.kotlinpoet.asTypeName +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Test +import javax.lang.model.element.Modifier + +class PoetExtensionsTest { + + @Test fun testAnnotationSpecToKPoet() { + val annotation = EpoxyModelClass::class.java + val type = annotation.asTypeName() + val javaAnnotation = JavaAnnotationSpec.builder(annotation).build() + + val kotlinAnnotation = javaAnnotation.toKPoet() + assertNotNull(kotlinAnnotation) + assertEquals(type, kotlinAnnotation?.type) + } + + @Test fun testAnnotationSpecToKPoetWithParams() { + val annotation = FloatRange::class.java + val javaAnnotation = JavaAnnotationSpec.builder(annotation) + .addMember("from", "0.0") + .addMember("to", "1.0") + .build() + + val kotlinAnnotation = javaAnnotation.toKPoet() + assertNull(kotlinAnnotation) + } + + @Test fun testIsLambdaWithString() { + val stringType = JavaClassName.bestGuess("java.lang.String") + assertFalse(isLambda(stringType)) + } + + @Test fun testIsLambdaWithLambda() { + val lambdaType = JavaClassName.bestGuess("kotlin.Function2") + assertTrue(isLambda(lambdaType)) + } + + @Test fun testJavaParameterSpecToKPoet() { + val name = "android" + val javaParameter = JavaParameterSpec.builder(JavaClassName.bestGuess("java.lang.String"), name, Modifier.PRIVATE) + .addAnnotation(NonNull::class.java) + .build() + val kotlinString = KotlinClassName("kotlin", "String") + + val kotlinParameter = javaParameter.toKPoet() + assertEquals(name, kotlinParameter.name) + assertEquals(kotlinString, kotlinParameter.type) + assertEquals(javaParameter.annotations.size, kotlinParameter.annotations.size) + assertEquals(NonNull::class.java.asTypeName(), kotlinParameter.annotations[0].type) + } + + @Test fun testJavaTypeNameToKPoet() { + val javaType = JavaParametrizedTypeName.get(JavaClassName.bestGuess("java.util.List"), + JavaClassName.bestGuess("java.lang.String")) + + val kotlinType = javaType.toKPoet() + val kotlinList = KotlinClassName("kotlin.collections", "List") + val kotlinString = KotlinClassName("kotlin", "String") + assertEquals(kotlinList, kotlinType.rawType) + assertEquals(kotlinString, kotlinType.typeArguments[0]) + } + + @Test fun testJavaArrayTypeNameToKPoet() { + val javaIntArray = JavaArrayTypeName.of(JavaClassName.INT) + val kotlinIntArray = javaIntArray.toKPoet() + + assertEquals("kotlin.IntArray", kotlinIntArray.toString()) + + val javaFloatArray = JavaArrayTypeName.of(JavaClassName.bestGuess("java.lang.Float")) + val kotlinFloatArray = javaFloatArray.toKPoet() + + assertEquals("kotlin.Array", kotlinFloatArray.toString()) + } + + @Test fun testJavaClassNameToKPoet() { + val javaClassName = JavaClassName.bestGuess("java.lang.Integer") + val kotlinClassName = javaClassName.toKPoet() + + val javaByteName = JavaClassName.BYTE + val kotlinByteName = javaByteName.toKPoet() + + assertEquals("kotlin.Int", kotlinClassName.toString()) + assertEquals("kotlin.Byte", kotlinByteName.toString()) + } +} diff --git a/kotlinsample/src/test/java/com/airbnb/epoxy/kotlinsample/AnnotationModel.kt b/kotlinsample/src/test/java/com/airbnb/epoxy/kotlinsample/AnnotationModel.kt new file mode 100644 index 0000000000..cd6745b371 --- /dev/null +++ b/kotlinsample/src/test/java/com/airbnb/epoxy/kotlinsample/AnnotationModel.kt @@ -0,0 +1,26 @@ +package com.airbnb.epoxy.kotlinsample + +import android.support.annotation.DrawableRes +import android.support.annotation.FloatRange +import android.support.annotation.StringRes +import android.view.View +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyHolder +import com.airbnb.epoxy.EpoxyModelClass +import com.airbnb.epoxy.EpoxyModelWithHolder + +@EpoxyModelClass(layout = R.layout.activity_kotlin_sample) +abstract class AnnotationModel( + @StringRes val resId: Int, + @FloatRange(from = 0.0, to = 1.0) val range: Float +): EpoxyModelWithHolder() { + + @EpoxyAttribute @DrawableRes var drawable: Int? = null +} + +class AnnotationHolder: EpoxyHolder() { + override fun bindView(itemView: View) { + + } + +}