/*
* 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.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) {
override fun readFromCursor(
outVarName: String,
cursorVarName: String,
indexVarName: String,
scope: CodeGenScope
) {
val stringToEnumMethod = stringToEnumMethod(scope)
scope.builder()
.addStatement(
"$L = $N($L.getString($L))",
outVarName, stringToEnumMethod, cursorVarName, indexVarName
)
}
override fun bindToStmt(
stmtName: String,
indexVarName: String,
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, $N($L))",
stmtName, indexVarName, enumToStringMethod, valueVarName
)
endControlFlow()
}
}
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()
}
}
}