blob: 62a24adf604273564831ae904a8f84c368c8f2d9 [file] [log] [blame]
/*
* Copyright (C) 2016 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 android.arch.persistence.room.solver
import android.arch.persistence.room.Entity
import android.arch.persistence.room.ext.hasAnnotation
import android.arch.persistence.room.parser.ParsedQuery
import android.arch.persistence.room.parser.SQLTypeAffinity
import android.arch.persistence.room.processor.Context
import android.arch.persistence.room.processor.EntityProcessor
import android.arch.persistence.room.processor.FieldProcessor
import android.arch.persistence.room.processor.PojoProcessor
import android.arch.persistence.room.solver.binderprovider.CursorQueryResultBinderProvider
import android.arch.persistence.room.solver.binderprovider.DataSourceQueryResultBinderProvider
import android.arch.persistence.room.solver.binderprovider.FlowableQueryResultBinderProvider
import android.arch.persistence.room.solver.binderprovider.InstantQueryResultBinderProvider
import android.arch.persistence.room.solver.binderprovider.LiveDataQueryResultBinderProvider
import android.arch.persistence.room.solver.binderprovider.DataSourceFactoryQueryResultBinderProvider
import android.arch.persistence.room.solver.binderprovider.RxMaybeQueryResultBinderProvider
import android.arch.persistence.room.solver.binderprovider.RxSingleQueryResultBinderProvider
import android.arch.persistence.room.solver.query.parameter.ArrayQueryParameterAdapter
import android.arch.persistence.room.solver.query.parameter.BasicQueryParameterAdapter
import android.arch.persistence.room.solver.query.parameter.CollectionQueryParameterAdapter
import android.arch.persistence.room.solver.query.parameter.QueryParameterAdapter
import android.arch.persistence.room.solver.query.result.ArrayQueryResultAdapter
import android.arch.persistence.room.solver.query.result.EntityRowAdapter
import android.arch.persistence.room.solver.query.result.InstantQueryResultBinder
import android.arch.persistence.room.solver.query.result.ListQueryResultAdapter
import android.arch.persistence.room.solver.query.result.PojoRowAdapter
import android.arch.persistence.room.solver.query.result.QueryResultAdapter
import android.arch.persistence.room.solver.query.result.QueryResultBinder
import android.arch.persistence.room.solver.query.result.RowAdapter
import android.arch.persistence.room.solver.query.result.SingleColumnRowAdapter
import android.arch.persistence.room.solver.query.result.SingleEntityQueryResultAdapter
import android.arch.persistence.room.solver.types.BoxedBooleanToBoxedIntConverter
import android.arch.persistence.room.solver.types.BoxedPrimitiveColumnTypeAdapter
import android.arch.persistence.room.solver.types.ByteArrayColumnTypeAdapter
import android.arch.persistence.room.solver.types.ColumnTypeAdapter
import android.arch.persistence.room.solver.types.CompositeAdapter
import android.arch.persistence.room.solver.types.CompositeTypeConverter
import android.arch.persistence.room.solver.types.CursorValueReader
import android.arch.persistence.room.solver.types.NoOpConverter
import android.arch.persistence.room.solver.types.PrimitiveBooleanToIntConverter
import android.arch.persistence.room.solver.types.PrimitiveColumnTypeAdapter
import android.arch.persistence.room.solver.types.StatementValueBinder
import android.arch.persistence.room.solver.types.StringColumnTypeAdapter
import android.arch.persistence.room.solver.types.TypeConverter
import com.google.auto.common.MoreElements
import com.google.auto.common.MoreTypes
import com.google.common.annotations.VisibleForTesting
import java.util.LinkedList
import javax.lang.model.type.ArrayType
import javax.lang.model.type.TypeKind
import javax.lang.model.type.TypeMirror
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
/**
* Holds all type adapters and can create on demand composite type adapters to convert a type into a
* database column.
*/
class TypeAdapterStore private constructor(
val context: Context,
/**
* first type adapter has the highest priority
*/
private val columnTypeAdapters: List<ColumnTypeAdapter>,
/**
* first converter has the highest priority
*/
private val typeConverters: List<TypeConverter>) {
companion object {
fun copy(context: Context, store: TypeAdapterStore): TypeAdapterStore {
return TypeAdapterStore(context = context,
columnTypeAdapters = store.columnTypeAdapters,
typeConverters = store.typeConverters)
}
fun create(context: Context, @VisibleForTesting vararg extras: Any): TypeAdapterStore {
val adapters = arrayListOf<ColumnTypeAdapter>()
val converters = arrayListOf<TypeConverter>()
fun addAny(extra: Any?) {
when (extra) {
is TypeConverter -> converters.add(extra)
is ColumnTypeAdapter -> adapters.add(extra)
is List<*> -> extra.forEach(::addAny)
else -> throw IllegalArgumentException("unknown extra $extra")
}
}
extras.forEach(::addAny)
fun addTypeConverter(converter: TypeConverter) {
converters.add(converter)
}
fun addColumnAdapter(adapter: ColumnTypeAdapter) {
adapters.add(adapter)
}
val primitives = PrimitiveColumnTypeAdapter
.createPrimitiveAdapters(context.processingEnv)
primitives.forEach(::addColumnAdapter)
BoxedPrimitiveColumnTypeAdapter
.createBoxedPrimitiveAdapters(context.processingEnv, primitives)
.forEach(::addColumnAdapter)
addColumnAdapter(StringColumnTypeAdapter(context.processingEnv))
addColumnAdapter(ByteArrayColumnTypeAdapter(context.processingEnv))
PrimitiveBooleanToIntConverter.create(context.processingEnv).forEach(::addTypeConverter)
BoxedBooleanToBoxedIntConverter.create(context.processingEnv)
.forEach(::addTypeConverter)
return TypeAdapterStore(context = context, columnTypeAdapters = adapters,
typeConverters = converters)
}
}
val queryResultBinderProviders = listOf(
CursorQueryResultBinderProvider(context),
LiveDataQueryResultBinderProvider(context),
FlowableQueryResultBinderProvider(context),
RxMaybeQueryResultBinderProvider(context),
RxSingleQueryResultBinderProvider(context),
DataSourceQueryResultBinderProvider(context),
DataSourceFactoryQueryResultBinderProvider(context),
InstantQueryResultBinderProvider(context)
)
// type mirrors that be converted into columns w/o an extra converter
private val knownColumnTypeMirrors by lazy {
columnTypeAdapters.map { it.out }
}
/**
* Searches 1 way to bind a value into a statement.
*/
fun findStatementValueBinder(
input: TypeMirror,
affinity: SQLTypeAffinity?
): StatementValueBinder? {
if (input.kind == TypeKind.ERROR) {
return null
}
val adapter = findDirectAdapterFor(input, affinity)
if (adapter != null) {
return adapter
}
val targetTypes = targetTypeMirrorsFor(affinity)
val binder = findTypeConverter(input, targetTypes) ?: return null
// columnAdapter should not be null but we are receiving errors on crash in `first()` so
// this safeguard allows us to dispatch the real problem to the user (e.g. why we couldn't
// find the right adapter)
val columnAdapter = getAllColumnAdapters(binder.to).firstOrNull() ?: return null
return CompositeAdapter(input, columnAdapter, binder, null)
}
/**
* Returns which entities targets the given affinity.
*/
private fun targetTypeMirrorsFor(affinity: SQLTypeAffinity?): List<TypeMirror> {
val specifiedTargets = affinity?.getTypeMirrors(context.processingEnv)
return if (specifiedTargets == null || specifiedTargets.isEmpty()) {
knownColumnTypeMirrors
} else {
specifiedTargets
}
}
/**
* Searches 1 way to read it from cursor
*/
fun findCursorValueReader(output: TypeMirror, affinity: SQLTypeAffinity?): CursorValueReader? {
if (output.kind == TypeKind.ERROR) {
return null
}
val adapter = findColumnTypeAdapter(output, affinity)
if (adapter != null) {
// two way is better
return adapter
}
// we could not find a two way version, search for anything
val targetTypes = targetTypeMirrorsFor(affinity)
val converter = findTypeConverter(targetTypes, output) ?: return null
return CompositeAdapter(output,
getAllColumnAdapters(converter.from).first(), null, converter)
}
/**
* Tries to reverse the converter going through the same nodes, if possible.
*/
@VisibleForTesting
fun reverse(converter: TypeConverter): TypeConverter? {
return when (converter) {
is NoOpConverter -> converter
is CompositeTypeConverter -> {
val r1 = reverse(converter.conv1) ?: return null
val r2 = reverse(converter.conv2) ?: return null
CompositeTypeConverter(r2, r1)
}
else -> {
val types = context.processingEnv.typeUtils
typeConverters.firstOrNull {
types.isSameType(it.from, converter.to) && types
.isSameType(it.to, converter.from)
}
}
}
}
/**
* Finds a two way converter, if you need 1 way, use findStatementValueBinder or
* findCursorValueReader.
*/
fun findColumnTypeAdapter(out: TypeMirror, affinity: SQLTypeAffinity?): ColumnTypeAdapter? {
if (out.kind == TypeKind.ERROR) {
return null
}
val adapter = findDirectAdapterFor(out, affinity)
if (adapter != null) {
return adapter
}
val targetTypes = targetTypeMirrorsFor(affinity)
val intoStatement = findTypeConverter(out, targetTypes) ?: return null
// ok found a converter, try the reverse now
val fromCursor = reverse(intoStatement) ?: findTypeConverter(intoStatement.to, out)
?: return null
return CompositeAdapter(out, getAllColumnAdapters(intoStatement.to).first(), intoStatement,
fromCursor)
}
private fun findDirectAdapterFor(
out: TypeMirror, affinity: SQLTypeAffinity?): ColumnTypeAdapter? {
val adapter = getAllColumnAdapters(out).firstOrNull {
affinity == null || it.typeAffinity == affinity
}
return adapter
}
fun findTypeConverter(input: TypeMirror, output: TypeMirror): TypeConverter? {
return findTypeConverter(listOf(input), listOf(output))
}
fun findQueryResultBinder(typeMirror: TypeMirror, query: ParsedQuery): QueryResultBinder {
return if (typeMirror.kind == TypeKind.DECLARED) {
val declared = MoreTypes.asDeclared(typeMirror)
return queryResultBinderProviders.first {
it.matches(declared)
}.provide(declared, query)
} else {
InstantQueryResultBinder(findQueryResultAdapter(typeMirror, query))
}
}
fun findQueryResultAdapter(typeMirror: TypeMirror, query: ParsedQuery): QueryResultAdapter? {
if (typeMirror.kind == TypeKind.ERROR) {
return null
}
if (typeMirror.kind == TypeKind.DECLARED) {
val declared = MoreTypes.asDeclared(typeMirror)
if (declared.typeArguments.isEmpty()) {
val rowAdapter = findRowAdapter(typeMirror, query) ?: return null
return SingleEntityQueryResultAdapter(rowAdapter)
}
if (MoreTypes.isTypeOf(java.util.List::class.java, typeMirror)) {
val typeArg = declared.typeArguments.first()
val rowAdapter = findRowAdapter(typeArg, query) ?: return null
return ListQueryResultAdapter(rowAdapter)
}
return null
} else if (typeMirror is ArrayType && typeMirror.componentType.kind != TypeKind.BYTE) {
val rowAdapter =
findRowAdapter(typeMirror.componentType, query) ?: return null
return ArrayQueryResultAdapter(rowAdapter)
} else {
val rowAdapter = findRowAdapter(typeMirror, query) ?: return null
return SingleEntityQueryResultAdapter(rowAdapter)
}
}
/**
* Find a converter from cursor to the given type mirror.
* If there is information about the query result, we try to use it to accept *any* POJO.
*/
@VisibleForTesting
fun findRowAdapter(typeMirror: TypeMirror, query: ParsedQuery): RowAdapter? {
if (typeMirror.kind == TypeKind.ERROR) {
return null
}
if (typeMirror.kind == TypeKind.DECLARED) {
val declared = MoreTypes.asDeclared(typeMirror)
if (declared.typeArguments.isNotEmpty()) {
// TODO one day support this
return null
}
val resultInfo = query.resultInfo
val (rowAdapter, rowAdapterLogs) = if (resultInfo != null && query.errors.isEmpty()
&& resultInfo.error == null) {
// if result info is not null, first try a pojo row adapter
context.collectLogs { subContext ->
val pojo = PojoProcessor(
baseContext = subContext,
element = MoreTypes.asTypeElement(typeMirror),
bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
parent = null
).process()
PojoRowAdapter(
context = subContext,
info = resultInfo,
pojo = pojo,
out = typeMirror)
}
} else {
Pair(null, null)
}
if (rowAdapter == null && query.resultInfo == null) {
// we don't know what query returns. Check for entity.
val asElement = MoreTypes.asElement(typeMirror)
if (asElement.hasAnnotation(Entity::class)) {
return EntityRowAdapter(EntityProcessor(context,
MoreElements.asType(asElement)).process())
}
}
if (rowAdapter != null && rowAdapterLogs?.hasErrors() != true) {
rowAdapterLogs?.writeTo(context.processingEnv)
return rowAdapter
}
if ((resultInfo?.columns?.size ?: 1) == 1) {
val singleColumn = findCursorValueReader(typeMirror,
resultInfo?.columns?.get(0)?.type)
if (singleColumn != null) {
return SingleColumnRowAdapter(singleColumn)
}
}
// if we tried, return its errors
if (rowAdapter != null) {
rowAdapterLogs?.writeTo(context.processingEnv)
return rowAdapter
}
if (query.runtimeQueryPlaceholder) {
// just go w/ pojo and hope for the best. this happens for @RawQuery where we
// try to guess user's intention and hope that their query fits the result.
val pojo = PojoProcessor(
baseContext = context,
element = MoreTypes.asTypeElement(typeMirror),
bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
parent = null
).process()
return PojoRowAdapter(
context = context,
info = null,
pojo = pojo,
out = typeMirror)
}
return null
} else {
val singleColumn = findCursorValueReader(typeMirror, null) ?: return null
return SingleColumnRowAdapter(singleColumn)
}
}
fun findQueryParameterAdapter(typeMirror: TypeMirror): QueryParameterAdapter? {
if (MoreTypes.isType(typeMirror)
&& (MoreTypes.isTypeOf(java.util.List::class.java, typeMirror)
|| MoreTypes.isTypeOf(java.util.Set::class.java, typeMirror))) {
val declared = MoreTypes.asDeclared(typeMirror)
val binder = findStatementValueBinder(declared.typeArguments.first(),
null)
if (binder != null) {
return CollectionQueryParameterAdapter(binder)
} else {
// maybe user wants to convert this collection themselves. look for a match
val collectionBinder = findStatementValueBinder(typeMirror, null) ?: return null
return BasicQueryParameterAdapter(collectionBinder)
}
} else if (typeMirror is ArrayType && typeMirror.componentType.kind != TypeKind.BYTE) {
val component = typeMirror.componentType
val binder = findStatementValueBinder(component, null) ?: return null
return ArrayQueryParameterAdapter(binder)
} else {
val binder = findStatementValueBinder(typeMirror, null) ?: return null
return BasicQueryParameterAdapter(binder)
}
}
private fun findTypeConverter(input: TypeMirror, outputs: List<TypeMirror>): TypeConverter? {
return findTypeConverter(listOf(input), outputs)
}
private fun findTypeConverter(input: List<TypeMirror>, output: TypeMirror): TypeConverter? {
return findTypeConverter(input, listOf(output))
}
private fun findTypeConverter(
inputs: List<TypeMirror>, outputs: List<TypeMirror>): TypeConverter? {
if (inputs.isEmpty()) {
return null
}
val types = context.processingEnv.typeUtils
inputs.forEach { input ->
if (outputs.any { output -> types.isSameType(input, output) }) {
return NoOpConverter(input)
}
}
val excludes = arrayListOf<TypeMirror>()
val queue = LinkedList<TypeConverter>()
fun exactMatch(candidates: List<TypeConverter>): TypeConverter? {
return candidates.firstOrNull {
outputs.any { output -> types.isSameType(output, it.to) }
}
}
inputs.forEach { input ->
val candidates = getAllTypeConverters(input, excludes)
val match = exactMatch(candidates)
if (match != null) {
return match
}
candidates.forEach {
excludes.add(it.to)
queue.add(it)
}
}
excludes.addAll(inputs)
while (queue.isNotEmpty()) {
val prev = queue.pop()
val from = prev.to
val candidates = getAllTypeConverters(from, excludes)
val match = exactMatch(candidates)
if (match != null) {
return CompositeTypeConverter(prev, match)
}
candidates.forEach {
excludes.add(it.to)
queue.add(CompositeTypeConverter(prev, it))
}
}
return null
}
private fun getAllColumnAdapters(input: TypeMirror): List<ColumnTypeAdapter> {
return columnTypeAdapters.filter {
context.processingEnv.typeUtils.isSameType(input, it.out)
}
}
private fun getAllTypeConverters(input: TypeMirror, excludes: List<TypeMirror>):
List<TypeConverter> {
val types = context.processingEnv.typeUtils
// for input, check assignability because it defines whether we can use the method or not.
// for excludes, use exact match
return typeConverters.filter { converter ->
types.isAssignable(input, converter.from) &&
!excludes.any { types.isSameType(it, converter.to) }
}
}
}