blob: 7255e20af17926a5b8452bb77d27220c0a14c3bb [file] [log] [blame]
/*
* 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.
* 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.solver.types
import androidx.room.compiler.codegen.CodeLanguage
import androidx.room.compiler.codegen.XCodeBlock
import androidx.room.compiler.codegen.XFunSpec
import androidx.room.compiler.processing.XEnumTypeElement
import androidx.room.compiler.processing.XNullability
import androidx.room.compiler.processing.XType
import androidx.room.ext.CommonTypeNames
import androidx.room.ext.ExceptionTypeNames
import androidx.room.parser.SQLTypeAffinity.TEXT
import androidx.room.solver.CodeGenScope
import androidx.room.writer.TypeWriter
/**
* Uses enum string representation.
*/
class EnumColumnTypeAdapter(
private val enumTypeElement: XEnumTypeElement,
out: XType
) : ColumnTypeAdapter(out, TEXT) {
override fun readFromCursor(
outVarName: String,
cursorVarName: String,
indexVarName: String,
scope: CodeGenScope
) {
val stringToEnumMethod = stringToEnumMethod(scope)
scope.builder.apply {
fun XCodeBlock.Builder.addGetStringStatement() {
addStatement(
"%L = %N(%L.getString(%L))",
outVarName,
stringToEnumMethod,
cursorVarName,
indexVarName
)
}
if (out.nullability == XNullability.NONNULL) {
addGetStringStatement()
} else {
beginControlFlow("if (%L.isNull(%L))", cursorVarName, indexVarName)
.addStatement("%L = null", outVarName)
nextControlFlow("else")
.addGetStringStatement()
endControlFlow()
}
}
}
override fun bindToStmt(
stmtName: String,
indexVarName: String,
valueVarName: String,
scope: CodeGenScope
) {
val enumToStringMethod = enumToStringMethod(scope)
scope.builder.apply {
fun XCodeBlock.Builder.addBindStringStatement() {
addStatement(
"%L.bindString(%L, %N(%L))",
stmtName, indexVarName, enumToStringMethod, valueVarName,
)
}
if (out.nullability == XNullability.NONNULL) {
addBindStringStatement()
} else {
beginControlFlow("if (%L == null)", valueVarName)
.addStatement("%L.bindNull(%L)", stmtName, indexVarName)
nextControlFlow("else")
addBindStringStatement()
endControlFlow()
}
}
}
private fun enumToStringMethod(scope: CodeGenScope): XFunSpec {
val funSpec = object : TypeWriter.SharedFunctionSpec(
out.typeElement!!.name + "_enumToString"
) {
val paramName = "_value"
override fun getUniqueKey(): String {
return "enumToString_" + enumTypeElement.asClassName().toString()
}
override fun prepare(
methodName: String,
writer: TypeWriter,
builder: XFunSpec.Builder
) {
val body = XCodeBlock.builder(builder.language).apply {
when (writer.codeLanguage) {
// Use a switch control flow
CodeLanguage.JAVA -> {
beginControlFlow("switch (%L)", paramName)
enumTypeElement.entries.map { it.name }.forEach { enumConstantName ->
addStatement(
"case %L: return %S",
enumConstantName, enumConstantName
)
}
addStatement(
"default: throw new %T(%S + %L)",
ExceptionTypeNames.ILLEGAL_ARG_EXCEPTION,
ENUM_TO_STRING_ERROR_MSG,
paramName
)
endControlFlow()
}
// Use a when control flow, note that it is exhaustive and there is no need
// or an `else` case.
CodeLanguage.KOTLIN -> {
beginControlFlow("return when (%L)", paramName)
enumTypeElement.entries.map { it.name }.forEach { enumConstantName ->
addStatement(
"%T.%L -> %S",
enumTypeElement.asClassName(),
enumConstantName,
enumConstantName
)
}
endControlFlow()
}
}
}.build()
builder.apply {
returns(CommonTypeNames.STRING.copy(nullable = false))
addParameter(
enumTypeElement.asClassName(),
paramName
)
addCode(body)
}
}
}
return scope.writer.getOrCreateFunction(funSpec)
}
private fun stringToEnumMethod(scope: CodeGenScope): XFunSpec {
val funSpec = object : TypeWriter.SharedFunctionSpec(
out.typeElement!!.name + "_stringToEnum"
) {
val paramName = "_value"
override fun getUniqueKey(): String {
return "stringToEnum_" + enumTypeElement.asClassName().toString()
}
override fun prepare(
methodName: String,
writer: TypeWriter,
builder: XFunSpec.Builder
) {
val body = XCodeBlock.builder(builder.language).apply {
when (writer.codeLanguage) {
// Use a switch control flow
CodeLanguage.JAVA -> {
beginControlFlow("switch (%L)", paramName)
enumTypeElement.entries.map { it.name }.forEach { enumConstantName ->
addStatement(
"case %S: return %T.%L",
enumConstantName,
enumTypeElement.asClassName(),
enumConstantName
)
}
addStatement(
"default: throw new %T(%S + %L)",
ExceptionTypeNames.ILLEGAL_ARG_EXCEPTION,
STRING_TO_ENUM_ERROR_MSG,
paramName
)
endControlFlow()
}
// Use a when control flow
CodeLanguage.KOTLIN -> {
beginControlFlow("return when (%L)", paramName)
enumTypeElement.entries.map { it.name }.forEach { enumConstantName ->
addStatement(
"%S -> %T.%L",
enumConstantName,
enumTypeElement.asClassName(),
enumConstantName
)
}
addStatement(
"else -> throw %T(%S + %L)",
ExceptionTypeNames.ILLEGAL_ARG_EXCEPTION,
STRING_TO_ENUM_ERROR_MSG,
paramName
)
endControlFlow()
}
}
}.build()
builder.apply {
returns(enumTypeElement.asClassName())
addParameter(
CommonTypeNames.STRING.copy(nullable = false),
paramName
)
addCode(body)
}
}
}
return scope.writer.getOrCreateFunction(funSpec)
}
companion object {
private const val ENUM_TO_STRING_ERROR_MSG =
"Can't convert enum to string, unknown enum value: "
private const val STRING_TO_ENUM_ERROR_MSG =
"Can't convert value to enum, unknown value: "
}
}