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