Merge "Adding functionality to handle proguard when storing enums in databases." into androidx-master-dev
diff --git a/room/compiler/src/main/kotlin/androidx/room/ext/javapoet_ext.kt b/room/compiler/src/main/kotlin/androidx/room/ext/javapoet_ext.kt
index 3babfd8..ba1a146 100644
--- a/room/compiler/src/main/kotlin/androidx/room/ext/javapoet_ext.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/ext/javapoet_ext.kt
@@ -131,6 +131,9 @@
val STRING = ClassName.get("java.lang", "String")
val INTEGER = ClassName.get("java.lang", "Integer")
val OPTIONAL = ClassName.get("java.util", "Optional")
+ val ILLEGAL_ARG_EXCEPTION = ClassName.get(
+ "java.lang", "IllegalArgumentException"
+ )
}
object GuavaBaseTypeNames {
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/types/EnumColumnTypeAdapter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/types/EnumColumnTypeAdapter.kt
index 1194b1d..090dfc8 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/types/EnumColumnTypeAdapter.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/types/EnumColumnTypeAdapter.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -16,29 +16,38 @@
package androidx.room.solver.types
+import androidx.room.compiler.processing.XFieldElement
import androidx.room.compiler.processing.XType
+import androidx.room.ext.CommonTypeNames.ILLEGAL_ARG_EXCEPTION
import androidx.room.ext.L
+import androidx.room.ext.N
+import androidx.room.ext.S
import androidx.room.ext.T
import androidx.room.parser.SQLTypeAffinity.TEXT
import androidx.room.solver.CodeGenScope
+import androidx.room.writer.ClassWriter
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterSpec
+import java.util.Locale
+import javax.lang.model.element.ElementKind
+import javax.lang.model.element.Modifier
/**
* Uses enum string representation.
*/
class EnumColumnTypeAdapter(out: XType) :
ColumnTypeAdapter(out, TEXT) {
- private val enumTypeName = out.typeName
override fun readFromCursor(
outVarName: String,
cursorVarName: String,
indexVarName: String,
scope: CodeGenScope
) {
+ val stringToEnumMethod = stringToEnumMethod(scope)
scope.builder()
.addStatement(
- "$L = $T.valueOf($L.getString($L))", outVarName, enumTypeName,
- cursorVarName,
- indexVarName
+ "$L = $N($L.getString($L))",
+ outVarName, stringToEnumMethod, cursorVarName, indexVarName
)
}
@@ -48,12 +57,102 @@
valueVarName: String,
scope: CodeGenScope
) {
+ val enumToStringMethod = enumToStringMethod(scope)
scope.builder().apply {
beginControlFlow("if ($L == null)", valueVarName)
.addStatement("$L.bindNull($L)", stmtName, indexVarName)
nextControlFlow("else")
- .addStatement("$L.bindString($L, $L.name())", stmtName, indexVarName, valueVarName)
+ .addStatement(
+ "$L.bindString($L, $N($L))",
+ stmtName, indexVarName, enumToStringMethod, valueVarName
+ )
endControlFlow()
}
}
-}
\ No newline at end of file
+
+ private fun enumToStringMethod(scope: CodeGenScope): MethodSpec {
+ return scope.writer.getOrCreateMethod(object :
+ ClassWriter.SharedMethodSpec(out.asTypeElement().name + "_enumToString") {
+ override fun getUniqueKey(): String {
+ return "enumToString_" + out.typeName.toString()
+ }
+
+ override fun prepare(
+ methodName: String,
+ writer: ClassWriter,
+ builder: MethodSpec.Builder
+ ) {
+ builder.apply {
+ addModifiers(Modifier.PRIVATE)
+ returns(String::class.java)
+ val param = ParameterSpec.builder(
+ out.typeName, "_value", Modifier.FINAL
+ ).build()
+ addParameter(param)
+ beginControlFlow("if ($N == null)", param)
+ addStatement("return null")
+ nextControlFlow("switch ($N)", param)
+ getEnumConstantElements().forEach { enumConstant ->
+ addStatement("case $L: return $S", enumConstant.name, enumConstant.name)
+ }
+ addStatement(
+ "default: throw new $T($S)",
+ ILLEGAL_ARG_EXCEPTION,
+ "Can't convert ${param.name} to string, unknown enum value."
+ )
+ endControlFlow()
+ }
+ }
+ })
+ }
+
+ private fun stringToEnumMethod(scope: CodeGenScope): MethodSpec {
+ return scope.writer.getOrCreateMethod(object :
+ ClassWriter.SharedMethodSpec(out.asTypeElement().name + "_stringToEnum") {
+ override fun getUniqueKey(): String {
+ return out.typeName.toString()
+ }
+
+ override fun prepare(
+ methodName: String,
+ writer: ClassWriter,
+ builder: MethodSpec.Builder
+ ) {
+ builder.apply {
+ addModifiers(Modifier.PRIVATE)
+ returns(out.typeName)
+ val param = ParameterSpec.builder(
+ String::class.java, "_value", Modifier.FINAL
+ ).build()
+ addParameter(param)
+ beginControlFlow("if ($N == null)", param)
+ addStatement("return null")
+ nextControlFlow("switch ($N)", param)
+ getEnumConstantElements().forEach {
+ enumConstant ->
+ addStatement(
+ "case $S: return $T.$L",
+ enumConstant.name, out.typeName, enumConstant.name
+ )
+ }
+ addStatement(
+ "default: throw new $T($S)",
+ ILLEGAL_ARG_EXCEPTION,
+ "Can't convert ${param.name} to enum, unknown value."
+ )
+ endControlFlow()
+ }
+ }
+ })
+ }
+
+ private fun getEnumConstantElements(): List<XFieldElement> {
+ // TODO: Switch below logic to use`getDeclaredFields` when the
+ // functionality is available in the XTypeElement API
+ val typeElementFields = out.asTypeElement().getAllFieldsIncludingPrivateSupers()
+ return typeElementFields.filter {
+ // TODO: (b/173236324) Add kind to the X abstraction API to avoid using kindName()
+ ElementKind.ENUM_CONSTANT.toString().toLowerCase(Locale.US) == it.kindName()
+ }
+ }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/EnumColumnTypeAdapterTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/EnumColumnTypeAdapterTest.java
index 4c0df94..94cccaf 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/EnumColumnTypeAdapterTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/EnumColumnTypeAdapterTest.java
@@ -42,6 +42,7 @@
public class EnumColumnTypeAdapterTest {
private EnumColumnTypeAdapterDatabase mDb;
+ private EnumColumnTypeAdapterDatabase mDbComplex;
@Entity
public static class EntityWithEnum {
@@ -49,6 +50,12 @@
public Long id;
public Fruit fruit;
}
+ @Entity
+ public static class ComplexEntityWithEnum {
+ @PrimaryKey
+ public Long id;
+ public Season mSeason;
+ }
public enum Fruit {
BANANA,
@@ -56,6 +63,19 @@
WILDBERRY
}
+ public enum Season {
+ SUMMER("Sunny"),
+ SPRING("Warm"),
+ WINTER("Cold"),
+ AUTUMN("Rainy");
+
+ private final String mSeason;
+
+ Season(String mSeason) {
+ this.mSeason = mSeason;
+ }
+ }
+
@Dao
public interface SampleDao {
@Query("INSERT INTO EntityWithEnum (id, fruit) VALUES (:id, :fruit)")
@@ -65,9 +85,20 @@
EntityWithEnum getValueWithId(long id);
}
- @Database(entities = {EntityWithEnum.class}, version = 1, exportSchema = false)
+ @Dao
+ public interface SampleDaoWithComplexEnum {
+ @Query("INSERT INTO ComplexEntityWithEnum (id, mSeason) VALUES (:id, :season)")
+ long insertComplex(long id, Season season);
+
+ @Query("SELECT * FROM ComplexEntityWithEnum WHERE id = :id")
+ ComplexEntityWithEnum getComplexValueWithId(long id);
+ }
+
+ @Database(entities = {EntityWithEnum.class, ComplexEntityWithEnum.class}, version = 1,
+ exportSchema = false)
public abstract static class EnumColumnTypeAdapterDatabase extends RoomDatabase {
public abstract EnumColumnTypeAdapterTest.SampleDao dao();
+ public abstract EnumColumnTypeAdapterTest.SampleDaoWithComplexEnum complexDao();
}
@Before
@@ -77,6 +108,10 @@
context,
EnumColumnTypeAdapterDatabase.class)
.build();
+ mDbComplex = Room.inMemoryDatabaseBuilder(
+ context,
+ EnumColumnTypeAdapterDatabase.class)
+ .build();
}
@Test
@@ -87,4 +122,11 @@
assertThat(mDb.dao().getValueWithId(1).fruit, is(equalTo(Fruit.BANANA)));
assertThat(mDb.dao().getValueWithId(2).fruit, is(equalTo(Fruit.STRAWBERRY)));
}
+
+ @Test
+ public void filterOutComplexEnumTest() {
+ final long id1 = mDbComplex.complexDao().insertComplex(1, Season.AUTUMN);
+ assertThat(mDbComplex.complexDao().getComplexValueWithId(1).mSeason,
+ is(equalTo(Season.AUTUMN)));
+ }
}