/*
* Copyright 2018 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.processor
import androidx.room.ext.KotlinTypeNames
import androidx.room.ext.L
import androidx.room.ext.N
import androidx.room.ext.RoomCoroutinesTypeNames
import androidx.room.ext.T
import androidx.room.ext.getSuspendFunctionReturnType
import androidx.room.kotlin.KotlinMetadataElement
import androidx.room.parser.ParsedQuery
import androidx.room.solver.prepared.binder.CallablePreparedQueryResultBinder.Companion.createPreparedBinder
import androidx.room.solver.prepared.binder.PreparedQueryResultBinder
import androidx.room.solver.query.result.CoroutineResultBinder
import androidx.room.solver.query.result.QueryResultBinder
import androidx.room.solver.shortcut.binder.CallableDeleteOrUpdateMethodBinder.Companion.createDeleteOrUpdateBinder
import androidx.room.solver.shortcut.binder.CallableInsertMethodBinder.Companion.createInsertBinder
import androidx.room.solver.shortcut.binder.DeleteOrUpdateMethodBinder
import androidx.room.solver.shortcut.binder.InsertMethodBinder
import androidx.room.solver.transaction.binder.InstantTransactionMethodBinder
import androidx.room.solver.transaction.binder.CoroutineTransactionMethodBinder
import androidx.room.solver.transaction.binder.TransactionMethodBinder
import androidx.room.solver.transaction.result.TransactionMethodAdapter
import androidx.room.vo.QueryParameter
import androidx.room.vo.ShortcutQueryParameter
import androidx.room.vo.TransactionMethod
import com.google.auto.common.MoreTypes
import javax.lang.model.element.ExecutableElement
import javax.lang.model.element.VariableElement
import javax.lang.model.type.DeclaredType
import javax.lang.model.type.TypeMirror
/**
* Delegate class with common functionality for DAO method processors.
*/
abstract class MethodProcessorDelegate(
val context: Context,
val containing: DeclaredType,
val executableElement: ExecutableElement,
protected val classMetadata: KotlinMetadataElement?
) {
abstract fun extractReturnType(): TypeMirror
abstract fun extractParams(): List<VariableElement>
fun extractQueryParams(): List<QueryParameter> {
val kotlinParameterNames = classMetadata?.getParameterNames(executableElement)
return extractParams().mapIndexed { index, variableElement ->
QueryParameterProcessor(
baseContext = context,
containing = containing,
element = variableElement,
sqlName = kotlinParameterNames?.getOrNull(index)
).process()
}
}
abstract fun findResultBinder(returnType: TypeMirror, query: ParsedQuery): QueryResultBinder
abstract fun findPreparedResultBinder(
returnType: TypeMirror,
query: ParsedQuery
): PreparedQueryResultBinder
abstract fun findInsertMethodBinder(
returnType: TypeMirror,
params: List<ShortcutQueryParameter>
): InsertMethodBinder
abstract fun findDeleteOrUpdateMethodBinder(returnType: TypeMirror): DeleteOrUpdateMethodBinder
abstract fun findTransactionMethodBinder(
callType: TransactionMethod.CallType
): TransactionMethodBinder
companion object {
fun createFor(
context: Context,
containing: DeclaredType,
executableElement: ExecutableElement
): MethodProcessorDelegate {
val kotlinMetadata =
KotlinMetadataElement.createFor(context, executableElement.enclosingElement)
return if (kotlinMetadata?.isSuspendFunction(executableElement) == true) {
val hasCoroutineArtifact = context.processingEnv.elementUtils
.getTypeElement(RoomCoroutinesTypeNames.COROUTINES_ROOM.toString()) != null
if (!hasCoroutineArtifact) {
context.logger.e(ProcessorErrors.MISSING_ROOM_COROUTINE_ARTIFACT)
}
SuspendMethodProcessorDelegate(
context,
containing,
executableElement,
kotlinMetadata
)
} else {
DefaultMethodProcessorDelegate(
context,
containing,
executableElement,
kotlinMetadata
)
}
}
}
}
/**
* Default delegate for DAO methods.
*/
class DefaultMethodProcessorDelegate(
context: Context,
containing: DeclaredType,
executableElement: ExecutableElement,
classMetadata: KotlinMetadataElement?
) : MethodProcessorDelegate(context, containing, executableElement, classMetadata) {
override fun extractReturnType(): TypeMirror {
val asMember = context.processingEnv.typeUtils.asMemberOf(containing, executableElement)
return MoreTypes.asExecutable(asMember).returnType
}
override fun extractParams() = executableElement.parameters
override fun findResultBinder(returnType: TypeMirror, query: ParsedQuery) =
context.typeAdapterStore.findQueryResultBinder(returnType, query)
override fun findPreparedResultBinder(
returnType: TypeMirror,
query: ParsedQuery
) = context.typeAdapterStore.findPreparedQueryResultBinder(returnType, query)
override fun findInsertMethodBinder(
returnType: TypeMirror,
params: List<ShortcutQueryParameter>
) = context.typeAdapterStore.findInsertMethodBinder(returnType, params)
override fun findDeleteOrUpdateMethodBinder(returnType: TypeMirror) =
context.typeAdapterStore.findDeleteOrUpdateMethodBinder(returnType)
override fun findTransactionMethodBinder(callType: TransactionMethod.CallType) =
InstantTransactionMethodBinder(
TransactionMethodAdapter(executableElement.simpleName.toString(), callType))
}
/**
* Delegate for DAO methods that are a suspend function.
*/
class SuspendMethodProcessorDelegate(
context: Context,
containing: DeclaredType,
executableElement: ExecutableElement,
kotlinMetadata: KotlinMetadataElement
) : MethodProcessorDelegate(context, containing, executableElement, kotlinMetadata) {
private val continuationParam: VariableElement by lazy {
val typesUtil = context.processingEnv.typeUtils
val continuationType = typesUtil.erasure(
context.processingEnv.elementUtils
.getTypeElement(KotlinTypeNames.CONTINUATION.toString())
.asType()
)
executableElement.parameters.last {
typesUtil.isSameType(typesUtil.erasure(it.asType()), continuationType)
}
}
override fun extractReturnType(): TypeMirror {
val asMember = context.processingEnv.typeUtils.asMemberOf(containing, executableElement)
return MoreTypes.asExecutable(asMember).getSuspendFunctionReturnType()
}
override fun extractParams() =
executableElement.parameters.filterNot { it == continuationParam }
override fun findResultBinder(returnType: TypeMirror, query: ParsedQuery) =
CoroutineResultBinder(
typeArg = returnType,
adapter = context.typeAdapterStore.findQueryResultAdapter(returnType, query),
continuationParamName = continuationParam.simpleName.toString()
)
override fun findPreparedResultBinder(
returnType: TypeMirror,
query: ParsedQuery
) = createPreparedBinder(
returnType = returnType,
adapter = context.typeAdapterStore.findPreparedQueryResultAdapter(returnType, query)
) { callableImpl, dbField ->
addStatement(
"return $T.execute($N, $L, $L, $N)",
RoomCoroutinesTypeNames.COROUTINES_ROOM,
dbField,
"true", // inTransaction
callableImpl,
continuationParam.simpleName.toString()
)
}
override fun findInsertMethodBinder(
returnType: TypeMirror,
params: List<ShortcutQueryParameter>
) = createInsertBinder(
typeArg = returnType,
adapter = context.typeAdapterStore.findInsertAdapter(returnType, params)
) { callableImpl, dbField ->
addStatement(
"return $T.execute($N, $L, $L, $N)",
RoomCoroutinesTypeNames.COROUTINES_ROOM,
dbField,
"true", // inTransaction
callableImpl,
continuationParam.simpleName.toString()
)
}
override fun findDeleteOrUpdateMethodBinder(returnType: TypeMirror) =
createDeleteOrUpdateBinder(
typeArg = returnType,
adapter = context.typeAdapterStore.findDeleteOrUpdateAdapter(returnType)
) { callableImpl, dbField ->
addStatement(
"return $T.execute($N, $L, $L, $N)",
RoomCoroutinesTypeNames.COROUTINES_ROOM,
dbField,
"true", // inTransaction
callableImpl,
continuationParam.simpleName.toString()
)
}
override fun findTransactionMethodBinder(callType: TransactionMethod.CallType) =
CoroutineTransactionMethodBinder(
adapter = TransactionMethodAdapter(executableElement.simpleName.toString(), callType),
continuationParamName = continuationParam.simpleName.toString()
)
}