Skip to content

Commit

Permalink
Improve transformation of java types to kotlin for extensions (#331)
Browse files Browse the repository at this point in the history
  • Loading branch information
elihart authored Oct 23, 2017
1 parent 98c2987 commit 715cadd
Show file tree
Hide file tree
Showing 4 changed files with 220 additions and 13 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.airbnb.epoxy.integrationtest;

import com.airbnb.epoxy.EpoxyAttribute;
import com.airbnb.epoxy.EpoxyModel;
import com.airbnb.epoxy.EpoxyModelClass;
import com.airbnb.epoxy.EpoxyModelGroup;

import java.util.List;

@EpoxyModelClass
public abstract class ModelGroupWithAnnotation extends EpoxyModelGroup {
@EpoxyAttribute int backgroundColor;

public ModelGroupWithAnnotation(List<? extends EpoxyModel<?>> models) {
super(R.layout.model_with_click_listener, models);
}

@Override
public void bind(Holder holder) {
super.bind(holder);
holder.getRootView().setBackgroundColor(backgroundColor);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
import com.airbnb.epoxy.EpoxyModel;
import com.airbnb.epoxy.EpoxyModelClass;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

@EpoxyModelClass
public abstract class ModelWithConstructors extends EpoxyModel<TextView> {
@EpoxyAttribute public int value;
Expand All @@ -23,6 +28,48 @@ public ModelWithConstructors(long id) {
super(id);
}

// Tests that kotlin extension functions
public ModelWithConstructors(
Collection<String> collection,
Iterable<String> iterable,
List<String> list,
List<? extends String> upperBoundList,
// List<? super String> lowerBoundList,!!! Doesn't work, we would need to transform this
// type to
// a MutableList
List<?> wildCardList,
Map<String, String> map,
Set<String> set,
Boolean boxedBoolean,
Byte boxedByte,
Short boxedShort,
Integer boxedInteger,
Long boxedLong,
Character boxedCharacter,
Float boxedFloat,
Double boxedDouble,
String[] stringArray,
byte[] byteArray,
short[] shortArray,
char[] charArray,
int[] intArray,
float[] floatArray,
double[] doubleArray,
long[] longArray,
boolean[] booleanArray,
Byte[] boxedByteArray,
Short[] boxedShortArray,
Character[] boxedCharArray,
Integer[] boxedIntArray,
Float[] boxedFloatArray,
Double[] boxedDoubleArray,
Long[] boxedLongArray,
Boolean[] boxedBooleanArray,
Object objectParam
) {

}

@Override
protected int getDefaultLayout() {
return R.layout.model_with_click_listener;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.airbnb.epoxy

import com.airbnb.epoxy.integrationtest.*
import org.junit.*
import org.junit.runner.*
import org.robolectric.*
import org.robolectric.annotation.*

@RunWith(RobolectricTestRunner::class)
@Config(constants = BuildConfig::class, sdk = intArrayOf(21))
class ModelGroupIntegrationTest {

@Test
fun modelGroupSubclassIsGenerated() {
// Just checking that the generated class exists and compiles
ModelGroupWithAnnotation_(listOf(Model_()))
}
}
145 changes: 132 additions & 13 deletions epoxy-processor/src/main/java/com/airbnb/epoxy/PoetExtensions.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.airbnb.epoxy

import com.squareup.javapoet.TypeName
import com.squareup.kotlinpoet.*
import javax.lang.model.element.*

Expand All @@ -25,9 +26,68 @@ typealias KotlinParameterSpec = com.squareup.kotlinpoet.ParameterSpec
typealias KotlinAnnotationSpec = com.squareup.kotlinpoet.AnnotationSpec
typealias KotlinTypeSpec = com.squareup.kotlinpoet.TypeSpec

fun JavaClassName.toKPoet() = KotlinClassName(
packageName(),
simpleName())
private val javaUtilPkg = "java.util"
private val javaLangPkg = "java.lang"
private val kotlinCollectionsPkg = "kotlin.collections"
private val kotlinPkg = "kotlin"
fun JavaClassName.toKPoet(): KotlinClassName {

val simpleNames = getSimpleNamesInKotlin()
val packageName = getPackageNameInKotlin()

return KotlinClassName(
packageName,
simpleNames.first(),
*simpleNames.drop(1).toTypedArray())
}

/** Some classes, like List or Byte have the same class name but a different package for their kotlin equivalent. */
private fun JavaClassName.getPackageNameInKotlin(): String {
if (packageName() in listOf(javaUtilPkg, javaLangPkg) && simpleNames().size == 1) {

val transformedPkg = if (isBoxedPrimitive) {
kotlinPkg
} else {
when (simpleName()) {
"Collection",
"List",
"Map",
"Set",
"Iterable" -> kotlinCollectionsPkg
"String" -> kotlinPkg
else -> null
}
}

if (transformedPkg != null) {
return transformedPkg
}
}

return packageName()
}

/** Some classes, notably Integer and Character, have a different simple name in Kotlin. */
private fun JavaClassName.getSimpleNamesInKotlin(): List<String> {
val originalNames = simpleNames()

if (isBoxedPrimitive) {
val transformedName = when (originalNames.first()) {
"Integer" -> "Int"
"Character" -> "Char"
else -> null
}

if (transformedName != null) {
return listOf(transformedName)
}
}

return originalNames
}

fun JavaClassName.setPackage(packageName: String)
= JavaClassName.get(packageName, simpleName(), *simpleNames().drop(1).toTypedArray())!!

// Does not support transferring annotations
fun JavaWildcardTypeName.toKPoet() =
Expand All @@ -44,10 +104,31 @@ fun JavaParametrizedTypeName.toKPoet()
*typeArguments.toKPoet().toTypedArray())

// Does not support transferring annotations
fun JavaArrayTypeName.toKPoet()
= KotlinParameterizedTypeName.get(
KotlinClassName.bestGuess("kotlin.Array"),
this.componentType.toKPoet())
fun JavaArrayTypeName.toKPoet(): KotlinTypeName {

// Kotlin has special classes for primitive arrays
if (componentType.isPrimitive) {
val kotlinArrayType = when (componentType) {
TypeName.BYTE -> "ByteArray"
TypeName.SHORT -> "ShortArray"
TypeName.CHAR -> "CharArray"
TypeName.INT -> "IntArray"
TypeName.FLOAT -> "FloatArray"
TypeName.DOUBLE -> "DoubleArray"
TypeName.LONG -> "LongArray"
TypeName.BOOLEAN -> "BooleanArray"
else -> null
}

if (kotlinArrayType != null) {
return KotlinClassName(kotlinPkg, kotlinArrayType)
}
}

return KotlinParameterizedTypeName.get(
KotlinClassName(kotlinPkg, "Array"),
this.componentType.toKPoet())
}

// Does not support transferring annotations
fun JavaTypeVariableName.toKPoet()
Expand Down Expand Up @@ -76,12 +157,18 @@ fun JavaTypeName.toKPoet(): KotlinTypeName = when (this) {

fun <T : JavaTypeName> Iterable<T>.toKPoet() = map { it.toKPoet() }

fun JavaParameterSpec.toKPoet(): KotlinParameterSpec
= KotlinParameterSpec.builder(
name,
type.toKPoet(),
*modifiers.toKModifier().toTypedArray()
).build()
fun JavaParameterSpec.toKPoet(): KotlinParameterSpec {

// A param name in java might be reserved in kotlin
val paramName = if (name in KOTLIN_KEYWORDS) name + "Param" else name

return KotlinParameterSpec.builder(
paramName,
type.toKPoet(),
*modifiers.toKModifier().toTypedArray()
).build()

}

fun Iterable<JavaParameterSpec>.toKParams() = map { it.toKPoet() }

Expand All @@ -97,3 +184,35 @@ fun Modifier.toKModifier() = when (this) {
else -> null
}

// https://github.com/JetBrains/kotlin/blob/master/core/descriptors/src/org/jetbrains/kotlin/renderer/KeywordStringsGenerated.java
private val KOTLIN_KEYWORDS = setOf(
"package",
"as",
"typealias",
"class",
"this",
"super",
"val",
"var",
"fun",
"for",
"null",
"true",
"false",
"is",
"in",
"throw",
"return",
"break",
"continue",
"object",
"if",
"try",
"else",
"while",
"do",
"when",
"interface",
"typeof"
)

0 comments on commit 715cadd

Please sign in to comment.