Create XEnumTypeElement for enum declarations
Enum classes are slightly different than other classes where they have
inner classes/properties that are actually enum constants.
Moreoever, KSP returns those constants as class declarations whereas
java AP returns them as fields.
To avoid possibly confusions, this CL adds a new XEnumTypeElement with a
field to receive enum constant names.
Subsequently, java implementation of XTypeElement was returning enum
constants as if they are fields, I've removed them from that list as
well.
As part of this change, I've removed XType.isEnum and instead added
XTypeElement.isEnum which can also do the auto cast to XEnumTypeElement.
Also refactored EnumColumnTypeAdapter to use the new APIs.
Note that there is an existing bug in KSP where it does not report enum
constants properly from java sources so for now, I've excluded that case
from tests: https://github.com/google/ksp/issues/234
I've copied the EnumColumnTypeAdapterTest to also run in room kotlin
test app so that we can ensure KSP is working propery (it was failing).
Bug: 160322705
Bug: 173236324
Test: XTypeElementTest, EnumColumnTypeAdapterTest.kt
Change-Id: Ie6d74cc5ccd93ed6be2687cdb382baf9dee16a0f
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/XEnumTypeElement.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/XEnumTypeElement.kt
new file mode 100644
index 0000000..5d35646
--- /dev/null
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/XEnumTypeElement.kt
@@ -0,0 +1,33 @@
+/*
+ * 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
+
+import kotlin.contracts.contract
+
+/**
+ * Type elements that represent Enum declarations.
+ */
+interface XEnumTypeElement : XTypeElement {
+ val enumConstantNames: Set<String>
+}
+
+fun XTypeElement.isEnum(): Boolean {
+ contract {
+ returns(true) implies (this@isEnum is XEnumTypeElement)
+ }
+ return this is XEnumTypeElement
+}
\ No newline at end of file
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt
index 13d02ef..52388ed 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt
@@ -107,11 +107,6 @@
fun isNone(): Boolean
/**
- * Returns true if this represented by an [Enum].
- */
- fun isEnum(): Boolean
-
- /**
* Returns `true` if this is the same raw type as [other]
*/
fun isTypeOf(other: KClass<*>): Boolean
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/ElementExt.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/ElementExt.kt
index 8959ca0..d9bee51 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/ElementExt.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/ElementExt.kt
@@ -20,6 +20,7 @@
import com.google.auto.common.MoreElements
import com.google.auto.common.MoreTypes
import javax.lang.model.element.Element
+import javax.lang.model.element.ElementKind
import javax.lang.model.element.TypeElement
import javax.lang.model.element.VariableElement
import javax.lang.model.type.TypeKind
@@ -39,6 +40,8 @@
/**
* Returns all fields including private fields (including private fields in super). Removes
* duplicate fields if class has a field with the same name as the parent.
+ * Note that enum constants are not included in the list even thought they are fields in java.
+ * To access enum constants, use [JavacTypeElement.JavacEnumTypeElement].
*/
internal fun TypeElement.getAllFieldsIncludingPrivateSupers(
elementUtils: Elements
@@ -46,6 +49,7 @@
val selection = ElementFilter
.fieldsIn(elementUtils.getAllMembers(this))
.filterIsInstance<VariableElement>()
+ .filterNot { it.kind == ElementKind.ENUM_CONSTANT }
.toMutableSet()
val selectionNames = selection.mapTo(mutableSetOf()) {
it.simpleName
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacProcessingEnv.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacProcessingEnv.kt
index 8e6e544..2266ced 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacProcessingEnv.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacProcessingEnv.kt
@@ -46,8 +46,8 @@
findElement = { qName ->
delegate.elementUtils.getTypeElement(qName)
},
- wrap = {
- JavacTypeElement(this, it)
+ wrap = { typeElement ->
+ JavacTypeElement.create(this, typeElement)
},
getQName = {
it.qualifiedName.toString()
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt
index 13889f5..e220f6b 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt
@@ -24,7 +24,6 @@
import androidx.room.compiler.processing.ksp.ERROR_TYPE_NAME
import androidx.room.compiler.processing.safeTypeName
import com.google.auto.common.MoreTypes
-import javax.lang.model.element.ElementKind
import javax.lang.model.type.TypeKind
import javax.lang.model.type.TypeMirror
import kotlin.reflect.KClass
@@ -167,7 +166,4 @@
// a boxed primitive to be marked as non-null.
return copyWithNullability(XNullability.NONNULL)
}
-
- override fun isEnum() = typeMirror.kind == TypeKind.DECLARED &&
- MoreTypes.asElement(typeMirror).kind == ElementKind.ENUM
}
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
index 2395061..acaffb7 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
@@ -16,10 +16,12 @@
package androidx.room.compiler.processing.javac
+import androidx.room.compiler.processing.XEnumTypeElement
import androidx.room.compiler.processing.XFieldElement
import androidx.room.compiler.processing.XHasModifiers
import androidx.room.compiler.processing.XMethodElement
import androidx.room.compiler.processing.XTypeElement
+import androidx.room.compiler.processing.javac.JavacTypeElement.JavacEnumTypeElement
import androidx.room.compiler.processing.javac.kotlin.KotlinMetadataElement
import com.google.auto.common.MoreElements
import com.google.auto.common.MoreTypes
@@ -29,7 +31,7 @@
import javax.lang.model.type.TypeKind
import javax.lang.model.util.ElementFilter
-internal class JavacTypeElement(
+internal sealed class JavacTypeElement(
env: JavacProcessingEnv,
override val element: TypeElement
) : JavacElement(env, element), XTypeElement, XHasModifiers by JavacHasModifiers(element) {
@@ -142,4 +144,38 @@
override val equalityItems: Array<out Any?> by lazy {
arrayOf(element)
}
+
+ class DefaultJavacTypeElement(
+ env: JavacProcessingEnv,
+ element: TypeElement
+ ) : JavacTypeElement(env, element)
+
+ class JavacEnumTypeElement(
+ env: JavacProcessingEnv,
+ element: TypeElement
+ ) : JavacTypeElement(env, element), XEnumTypeElement {
+ init {
+ check(element.kind == ElementKind.ENUM)
+ }
+
+ override val enumConstantNames: Set<String> by lazy {
+ element.enclosedElements.filter {
+ it.kind == ElementKind.ENUM_CONSTANT
+ }.mapTo(mutableSetOf()) {
+ it.simpleName.toString()
+ }
+ }
+ }
+
+ companion object {
+ fun create(
+ env: JavacProcessingEnv,
+ typeElement: TypeElement
+ ): JavacTypeElement {
+ return when (typeElement.kind) {
+ ElementKind.ENUM -> JavacEnumTypeElement(env, typeElement)
+ else -> DefaultJavacTypeElement(env, typeElement)
+ }
+ }
+ }
}
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
index fee52da..ba8ae4e 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
@@ -57,8 +57,8 @@
// it is best to just not cache them
it.qualifiedName?.asString()
},
- wrap = {
- KspTypeElement(this, it)
+ wrap = { classDeclaration ->
+ KspTypeElement.create(this, classDeclaration)
}
)
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
index dcdfca1..4284a63 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
@@ -21,7 +21,6 @@
import androidx.room.compiler.processing.XType
import androidx.room.compiler.processing.tryBox
import androidx.room.compiler.processing.tryUnbox
-import com.google.devtools.ksp.symbol.ClassKind
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSType
import com.google.devtools.ksp.symbol.KSTypeReference
@@ -139,10 +138,6 @@
return ksType.toString()
}
- override fun isEnum(): Boolean {
- return (ksType.declaration as? KSClassDeclaration)?.classKind == ClassKind.ENUM_CLASS
- }
-
abstract override fun boxed(): KspType
/**
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
index 6712e14..437d9a3 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeElement.kt
@@ -18,6 +18,7 @@
import androidx.room.compiler.processing.XAnnotated
import androidx.room.compiler.processing.XConstructorElement
+import androidx.room.compiler.processing.XEnumTypeElement
import androidx.room.compiler.processing.XFieldElement
import androidx.room.compiler.processing.XHasModifiers
import androidx.room.compiler.processing.XMethodElement
@@ -40,7 +41,7 @@
import com.google.devtools.ksp.symbol.Origin
import com.squareup.javapoet.ClassName
-internal class KspTypeElement(
+internal sealed class KspTypeElement(
env: KspProcessingEnv,
override val declaration: KSClassDeclaration
) : KspElement(env, declaration),
@@ -342,4 +343,36 @@
override fun toString(): String {
return declaration.toString()
}
+
+ private class DefaultKspTypeElement(
+ env: KspProcessingEnv,
+ declaration: KSClassDeclaration
+ ) : KspTypeElement(env, declaration)
+
+ private class KspEnumTypeElement(
+ env: KspProcessingEnv,
+ declaration: KSClassDeclaration
+ ) : KspTypeElement(env, declaration), XEnumTypeElement {
+ override val enumConstantNames: Set<String> by lazy {
+ // TODO this does not work for java sources
+ // https://github.com/google/ksp/issues/234
+ declaration.declarations.filter {
+ it is KSClassDeclaration && it.classKind == ClassKind.ENUM_ENTRY
+ }.mapTo(mutableSetOf()) {
+ it.simpleName.asString()
+ }
+ }
+ }
+
+ companion object {
+ fun create(
+ env: KspProcessingEnv,
+ ksClassDeclaration: KSClassDeclaration
+ ): KspTypeElement {
+ return when (ksClassDeclaration.classKind) {
+ ClassKind.ENUM_CLASS -> KspEnumTypeElement(env, ksClassDeclaration)
+ else -> DefaultKspTypeElement(env, ksClassDeclaration)
+ }
+ }
+ }
}
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/ResolverExt.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/ResolverExt.kt
index 6e7b5bc..21ab8ac 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/ResolverExt.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/ResolverExt.kt
@@ -144,12 +144,8 @@
return declaration.simpleName.asString()
} catch (cannotFindDeclaration: IllegalStateException) {
// workaround for https://github.com/google/ksp/issues/200
- val name = declaration.simpleName.asString()
- if (name.startsWith("get") or name.startsWith("set")) {
- return name
- }
- // we don't know why it happened so we better throw
- throw cannotFindDeclaration
+ // happens for setters getters as well as `values` method of Enum descriptor
+ return declaration.simpleName.asString()
}
}
diff --git a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
index ddd18f3..7e46099 100644
--- a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
@@ -18,13 +18,14 @@
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.XTestInvocation
+import androidx.room.compiler.processing.util.compileFiles
import androidx.room.compiler.processing.util.getAllFieldNames
import androidx.room.compiler.processing.util.getField
import androidx.room.compiler.processing.util.getMethod
import androidx.room.compiler.processing.util.runKspTest
import androidx.room.compiler.processing.util.runProcessorTest
-import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.ParameterizedTypeName
import com.squareup.javapoet.TypeName
@@ -764,13 +765,84 @@
)
subjects.forEach {
- Truth.assertWithMessage(it)
+ assertWithMessage(it)
.that(invocation.processingEnv.requireTypeElement(it).findPrimaryConstructor())
.isNull()
}
}
}
+ @Test
+ fun enumTypeElement() {
+ fun createSources(packageName: String) = listOf(
+ Source.kotlin(
+ "$packageName/KotlinEnum.kt",
+ """
+ package $packageName
+ enum class KotlinEnum(private val x:Int) {
+ VAL1(1),
+ VAL2(2);
+
+ fun enumMethod():Unit {}
+ }
+ """.trimIndent()
+ ),
+ Source.java(
+ "$packageName.JavaEnum",
+ """
+ package $packageName;
+ public enum JavaEnum {
+ VAL1(1),
+ VAL2(2);
+
+ private int x;
+
+ JavaEnum(int x) {
+ this.x = x;
+ }
+ void enumMethod() {}
+ }
+ """.trimIndent()
+ )
+ )
+ val classpath = compileFiles(
+ createSources("lib")
+ )
+ runProcessorTest(
+ sources = createSources("app"),
+ classpath = listOf(classpath)
+ ) { invocation ->
+ listOf(
+ "lib.KotlinEnum", "lib.JavaEnum",
+ "app.KotlinEnum", "app.JavaEnum"
+ ).forEach { qName ->
+ val typeElement = invocation.processingEnv.requireTypeElement(qName)
+ assertWithMessage("$qName is enum")
+ .that(typeElement.isEnum())
+ .isTrue()
+ assertWithMessage("$qName does not report enum constants in methods")
+ .that(typeElement.getDeclaredMethods().map { it.name })
+ .run {
+ contains("enumMethod")
+ containsNoneOf("VAL1", "VAL2")
+ }
+ if (qName != "app.JavaEnum" || !invocation.isKsp) {
+ // KSP does not properly return enum constants for java sources yet
+ // https://github.com/google/ksp/issues/234
+ assertWithMessage("$qName can return enum constants")
+ .that((typeElement as XEnumTypeElement).enumConstantNames)
+ .containsExactly("VAL1", "VAL2")
+ assertWithMessage("$qName does not report enum constants in fields")
+ .that(typeElement.getAllFieldNames())
+ .run {
+ contains("x")
+ containsNoneOf("VAL1", "VAL2")
+ }
+ }
+ }
+ }
+ }
+
/**
* it is good to exclude methods coming from Object when testing as they differ between KSP
* and KAPT but irrelevant for Room.
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt b/room/compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
index f12c2b1..0a3c658 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
@@ -18,6 +18,7 @@
import androidx.room.compiler.processing.XType
import androidx.room.compiler.processing.isArray
+import androidx.room.compiler.processing.isEnum
import androidx.room.ext.CommonTypeNames
import androidx.room.ext.GuavaBaseTypeNames
import androidx.room.ext.isEntityElement
@@ -346,8 +347,9 @@
}
private fun createEnumTypeAdapter(type: XType): ColumnTypeAdapter? {
- if (type.isEnum()) {
- return EnumColumnTypeAdapter(type)
+ val typeElement = type.typeElement ?: return null
+ if (typeElement.isEnum()) {
+ return EnumColumnTypeAdapter(typeElement)
}
return null
}
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 2f01519..879019b 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
@@ -16,8 +16,7 @@
package androidx.room.solver.types
-import androidx.room.compiler.processing.XFieldElement
-import androidx.room.compiler.processing.XType
+import androidx.room.compiler.processing.XEnumTypeElement
import androidx.room.ext.CommonTypeNames.ILLEGAL_ARG_EXCEPTION
import androidx.room.ext.L
import androidx.room.ext.N
@@ -28,15 +27,14 @@
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) {
+class EnumColumnTypeAdapter(
+ private val enumTypeElement: XEnumTypeElement
+) : ColumnTypeAdapter(enumTypeElement.type, TEXT) {
override fun readFromCursor(
outVarName: String,
cursorVarName: String,
@@ -92,8 +90,8 @@
beginControlFlow("if ($N == null)", param)
addStatement("return null")
nextControlFlow("switch ($N)", param)
- getEnumConstantElements().forEach { enumConstant ->
- addStatement("case $L: return $S", enumConstant.name, enumConstant.name)
+ enumTypeElement.enumConstantNames.forEach { enumConstantName ->
+ addStatement("case $L: return $S", enumConstantName, enumConstantName)
}
addStatement(
"default: throw new $T($S + $N)",
@@ -129,11 +127,10 @@
beginControlFlow("if ($N == null)", param)
addStatement("return null")
nextControlFlow("switch ($N)", param)
- getEnumConstantElements().forEach {
- enumConstant ->
+ enumTypeElement.enumConstantNames.forEach { enumConstantName ->
addStatement(
"case $S: return $T.$L",
- enumConstant.name, out.typeName, enumConstant.name
+ enumConstantName, out.typeName, enumConstantName
)
}
addStatement(
@@ -147,14 +144,4 @@
}
})
}
-
- private fun getEnumConstantElements(): List<XFieldElement> {
- // TODO: Switch below logic to use`getDeclaredFields` when the
- // functionality is available in the XTypeElement API
- val typeElementFields = out.typeElement!!.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/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/EnumColumnTypeAdapterTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/EnumColumnTypeAdapterTest.kt
new file mode 100644
index 0000000..2911d71
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/EnumColumnTypeAdapterTest.kt
@@ -0,0 +1,177 @@
+/*
+ * 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.integration.kotlintestapp.test
+
+import android.content.Context
+import androidx.room.Dao
+import androidx.room.Database
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import androidx.room.Query
+import androidx.room.Room
+import androidx.room.RoomDatabase
+import androidx.room.TypeConverter
+import androidx.room.TypeConverters
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class EnumColumnTypeAdapterTest {
+ private lateinit var db: EnumColumnTypeAdapterDatabase
+
+ @Entity
+ data class EntityWithEnum(
+ @PrimaryKey
+ val id: Long,
+ val fruit: Fruit
+ )
+
+ @Entity
+ data class EntityWithOneWayEnum(
+ @PrimaryKey
+ val id: Long,
+ val color: Color
+ )
+
+ @Entity
+ data class ComplexEntityWithEnum(
+ @PrimaryKey
+ val id: Long,
+ val season: Season
+ )
+
+ enum class Color {
+ RED, GREEN
+ }
+
+ enum class Fruit {
+ BANANA, STRAWBERRY, WILDBERRY
+ }
+
+ enum class Season(private val text: String) {
+ SUMMER("Sunny"), SPRING("Warm"), WINTER("Cold"), AUTUMN("Rainy");
+ }
+
+ @Dao
+ interface SampleDao {
+ @Query("INSERT INTO EntityWithEnum (id, fruit) VALUES (:id, :fruit)")
+ fun insert(id: Long, fruit: Fruit?): Long
+
+ @Query("SELECT * FROM EntityWithEnum WHERE id = :id")
+ fun getValueWithId(id: Long): EntityWithEnum
+ }
+
+ @Dao
+ interface SampleDaoWithOneWayConverter {
+ @Query("INSERT INTO EntityWithOneWayEnum (id, color) VALUES (:id, :colorInt)")
+ fun insert(id: Long, colorInt: Int): Long
+
+ @Query("SELECT * FROM EntityWithOneWayEnum WHERE id = :id")
+ fun getValueWithId(id: Long): EntityWithOneWayEnum
+ }
+
+ class ColorTypeConverter {
+ @TypeConverter
+ fun fromIntToColorEnum(colorInt: Int): Color {
+ return if (colorInt == 1) {
+ Color.RED
+ } else {
+ Color.GREEN
+ }
+ }
+ }
+
+ @Dao
+ interface SampleDaoWithComplexEnum {
+ @Query("INSERT INTO ComplexEntityWithEnum (id, season) VALUES (:id, :season)")
+ fun insertComplex(id: Long, season: Season?): Long
+
+ @Query("SELECT * FROM ComplexEntityWithEnum WHERE id = :id")
+ fun getComplexValueWithId(id: Long): ComplexEntityWithEnum
+ }
+
+ @Database(
+ entities = [
+ EntityWithEnum::class,
+ ComplexEntityWithEnum::class,
+ EntityWithOneWayEnum::class
+ ],
+ version = 1,
+ exportSchema = false
+ )
+ @TypeConverters(
+ ColorTypeConverter::class
+ )
+ abstract class EnumColumnTypeAdapterDatabase : RoomDatabase() {
+ abstract fun dao(): SampleDao
+ abstract fun oneWayDao(): SampleDaoWithOneWayConverter
+ abstract fun complexDao(): SampleDaoWithComplexEnum
+ }
+
+ @Before
+ fun initDb() {
+ val context = ApplicationProvider.getApplicationContext<Context>()
+ db = Room.inMemoryDatabaseBuilder(
+ context,
+ EnumColumnTypeAdapterDatabase::class.java
+ ).build()
+ }
+
+ @After
+ fun teardown() {
+ db.close()
+ }
+
+ @Test
+ fun readAndWriteEnumToDatabase() {
+ db.dao().insert(1, Fruit.BANANA)
+ db.dao().insert(2, Fruit.STRAWBERRY)
+ assertThat(
+ db.dao().getValueWithId(1).fruit
+ ).isEqualTo(Fruit.BANANA)
+ assertThat(
+ db.dao().getValueWithId(2).fruit
+ ).isEqualTo(Fruit.STRAWBERRY)
+ }
+
+ @Test
+ fun writeOneWayEnumToDatabase() {
+ db.oneWayDao().insert(1, 1)
+ assertThat(
+ db.oneWayDao().getValueWithId(1).color
+ ).isEqualTo(
+ Color.RED
+ )
+ }
+
+ @Test
+ fun filterOutComplexEnumTest() {
+ db.complexDao().insertComplex(1, Season.AUTUMN)
+ assertThat(
+ db.complexDao().getComplexValueWithId(1).season
+ ).isEqualTo(
+ Season.AUTUMN
+ )
+ }
+}