1 /*
<lambda>null2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.room.solver
18 
19 import androidx.annotation.VisibleForTesting
20 import androidx.room.compiler.codegen.CodeLanguage
21 import androidx.room.compiler.codegen.XTypeName
22 import androidx.room.compiler.processing.XNullability
23 import androidx.room.compiler.processing.XType
24 import androidx.room.compiler.processing.isArray
25 import androidx.room.compiler.processing.isEnum
26 import androidx.room.ext.CollectionTypeNames.ARRAY_MAP
27 import androidx.room.ext.CollectionTypeNames.INT_SPARSE_ARRAY
28 import androidx.room.ext.CollectionTypeNames.LONG_SPARSE_ARRAY
29 import androidx.room.ext.CommonTypeNames
30 import androidx.room.ext.GuavaTypeNames
31 import androidx.room.ext.getValueClassUnderlyingInfo
32 import androidx.room.ext.isByteBuffer
33 import androidx.room.ext.isEntityElement
34 import androidx.room.ext.isNotByte
35 import androidx.room.ext.isNotKotlinUnit
36 import androidx.room.ext.isNotVoid
37 import androidx.room.ext.isNotVoidObject
38 import androidx.room.ext.isUUID
39 import androidx.room.parser.ParsedQuery
40 import androidx.room.parser.SQLTypeAffinity
41 import androidx.room.processor.Context
42 import androidx.room.processor.DataClassProcessor
43 import androidx.room.processor.EntityProcessor
44 import androidx.room.processor.ProcessorErrors
45 import androidx.room.processor.ProcessorErrors.DO_NOT_USE_GENERIC_IMMUTABLE_MULTIMAP
46 import androidx.room.processor.ProcessorErrors.invalidQueryForSingleColumnArray
47 import androidx.room.processor.PropertyProcessor
48 import androidx.room.solver.binderprovider.CoroutineFlowResultBinderProvider
49 import androidx.room.solver.binderprovider.CursorQueryResultBinderProvider
50 import androidx.room.solver.binderprovider.DataSourceFactoryQueryResultBinderProvider
51 import androidx.room.solver.binderprovider.DataSourceQueryResultBinderProvider
52 import androidx.room.solver.binderprovider.GuavaListenableFutureQueryResultBinderProvider
53 import androidx.room.solver.binderprovider.InstantQueryResultBinderProvider
54 import androidx.room.solver.binderprovider.ListenableFuturePagingSourceQueryResultBinderProvider
55 import androidx.room.solver.binderprovider.LiveDataQueryResultBinderProvider
56 import androidx.room.solver.binderprovider.PagingSourceQueryResultBinderProvider
57 import androidx.room.solver.binderprovider.RxJava2PagingSourceQueryResultBinderProvider
58 import androidx.room.solver.binderprovider.RxJava3PagingSourceQueryResultBinderProvider
59 import androidx.room.solver.binderprovider.RxLambdaQueryResultBinderProvider
60 import androidx.room.solver.binderprovider.RxQueryResultBinderProvider
61 import androidx.room.solver.prepared.binder.PreparedQueryResultBinder
62 import androidx.room.solver.prepared.binderprovider.GuavaListenableFuturePreparedQueryResultBinderProvider
63 import androidx.room.solver.prepared.binderprovider.InstantPreparedQueryResultBinderProvider
64 import androidx.room.solver.prepared.binderprovider.PreparedQueryResultBinderProvider
65 import androidx.room.solver.prepared.binderprovider.RxPreparedQueryResultBinderProvider
66 import androidx.room.solver.prepared.result.PreparedQueryResultAdapter
67 import androidx.room.solver.query.parameter.ArrayQueryParameterAdapter
68 import androidx.room.solver.query.parameter.BasicQueryParameterAdapter
69 import androidx.room.solver.query.parameter.CollectionQueryParameterAdapter
70 import androidx.room.solver.query.parameter.QueryParameterAdapter
71 import androidx.room.solver.query.result.ArrayQueryResultAdapter
72 import androidx.room.solver.query.result.DataClassRowAdapter
73 import androidx.room.solver.query.result.EntityRowAdapter
74 import androidx.room.solver.query.result.GuavaImmutableMultimapQueryResultAdapter
75 import androidx.room.solver.query.result.GuavaOptionalQueryResultAdapter
76 import androidx.room.solver.query.result.ImmutableListQueryResultAdapter
77 import androidx.room.solver.query.result.ImmutableMapQueryResultAdapter
78 import androidx.room.solver.query.result.ListQueryResultAdapter
79 import androidx.room.solver.query.result.MapQueryResultAdapter
80 import androidx.room.solver.query.result.MapValueResultAdapter
81 import androidx.room.solver.query.result.MultimapQueryResultAdapter
82 import androidx.room.solver.query.result.MultimapQueryResultAdapter.Companion.getMapColumnName
83 import androidx.room.solver.query.result.MultimapQueryResultAdapter.Companion.validateMapKeyTypeArg
84 import androidx.room.solver.query.result.MultimapQueryResultAdapter.Companion.validateMapValueTypeArg
85 import androidx.room.solver.query.result.MultimapQueryResultAdapter.MapType.Companion.isSparseArray
86 import androidx.room.solver.query.result.OptionalQueryResultAdapter
87 import androidx.room.solver.query.result.QueryResultAdapter
88 import androidx.room.solver.query.result.QueryResultBinder
89 import androidx.room.solver.query.result.RowAdapter
90 import androidx.room.solver.query.result.SingleColumnRowAdapter
91 import androidx.room.solver.query.result.SingleItemQueryResultAdapter
92 import androidx.room.solver.query.result.SingleNamedColumnRowAdapter
93 import androidx.room.solver.shortcut.binder.DeleteOrUpdateFunctionBinder
94 import androidx.room.solver.shortcut.binder.InsertOrUpsertFunctionBinder
95 import androidx.room.solver.shortcut.binderprovider.DeleteOrUpdateFunctionBinderProvider
96 import androidx.room.solver.shortcut.binderprovider.GuavaListenableFutureDeleteOrUpdateFunctionBinderProvider
97 import androidx.room.solver.shortcut.binderprovider.GuavaListenableFutureInsertOrUpsertFunctionBinderProvider
98 import androidx.room.solver.shortcut.binderprovider.InsertOrUpsertFunctionBinderProvider
99 import androidx.room.solver.shortcut.binderprovider.InstantDeleteOrUpdateFunctionBinderProvider
100 import androidx.room.solver.shortcut.binderprovider.InstantInsertOrUpsertFunctionBinderProvider
101 import androidx.room.solver.shortcut.binderprovider.RxCallableDeleteOrUpdateFunctionBinderProvider
102 import androidx.room.solver.shortcut.binderprovider.RxCallableInsertOrUpsertFunctionBinderProvider
103 import androidx.room.solver.shortcut.result.DeleteOrUpdateFunctionAdapter
104 import androidx.room.solver.shortcut.result.InsertOrUpsertFunctionAdapter
105 import androidx.room.solver.types.BoxedBooleanToBoxedIntConverter
106 import androidx.room.solver.types.BoxedPrimitiveColumnTypeAdapter
107 import androidx.room.solver.types.ByteArrayColumnTypeAdapter
108 import androidx.room.solver.types.ByteArrayWrapperColumnTypeAdapter
109 import androidx.room.solver.types.ByteBufferColumnTypeAdapter
110 import androidx.room.solver.types.ColumnTypeAdapter
111 import androidx.room.solver.types.CompositeAdapter
112 import androidx.room.solver.types.EnumColumnTypeAdapter
113 import androidx.room.solver.types.PrimitiveBooleanToIntConverter
114 import androidx.room.solver.types.PrimitiveColumnTypeAdapter
115 import androidx.room.solver.types.StatementValueBinder
116 import androidx.room.solver.types.StatementValueReader
117 import androidx.room.solver.types.StringColumnTypeAdapter
118 import androidx.room.solver.types.TypeConverter
119 import androidx.room.solver.types.UuidColumnTypeAdapter
120 import androidx.room.solver.types.ValueClassConverterWrapper
121 import androidx.room.vo.BuiltInConverterFlags
122 import androidx.room.vo.MapInfo
123 import androidx.room.vo.ShortcutQueryParameter
124 import androidx.room.vo.Warning
125 import androidx.room.vo.isEnabled
126 import com.google.common.collect.ImmutableList
127 import com.google.common.collect.ImmutableListMultimap
128 import com.google.common.collect.ImmutableMap
129 import com.google.common.collect.ImmutableMultimap
130 import com.google.common.collect.ImmutableSetMultimap
131 
132 /**
133  * Holds all type adapters and can create on demand composite type adapters to convert a type into a
134  * database column.
135  */
136 class TypeAdapterStore
137 private constructor(
138     val context: Context,
139     /** first type adapter has the highest priority */
140     private val columnTypeAdapters: List<ColumnTypeAdapter>,
141     @get:VisibleForTesting internal val typeConverterStore: TypeConverterStore,
142     private val builtInConverterFlags: BuiltInConverterFlags
143 ) {
144 
145     companion object {
146         fun copy(context: Context, store: TypeAdapterStore): TypeAdapterStore {
147             return TypeAdapterStore(
148                 context = context,
149                 columnTypeAdapters = store.columnTypeAdapters,
150                 typeConverterStore = store.typeConverterStore,
151                 builtInConverterFlags = store.builtInConverterFlags
152             )
153         }
154 
155         fun create(
156             context: Context,
157             builtInConverterFlags: BuiltInConverterFlags,
158             vararg extras: Any
159         ): TypeAdapterStore {
160             val adapters = arrayListOf<ColumnTypeAdapter>()
161             val converters = arrayListOf<TypeConverter>()
162             fun addAny(extra: Any?) {
163                 when (extra) {
164                     is TypeConverter -> converters.add(extra)
165                     is ColumnTypeAdapter -> adapters.add(extra)
166                     is List<*> -> extra.forEach(::addAny)
167                     else -> throw IllegalArgumentException("unknown extra $extra")
168                 }
169             }
170 
171             extras.forEach(::addAny)
172             fun addTypeConverter(converter: TypeConverter) {
173                 converters.add(converter)
174             }
175 
176             fun addColumnAdapter(adapter: ColumnTypeAdapter) {
177                 adapters.add(adapter)
178             }
179 
180             val primitives =
181                 PrimitiveColumnTypeAdapter.createPrimitiveAdapters(context.processingEnv)
182             primitives.forEach(::addColumnAdapter)
183             BoxedPrimitiveColumnTypeAdapter.createBoxedPrimitiveAdapters(primitives)
184                 .forEach(::addColumnAdapter)
185             StringColumnTypeAdapter.create(context.processingEnv).forEach(::addColumnAdapter)
186             ByteArrayColumnTypeAdapter.create(context.processingEnv).forEach(::addColumnAdapter)
187             ByteArrayWrapperColumnTypeAdapter.create(context.processingEnv)
188                 .forEach(::addColumnAdapter)
189             PrimitiveBooleanToIntConverter.create(context.processingEnv).forEach(::addTypeConverter)
190             // null aware converter is able to automatically null wrap converters so we don't
191             // need this as long as we are running in KSP
192             BoxedBooleanToBoxedIntConverter.create(context.processingEnv)
193                 .forEach(::addTypeConverter)
194             return TypeAdapterStore(
195                 context = context,
196                 columnTypeAdapters = adapters,
197                 typeConverterStore =
198                     TypeConverterStore.create(
199                         context = context,
200                         typeConverters = converters,
201                         knownColumnTypes = adapters.map { it.out }
202                     ),
203                 builtInConverterFlags = builtInConverterFlags
204             )
205         }
206     }
207 
208     private val queryResultBinderProviders: List<QueryResultBinderProvider> =
209         mutableListOf<QueryResultBinderProvider>().apply {
210             add(CursorQueryResultBinderProvider(context))
211             add(LiveDataQueryResultBinderProvider(context))
212             add(GuavaListenableFutureQueryResultBinderProvider(context))
213             addAll(RxQueryResultBinderProvider.getAll(context))
214             addAll(RxLambdaQueryResultBinderProvider.getAll(context))
215             add(DataSourceQueryResultBinderProvider(context))
216             add(DataSourceFactoryQueryResultBinderProvider(context))
217             add(RxJava2PagingSourceQueryResultBinderProvider(context))
218             add(RxJava3PagingSourceQueryResultBinderProvider(context))
219             add(ListenableFuturePagingSourceQueryResultBinderProvider(context))
220             add(PagingSourceQueryResultBinderProvider(context))
221             add(CoroutineFlowResultBinderProvider(context))
222             add(InstantQueryResultBinderProvider(context))
223         }
224 
225     private val preparedQueryResultBinderProviders: List<PreparedQueryResultBinderProvider> =
226         mutableListOf<PreparedQueryResultBinderProvider>().apply {
227             addAll(RxPreparedQueryResultBinderProvider.getAll(context))
228             add(GuavaListenableFuturePreparedQueryResultBinderProvider(context))
229             add(InstantPreparedQueryResultBinderProvider(context))
230         }
231 
232     private val insertOrUpsertBinderProviders: List<InsertOrUpsertFunctionBinderProvider> =
233         mutableListOf<InsertOrUpsertFunctionBinderProvider>().apply {
234             addAll(RxCallableInsertOrUpsertFunctionBinderProvider.getAll(context))
235             add(GuavaListenableFutureInsertOrUpsertFunctionBinderProvider(context))
236             add(InstantInsertOrUpsertFunctionBinderProvider(context))
237         }
238 
239     private val deleteOrUpdateBinderProvider: List<DeleteOrUpdateFunctionBinderProvider> =
240         mutableListOf<DeleteOrUpdateFunctionBinderProvider>().apply {
241             addAll(RxCallableDeleteOrUpdateFunctionBinderProvider.getAll(context))
242             add(GuavaListenableFutureDeleteOrUpdateFunctionBinderProvider(context))
243             add(InstantDeleteOrUpdateFunctionBinderProvider(context))
244         }
245 
246     /** Searches 1 way to bind a value into a statement. */
247     fun findStatementValueBinder(input: XType, affinity: SQLTypeAffinity?): StatementValueBinder? {
248         if (input.isError()) {
249             return null
250         }
251         val adapter = findDirectAdapterFor(input, affinity)
252         if (adapter != null) {
253             return adapter
254         }
255 
256         fun findTypeConverterAdapter(): ColumnTypeAdapter? {
257             val targetTypes = affinity?.getTypeMirrors(context.processingEnv)
258             val binder =
259                 typeConverterStore.findConverterIntoStatement(
260                     input = input,
261                     columnTypes = targetTypes
262                 ) ?: return null
263             // columnAdapter should not be null but we are receiving errors on crash in `first()` so
264             // this safeguard allows us to dispatch the real problem to the user (e.g. why we
265             // couldn't
266             // find the right adapter)
267             val columnAdapter = getAllColumnAdapters(binder.to).firstOrNull() ?: return null
268             return CompositeAdapter(input, columnAdapter, binder, null)
269         }
270 
271         val adapterByTypeConverter = findTypeConverterAdapter()
272         if (adapterByTypeConverter != null) {
273             return adapterByTypeConverter
274         }
275         val defaultAdapter = createDefaultTypeAdapter(input, affinity)
276         if (defaultAdapter != null) {
277             return defaultAdapter
278         }
279         return null
280     }
281 
282     /** Searches 1 way to read it from a statement */
283     fun findStatementValueReader(output: XType, affinity: SQLTypeAffinity?): StatementValueReader? {
284         if (output.isError()) {
285             return null
286         }
287         val adapter = findColumnTypeAdapter(output, affinity, skipDefaultConverter = true)
288         if (adapter != null) {
289             // two way is better
290             return adapter
291         }
292 
293         fun findTypeConverterAdapter(): ColumnTypeAdapter? {
294             val targetTypes = affinity?.getTypeMirrors(context.processingEnv)
295             val converter =
296                 typeConverterStore.findConverterFromStatement(
297                     columnTypes = targetTypes,
298                     output = output
299                 ) ?: return null
300             return CompositeAdapter(
301                 output,
302                 getAllColumnAdapters(converter.from).first(),
303                 null,
304                 converter
305             )
306         }
307 
308         // we could not find a two way version, search for anything
309         val typeConverterAdapter = findTypeConverterAdapter()
310         if (typeConverterAdapter != null) {
311             return typeConverterAdapter
312         }
313 
314         val defaultAdapter = createDefaultTypeAdapter(output, affinity)
315         if (defaultAdapter != null) {
316             return defaultAdapter
317         }
318 
319         return null
320     }
321 
322     /**
323      * Finds a two way converter, if you need 1 way, use findStatementValueBinder or
324      * findStatementValueReader.
325      */
326     fun findColumnTypeAdapter(
327         out: XType,
328         affinity: SQLTypeAffinity?,
329         skipDefaultConverter: Boolean
330     ): ColumnTypeAdapter? {
331         if (out.isError()) {
332             return null
333         }
334         val adapter = findDirectAdapterFor(out, affinity)
335         if (adapter != null) {
336             return adapter
337         }
338 
339         fun findTypeConverterAdapter(): ColumnTypeAdapter? {
340             val targetTypes = affinity?.getTypeMirrors(context.processingEnv)
341             val intoStatement =
342                 typeConverterStore.findConverterIntoStatement(
343                     input = out,
344                     columnTypes = targetTypes
345                 ) ?: return null
346             // ok found a converter, try the reverse now
347             val fromStmt =
348                 typeConverterStore.reverse(intoStatement)
349                     ?: typeConverterStore.findTypeConverter(intoStatement.to, out)
350                     ?: return null
351             return CompositeAdapter(
352                 out,
353                 getAllColumnAdapters(intoStatement.to).first(),
354                 intoStatement,
355                 fromStmt
356             )
357         }
358 
359         val adapterByTypeConverter = findTypeConverterAdapter()
360         if (adapterByTypeConverter != null) {
361             return adapterByTypeConverter
362         }
363 
364         if (!skipDefaultConverter) {
365             val defaultAdapter = createDefaultTypeAdapter(out, affinity)
366             if (defaultAdapter != null) {
367                 return defaultAdapter
368             }
369         }
370         return null
371     }
372 
373     private fun createDefaultTypeAdapter(
374         type: XType,
375         affinity: SQLTypeAffinity?
376     ): ColumnTypeAdapter? {
377         val typeElement = type.typeElement
378         if (typeElement?.isValueClass() == true) {
379             // Extract the type value of the Value class element
380             val underlyingInfo = typeElement.getValueClassUnderlyingInfo()
381             if (underlyingInfo.constructor.isPrivate() || underlyingInfo.getter == null) {
382                 return null
383             }
384             val underlyingTypeColumnAdapter =
385                 findColumnTypeAdapter(
386                     // Find an adapter for the non-null underlying type, nullability will be handled
387                     // by the value class adapter.
388                     out =
389                         try {
390                             // Workaround for KSP2
391                             underlyingInfo.parameter.asMemberOf(type).makeNonNullable()
392                         } catch (ex: Throwable) {
393                             underlyingInfo.parameter.type.makeNonNullable()
394                         },
395                     affinity = affinity,
396                     skipDefaultConverter = false
397                 ) ?: return null
398 
399             return ValueClassConverterWrapper(
400                 valueTypeColumnAdapter = underlyingTypeColumnAdapter,
401                 affinity = underlyingTypeColumnAdapter.typeAffinity,
402                 out = type,
403                 valuePropertyName = underlyingInfo.parameter.name
404             )
405         }
406         return when {
407             builtInConverterFlags.enums.isEnabled() && typeElement?.isEnum() == true ->
408                 EnumColumnTypeAdapter(typeElement, type)
409             builtInConverterFlags.uuid.isEnabled() && type.isUUID() -> UuidColumnTypeAdapter(type)
410             builtInConverterFlags.byteBuffer.isEnabled() && type.isByteBuffer() ->
411                 ByteBufferColumnTypeAdapter(type)
412             else -> null
413         }
414     }
415 
416     private fun findDirectAdapterFor(out: XType, affinity: SQLTypeAffinity?): ColumnTypeAdapter? {
417         return getAllColumnAdapters(out).firstOrNull {
418             affinity == null || it.typeAffinity == affinity
419         }
420     }
421 
422     fun findDeleteOrUpdateFunctionBinder(typeMirror: XType): DeleteOrUpdateFunctionBinder {
423         return deleteOrUpdateBinderProvider.first { it.matches(typeMirror) }.provide(typeMirror)
424     }
425 
426     fun findInsertFunctionBinder(
427         typeMirror: XType,
428         params: List<ShortcutQueryParameter>
429     ): InsertOrUpsertFunctionBinder {
430         return insertOrUpsertBinderProviders
431             .first { it.matches(typeMirror) }
432             .provide(typeMirror, params, false)
433     }
434 
435     fun findUpsertFunctionBinder(
436         typeMirror: XType,
437         params: List<ShortcutQueryParameter>
438     ): InsertOrUpsertFunctionBinder {
439         return insertOrUpsertBinderProviders
440             .first { it.matches(typeMirror) }
441             .provide(typeMirror, params, true)
442     }
443 
444     fun findQueryResultBinder(
445         typeMirror: XType,
446         query: ParsedQuery,
447         extrasCreator: TypeAdapterExtras.() -> Unit = {}
448     ): QueryResultBinder {
449         return findQueryResultBinder(typeMirror, query, TypeAdapterExtras().apply(extrasCreator))
450     }
451 
452     fun findQueryResultBinder(
453         typeMirror: XType,
454         query: ParsedQuery,
455         extras: TypeAdapterExtras
456     ): QueryResultBinder {
457         return queryResultBinderProviders
458             .first { it.matches(typeMirror) }
459             .provide(typeMirror, query, extras)
460     }
461 
462     fun findPreparedQueryResultBinder(
463         typeMirror: XType,
464         query: ParsedQuery
465     ): PreparedQueryResultBinder {
466         return preparedQueryResultBinderProviders
467             .first { it.matches(typeMirror) }
468             .provide(typeMirror, query)
469     }
470 
471     fun findPreparedQueryResultAdapter(typeMirror: XType, query: ParsedQuery) =
472         PreparedQueryResultAdapter.create(typeMirror, query.type)
473 
474     fun findDeleteOrUpdateAdapter(typeMirror: XType): DeleteOrUpdateFunctionAdapter? {
475         return DeleteOrUpdateFunctionAdapter.create(typeMirror)
476     }
477 
478     fun findInsertAdapter(
479         typeMirror: XType,
480         params: List<ShortcutQueryParameter>
481     ): InsertOrUpsertFunctionAdapter? {
482         return InsertOrUpsertFunctionAdapter.createInsert(context, typeMirror, params)
483     }
484 
485     fun findUpsertAdapter(
486         typeMirror: XType,
487         params: List<ShortcutQueryParameter>
488     ): InsertOrUpsertFunctionAdapter? {
489         return InsertOrUpsertFunctionAdapter.createUpsert(context, typeMirror, params)
490     }
491 
492     fun findQueryResultAdapter(
493         typeMirror: XType,
494         query: ParsedQuery,
495         extrasCreator: TypeAdapterExtras.() -> Unit = {}
496     ): QueryResultAdapter? {
497         return findQueryResultAdapter(typeMirror, query, TypeAdapterExtras().apply(extrasCreator))
498     }
499 
500     fun findQueryResultAdapter(
501         typeMirror: XType,
502         query: ParsedQuery,
503         extras: TypeAdapterExtras
504     ): QueryResultAdapter? {
505         if (typeMirror.isError()) {
506             return null
507         }
508 
509         // TODO: (b/192068912) Refactor the following since this if-else cascade has gotten large
510         if (typeMirror.isArray() && typeMirror.componentType.isNotByte()) {
511             val componentType = typeMirror.componentType
512             checkTypeNullability(typeMirror, extras, "Array", arrayComponentType = componentType)
513             val isSingleColumnArray =
514                 componentType.asTypeName().isPrimitive || componentType.isTypeOf(String::class)
515             val queryResultInfo = query.resultInfo
516             if (
517                 isSingleColumnArray && queryResultInfo != null && queryResultInfo.columns.size > 1
518             ) {
519                 context.logger.e(
520                     invalidQueryForSingleColumnArray(
521                         typeMirror.asTypeName().toString(context.codeLanguage)
522                     )
523                 )
524                 return null
525             }
526 
527             // Create a type mirror for a regular List in order to use ListQueryResultAdapter. This
528             // avoids code duplication as an Array can be initialized using a list.
529             val listType =
530                 context.processingEnv
531                     .getDeclaredType(
532                         context.processingEnv.requireTypeElement(List::class),
533                         componentType.boxed().makeNonNullable()
534                     )
535                     .makeNonNullable()
536 
537             val listResultAdapter =
538                 findQueryResultAdapter(typeMirror = listType, query = query, extras = extras)
539                     ?: return null
540 
541             return ArrayQueryResultAdapter(typeMirror, listResultAdapter as ListQueryResultAdapter)
542         } else if (typeMirror.typeArguments.isEmpty()) {
543             val rowAdapter = findRowAdapter(typeMirror, query) ?: return null
544             return SingleItemQueryResultAdapter(rowAdapter)
545         } else if (typeMirror.rawType.asTypeName() == GuavaTypeNames.OPTIONAL) {
546             checkTypeNullability(typeMirror, extras, "Optional")
547             // Handle Guava Optional by unpacking its generic type argument and adapting that.
548             // The Optional adapter will re-append the Optional type.
549             val typeArg = typeMirror.typeArguments.first()
550             // use nullable when finding row adapter as non-null adapters might return
551             // default values
552             val rowAdapter = findRowAdapter(typeArg.makeNullable(), query) ?: return null
553             return GuavaOptionalQueryResultAdapter(
554                 typeArg = typeArg,
555                 resultAdapter = SingleItemQueryResultAdapter(rowAdapter)
556             )
557         } else if (typeMirror.rawType.asTypeName() == CommonTypeNames.OPTIONAL) {
558             checkTypeNullability(typeMirror, extras, "Optional")
559 
560             // Handle java.util.Optional similarly.
561             val typeArg = typeMirror.typeArguments.first()
562             // use nullable when finding row adapter as non-null adapters might return
563             // default values
564             val rowAdapter = findRowAdapter(typeArg.makeNullable(), query) ?: return null
565             return OptionalQueryResultAdapter(
566                 typeArg = typeArg,
567                 resultAdapter = SingleItemQueryResultAdapter(rowAdapter)
568             )
569         } else if (typeMirror.isTypeOf(ImmutableList::class)) {
570             checkTypeNullability(typeMirror, extras)
571 
572             val typeArg = typeMirror.typeArguments.first().extendsBoundOrSelf()
573             val rowAdapter = findRowAdapter(typeArg, query) ?: return null
574             return ImmutableListQueryResultAdapter(typeArg = typeArg, rowAdapter = rowAdapter)
575         } else if (typeMirror.isTypeOf(java.util.List::class)) {
576             checkTypeNullability(typeMirror, extras)
577             val typeArg = typeMirror.typeArguments.first().extendsBoundOrSelf()
578             val rowAdapter = findRowAdapter(typeArg, query) ?: return null
579             return ListQueryResultAdapter(typeArg = typeArg, rowAdapter = rowAdapter)
580         } else if (typeMirror.isTypeOf(ImmutableMap::class)) {
581             val keyTypeArg = typeMirror.typeArguments[0].extendsBoundOrSelf()
582             val valueTypeArg = typeMirror.typeArguments[1].extendsBoundOrSelf()
583             checkTypeNullability(typeMirror, extras)
584 
585             // Create a type mirror for a regular Map in order to use MapQueryResultAdapter. This
586             // avoids code duplication as Immutable Map can be initialized by creating an immutable
587             // copy of a regular map.
588             val mapType =
589                 context.processingEnv.getDeclaredType(
590                     context.processingEnv.requireTypeElement(Map::class),
591                     keyTypeArg,
592                     valueTypeArg
593                 )
594 
595             val resultAdapter = findQueryResultAdapter(mapType, query, extras) ?: return null
596             return ImmutableMapQueryResultAdapter(
597                 context = context,
598                 parsedQuery = query,
599                 keyTypeArg = keyTypeArg,
600                 valueTypeArg = valueTypeArg,
601                 resultAdapter = resultAdapter
602             )
603         } else if (
604             typeMirror.isTypeOf(ImmutableSetMultimap::class) ||
605                 typeMirror.isTypeOf(ImmutableListMultimap::class) ||
606                 typeMirror.isTypeOf(ImmutableMultimap::class)
607         ) {
608             val keyTypeArg = typeMirror.typeArguments[0].extendsBoundOrSelf()
609             val valueTypeArg = typeMirror.typeArguments[1].extendsBoundOrSelf()
610             checkTypeNullability(typeMirror, extras)
611 
612             if (valueTypeArg.typeElement == null) {
613                 context.logger.e(
614                     "Guava multimap 'value' type argument does not represent a class. " +
615                         "Found $valueTypeArg."
616                 )
617                 return null
618             }
619 
620             val immutableClassName =
621                 if (typeMirror.isTypeOf(ImmutableListMultimap::class)) {
622                     GuavaTypeNames.IMMUTABLE_LIST_MULTIMAP
623                 } else if (typeMirror.isTypeOf(ImmutableSetMultimap::class)) {
624                     GuavaTypeNames.IMMUTABLE_SET_MULTIMAP
625                 } else {
626                     // Return type is base class ImmutableMultimap which is not recommended.
627                     context.logger.e(DO_NOT_USE_GENERIC_IMMUTABLE_MULTIMAP)
628                     return null
629                 }
630 
631             // Get @MapInfo info if any (this might be null)
632             val mapInfo = extras.getData(MapInfo::class)
633             val mapKeyColumn = getMapColumnName(context, query, keyTypeArg)
634             val mapValueColumn = getMapColumnName(context, query, valueTypeArg)
635             if (mapInfo != null && (mapKeyColumn != null || mapValueColumn != null)) {
636                 context.logger.e(ProcessorErrors.CANNOT_USE_MAP_COLUMN_AND_MAP_INFO_SIMULTANEOUSLY)
637             }
638 
639             val mappedKeyColumnName = mapKeyColumn ?: mapInfo?.keyColumnName
640             val mappedValueColumnName = mapValueColumn ?: mapInfo?.valueColumnName
641 
642             val keyRowAdapter =
643                 findRowAdapter(
644                     typeMirror = keyTypeArg,
645                     query = query,
646                     columnName = mappedKeyColumnName
647                 ) ?: return null
648 
649             val valueRowAdapter =
650                 findRowAdapter(
651                     typeMirror = valueTypeArg,
652                     query = query,
653                     columnName = mappedValueColumnName
654                 ) ?: return null
655 
656             validateMapKeyTypeArg(
657                 context = context,
658                 keyTypeArg = keyTypeArg,
659                 keyReader = findStatementValueReader(keyTypeArg, null),
660                 keyColumnName = mappedKeyColumnName
661             )
662             validateMapValueTypeArg(
663                 context = context,
664                 valueTypeArg = valueTypeArg,
665                 valueReader = findStatementValueReader(valueTypeArg, null),
666                 valueColumnName = mappedValueColumnName
667             )
668             return GuavaImmutableMultimapQueryResultAdapter(
669                 context = context,
670                 parsedQuery = query,
671                 keyTypeArg = keyTypeArg,
672                 valueTypeArg = valueTypeArg,
673                 keyRowAdapter = keyRowAdapter,
674                 valueRowAdapter = valueRowAdapter,
675                 immutableClassName = immutableClassName
676             )
677         } else if (
678             typeMirror.isTypeOf(java.util.Map::class) ||
679                 typeMirror.rawType.asTypeName().equalsIgnoreNullability(ARRAY_MAP) ||
680                 typeMirror.rawType.asTypeName().equalsIgnoreNullability(LONG_SPARSE_ARRAY) ||
681                 typeMirror.rawType.asTypeName().equalsIgnoreNullability(INT_SPARSE_ARRAY)
682         ) {
683             val mapType =
684                 when (typeMirror.rawType.asTypeName()) {
685                     LONG_SPARSE_ARRAY -> MultimapQueryResultAdapter.MapType.LONG_SPARSE
686                     INT_SPARSE_ARRAY -> MultimapQueryResultAdapter.MapType.INT_SPARSE
687                     ARRAY_MAP -> MultimapQueryResultAdapter.MapType.ARRAY_MAP
688                     else -> MultimapQueryResultAdapter.MapType.DEFAULT
689                 }
690             val keyTypeArg =
691                 when (mapType) {
692                     MultimapQueryResultAdapter.MapType.LONG_SPARSE ->
693                         context.processingEnv.requireType(XTypeName.PRIMITIVE_LONG)
694                     MultimapQueryResultAdapter.MapType.INT_SPARSE ->
695                         context.processingEnv.requireType(XTypeName.PRIMITIVE_INT)
696                     else -> typeMirror.typeArguments[0].extendsBoundOrSelf()
697                 }
698             checkTypeNullability(typeMirror, extras)
699 
700             val mapValueTypeArg =
701                 if (mapType.isSparseArray()) {
702                     typeMirror.typeArguments[0].extendsBoundOrSelf()
703                 } else {
704                     typeMirror.typeArguments[1].extendsBoundOrSelf()
705                 }
706 
707             if (mapValueTypeArg.typeElement == null) {
708                 context.logger.e(
709                     "Multimap 'value' collection type argument does not represent a class. " +
710                         "Found $mapValueTypeArg."
711                 )
712                 return null
713             }
714 
715             // Get @MapInfo info if any (this might be null)
716             val mapInfo = extras.getData(MapInfo::class)
717             val mapColumn = getMapColumnName(context, query, keyTypeArg)
718             if (mapInfo != null && mapColumn != null) {
719                 context.logger.e(ProcessorErrors.CANNOT_USE_MAP_COLUMN_AND_MAP_INFO_SIMULTANEOUSLY)
720             }
721 
722             val mappedKeyColumnName = mapColumn ?: mapInfo?.keyColumnName
723             val keyRowAdapter =
724                 findRowAdapter(
725                     typeMirror = keyTypeArg,
726                     query = query,
727                     columnName = mappedKeyColumnName
728                 ) ?: return null
729 
730             validateMapKeyTypeArg(
731                 context = context,
732                 keyTypeArg = keyTypeArg,
733                 keyReader = findStatementValueReader(keyTypeArg, null),
734                 keyColumnName = mappedKeyColumnName
735             )
736 
737             val mapValueResultAdapter =
738                 findMapValueResultAdapter(
739                     query = query,
740                     mapInfo = mapInfo,
741                     mapValueTypeArg = mapValueTypeArg
742                 ) ?: return null
743             return MapQueryResultAdapter(
744                 context = context,
745                 parsedQuery = query,
746                 mapValueResultAdapter =
747                     MapValueResultAdapter.NestedMapValueResultAdapter(
748                         keyRowAdapter = keyRowAdapter,
749                         keyTypeArg = keyTypeArg,
750                         mapType = mapType,
751                         mapValueResultAdapter = mapValueResultAdapter
752                     )
753             )
754         }
755         return null
756     }
757 
758     private fun checkTypeNullability(
759         searchingType: XType,
760         extras: TypeAdapterExtras,
761         typeKeyword: String = "Collection",
762         arrayComponentType: XType? = null
763     ) {
764         if (context.codeLanguage != CodeLanguage.KOTLIN) {
765             return
766         }
767 
768         val collectionType: XType =
769             extras.getData(ObservableQueryResultBinderProvider.OriginalTypeArg::class)?.original
770                 ?: searchingType
771 
772         if (collectionType.nullability != XNullability.NONNULL) {
773             context.logger.w(
774                 Warning.UNNECESSARY_NULLABILITY_IN_DAO_RETURN_TYPE,
775                 ProcessorErrors.nullableCollectionOrArrayReturnTypeInDaoFunction(
776                     searchingType.asTypeName().toString(context.codeLanguage),
777                     typeKeyword
778                 )
779             )
780         }
781 
782         // Since Array has typeArg in the componentType and not typeArguments, need a special check.
783         if (arrayComponentType != null && arrayComponentType.nullability != XNullability.NONNULL) {
784             context.logger.w(
785                 Warning.UNNECESSARY_NULLABILITY_IN_DAO_RETURN_TYPE,
786                 ProcessorErrors.nullableComponentInDaoFunctionReturnType(
787                     searchingType.asTypeName().toString(context.codeLanguage)
788                 )
789             )
790             return
791         }
792 
793         collectionType.typeArguments.forEach { typeArg ->
794             if (typeArg.nullability != XNullability.NONNULL) {
795                 context.logger.w(
796                     Warning.UNNECESSARY_NULLABILITY_IN_DAO_RETURN_TYPE,
797                     ProcessorErrors.nullableComponentInDaoFunctionReturnType(
798                         searchingType.asTypeName().toString(context.codeLanguage)
799                     )
800                 )
801             }
802         }
803     }
804 
805     private fun findMapValueResultAdapter(
806         query: ParsedQuery,
807         mapInfo: MapInfo?,
808         mapValueTypeArg: XType
809     ): MapValueResultAdapter? {
810         val collectionTypeRaw =
811             context.processingEnv.requireType(CommonTypeNames.COLLECTION).rawType
812         if (collectionTypeRaw.isAssignableFrom(mapValueTypeArg.rawType)) {
813             // The Map's value type argument is assignable to a Collection, we need to make
814             // sure it is either a List or a Set.
815             val listTypeRaw =
816                 context.processingEnv.requireType(CommonTypeNames.MUTABLE_LIST).rawType
817             val setTypeRaw = context.processingEnv.requireType(CommonTypeNames.MUTABLE_SET).rawType
818             val collectionValueType =
819                 when {
820                     mapValueTypeArg.rawType.isAssignableFrom(listTypeRaw) ->
821                         MultimapQueryResultAdapter.CollectionValueType.LIST
822                     mapValueTypeArg.rawType.isAssignableFrom(setTypeRaw) ->
823                         MultimapQueryResultAdapter.CollectionValueType.SET
824                     else -> {
825                         context.logger.e(
826                             ProcessorErrors.valueCollectionMustBeListOrSetOrMap(
827                                 mapValueTypeArg.asTypeName().toString(context.codeLanguage)
828                             )
829                         )
830                         return null
831                     }
832                 }
833 
834             val valueTypeArg = mapValueTypeArg.typeArguments.single().extendsBoundOrSelf()
835             val mapColumnName = getMapColumnName(context, query, valueTypeArg)
836             if (mapColumnName != null && mapInfo != null) {
837                 context.logger.e(ProcessorErrors.CANNOT_USE_MAP_COLUMN_AND_MAP_INFO_SIMULTANEOUSLY)
838             }
839 
840             val mappedValueColumnName = mapColumnName ?: mapInfo?.valueColumnName
841             val valueRowAdapter =
842                 findRowAdapter(
843                     typeMirror = valueTypeArg,
844                     query = query,
845                     columnName = mappedValueColumnName
846                 ) ?: return null
847 
848             validateMapValueTypeArg(
849                 context = context,
850                 valueTypeArg = valueTypeArg,
851                 valueReader = findStatementValueReader(valueTypeArg, null),
852                 valueColumnName = mappedValueColumnName
853             )
854 
855             return MapValueResultAdapter.EndMapValueResultAdapter(
856                 valueRowAdapter = valueRowAdapter,
857                 valueTypeArg = valueTypeArg,
858                 valueCollectionType = collectionValueType
859             )
860         } else if (mapValueTypeArg.isTypeOf(java.util.Map::class)) {
861             val keyTypeArg = mapValueTypeArg.typeArguments[0].extendsBoundOrSelf()
862             val valueTypeArg = mapValueTypeArg.typeArguments[1].extendsBoundOrSelf()
863 
864             val keyRowAdapter =
865                 findRowAdapter(
866                     typeMirror = keyTypeArg,
867                     query = query,
868                     // No need to account for @MapInfo since nested maps did not support
869                     // this now deprecated annotation anyway.
870                     columnName = getMapColumnName(context, query, keyTypeArg)
871                 ) ?: return null
872             val valueMapAdapter =
873                 findMapValueResultAdapter(
874                     query = query,
875                     mapInfo = mapInfo,
876                     mapValueTypeArg = valueTypeArg
877                 ) ?: return null
878             return MapValueResultAdapter.NestedMapValueResultAdapter(
879                 keyRowAdapter = keyRowAdapter,
880                 keyTypeArg = keyTypeArg,
881                 mapType = MultimapQueryResultAdapter.MapType.DEFAULT,
882                 mapValueResultAdapter = valueMapAdapter
883             )
884         } else {
885             val mappedValueColumnName =
886                 getMapColumnName(context, query, mapValueTypeArg) ?: mapInfo?.valueColumnName
887             val valueRowAdapter =
888                 findRowAdapter(
889                     typeMirror = mapValueTypeArg,
890                     query = query,
891                     columnName = mappedValueColumnName
892                 ) ?: return null
893 
894             validateMapValueTypeArg(
895                 context = context,
896                 valueTypeArg = mapValueTypeArg,
897                 valueReader = findStatementValueReader(mapValueTypeArg, null),
898                 valueColumnName = mappedValueColumnName
899             )
900             return MapValueResultAdapter.EndMapValueResultAdapter(
901                 valueRowAdapter = valueRowAdapter,
902                 valueTypeArg = mapValueTypeArg,
903                 valueCollectionType = null
904             )
905         }
906     }
907 
908     /**
909      * Find a converter from statement to the given type mirror. If there is information about the
910      * query result, we try to use it to accept *any* data class.
911      */
912     fun findRowAdapter(
913         typeMirror: XType,
914         query: ParsedQuery,
915         columnName: String? = null
916     ): RowAdapter? {
917         if (typeMirror.isError()) {
918             return null
919         }
920 
921         val typeElement = typeMirror.typeElement
922         if (typeElement != null && !typeMirror.asTypeName().isPrimitive) {
923             if (typeMirror.typeArguments.isNotEmpty()) {
924                 // TODO one day support this
925                 return null
926             }
927             val resultInfo = query.resultInfo
928 
929             val (rowAdapter, rowAdapterLogs) =
930                 if (resultInfo != null && query.errors.isEmpty() && resultInfo.error == null) {
931                     // if result info is not null, first try a data class row adapter
932                     context.collectLogs { subContext ->
933                         val dataClass =
934                             DataClassProcessor.createFor(
935                                     context = subContext,
936                                     element = typeElement,
937                                     bindingScope = PropertyProcessor.BindingScope.READ_FROM_STMT,
938                                     parent = null
939                                 )
940                                 .process()
941                         DataClassRowAdapter(
942                             context = subContext,
943                             info = resultInfo,
944                             query = query,
945                             dataClass = dataClass,
946                             out = typeMirror
947                         )
948                     }
949                 } else {
950                     Pair(null, null)
951                 }
952 
953             if (rowAdapter == null && query.resultInfo == null) {
954                 // we don't know what query returns. Check for entity.
955                 if (typeElement.isEntityElement()) {
956                     return EntityRowAdapter(
957                         entity =
958                             EntityProcessor(context = context, element = typeElement).process(),
959                         out = typeMirror
960                     )
961                 }
962             }
963 
964             if (rowAdapter != null && rowAdapterLogs?.hasErrors() != true) {
965                 rowAdapterLogs?.writeTo(context)
966                 return rowAdapter
967             }
968 
969             if (columnName != null) {
970                 val singleNamedColumn =
971                     findStatementValueReader(
972                         typeMirror,
973                         query.resultInfo?.columns?.find { it.name == columnName }?.type
974                     )
975                 if (singleNamedColumn != null) {
976                     return SingleNamedColumnRowAdapter(singleNamedColumn, columnName)
977                 }
978             }
979 
980             if ((resultInfo?.columns?.size ?: 1) == 1) {
981                 val singleColumn =
982                     findStatementValueReader(typeMirror, resultInfo?.columns?.get(0)?.type)
983                 if (singleColumn != null) {
984                     return SingleColumnRowAdapter(singleColumn)
985                 }
986             }
987             // if we tried, return its errors
988             if (rowAdapter != null) {
989                 rowAdapterLogs?.writeTo(context)
990                 return rowAdapter
991             }
992 
993             // use data class adapter as a last resort.
994             // this happens when @RawQuery or @SkipVerification is used.
995             if (
996                 query.resultInfo == null &&
997                     typeMirror.isNotVoid() &&
998                     typeMirror.isNotVoidObject() &&
999                     typeMirror.isNotKotlinUnit()
1000             ) {
1001                 val dataClass =
1002                     DataClassProcessor.createFor(
1003                             context = context,
1004                             element = typeElement,
1005                             bindingScope = PropertyProcessor.BindingScope.READ_FROM_STMT,
1006                             parent = null
1007                         )
1008                         .process()
1009                 return DataClassRowAdapter(
1010                     context = context,
1011                     info = null,
1012                     query = query,
1013                     dataClass = dataClass,
1014                     out = typeMirror
1015                 )
1016             }
1017             return null
1018         } else {
1019             if (columnName != null) {
1020                 val singleNamedColumn =
1021                     findStatementValueReader(
1022                         typeMirror,
1023                         query.resultInfo?.columns?.find { it.name == columnName }?.type
1024                     )
1025                 if (singleNamedColumn != null) {
1026                     return SingleNamedColumnRowAdapter(singleNamedColumn, columnName)
1027                 }
1028             }
1029             val singleColumn = findStatementValueReader(typeMirror, null) ?: return null
1030             return SingleColumnRowAdapter(singleColumn)
1031         }
1032     }
1033 
1034     fun findQueryParameterAdapter(
1035         typeMirror: XType,
1036         isMultipleParameter: Boolean
1037     ): QueryParameterAdapter? {
1038         val collectionType = context.processingEnv.requireType(CommonTypeNames.COLLECTION)
1039         if (collectionType.rawType.isAssignableFrom(typeMirror)) {
1040             val typeArg = typeMirror.typeArguments.first().extendsBoundOrSelf()
1041             // An adapter for the collection type arg wrapped in the built-in collection adapter.
1042             val wrappedCollectionAdapter =
1043                 findStatementValueBinder(typeArg, null)?.let {
1044                     CollectionQueryParameterAdapter(it, typeMirror.nullability)
1045                 }
1046             // An adapter for the collection itself, likely a user provided type converter for the
1047             // collection.
1048             val directCollectionAdapter =
1049                 findStatementValueBinder(typeMirror, null)?.let { BasicQueryParameterAdapter(it) }
1050             // Prioritize built-in collection adapters when finding an adapter for a multi-value
1051             // binding param since it is likely wrong to use a collection to single value converter
1052             // for an expression that takes in multiple values.
1053             return if (isMultipleParameter) {
1054                 wrappedCollectionAdapter ?: directCollectionAdapter
1055             } else {
1056                 directCollectionAdapter ?: wrappedCollectionAdapter
1057             }
1058         } else if (typeMirror.isArray() && typeMirror.componentType.isNotByte()) {
1059             val component = typeMirror.componentType
1060             val binder = findStatementValueBinder(component, null) ?: return null
1061             return ArrayQueryParameterAdapter(binder, typeMirror.nullability)
1062         } else {
1063             val binder = findStatementValueBinder(typeMirror, null) ?: return null
1064             return BasicQueryParameterAdapter(binder)
1065         }
1066     }
1067 
1068     private fun getAllColumnAdapters(input: XType): List<ColumnTypeAdapter> {
1069         return columnTypeAdapters.filter { input.isSameType(it.out) }
1070     }
1071 }
1072