1 /*
<lambda>null2  * Copyright (C) 2017 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.processor
18 
19 import androidx.room.ColumnInfo
20 import androidx.room.Embedded
21 import androidx.room.Ignore
22 import androidx.room.Junction
23 import androidx.room.PrimaryKey
24 import androidx.room.Relation
25 import androidx.room.compiler.processing.XExecutableElement
26 import androidx.room.compiler.processing.XFieldElement
27 import androidx.room.compiler.processing.XType
28 import androidx.room.compiler.processing.XTypeElement
29 import androidx.room.compiler.processing.XVariableElement
30 import androidx.room.compiler.processing.isVoid
31 import androidx.room.ext.isCollection
32 import androidx.room.ext.isNotVoid
33 import androidx.room.processor.ProcessorErrors.CANNOT_FIND_GETTER_FOR_PROPERTY
34 import androidx.room.processor.ProcessorErrors.CANNOT_FIND_SETTER_FOR_PROPERTY
35 import androidx.room.processor.ProcessorErrors.DATA_CLASS_PROPERTY_HAS_DUPLICATE_COLUMN_NAME
36 import androidx.room.processor.autovalue.AutoValueDataClassProcessorDelegate
37 import androidx.room.processor.cache.Cache
38 import androidx.room.vo.CallType
39 import androidx.room.vo.Constructor
40 import androidx.room.vo.DataClass
41 import androidx.room.vo.DataClassFunction
42 import androidx.room.vo.EmbeddedProperty
43 import androidx.room.vo.Entity
44 import androidx.room.vo.EntityOrView
45 import androidx.room.vo.Property
46 import androidx.room.vo.PropertyGetter
47 import androidx.room.vo.PropertySetter
48 import androidx.room.vo.Warning
49 import androidx.room.vo.columnNames
50 import androidx.room.vo.findPropertyByColumnName
51 import com.google.auto.value.AutoValue
52 
53 /** Processes any class as if it is a data class. */
54 class DataClassProcessor
55 private constructor(
56     baseContext: Context,
57     val element: XTypeElement,
58     val bindingScope: PropertyProcessor.BindingScope,
59     val parent: EmbeddedProperty?,
60     val referenceStack: LinkedHashSet<String> = LinkedHashSet(),
61     private val delegate: Delegate
62 ) {
63     val context = baseContext.fork(element)
64 
65     companion object {
66         val PROCESSED_ANNOTATIONS = listOf(ColumnInfo::class, Embedded::class, Relation::class)
67 
68         val TARGET_METHOD_ANNOTATIONS =
69             arrayOf(PrimaryKey::class, ColumnInfo::class, Embedded::class, Relation::class)
70 
71         fun createFor(
72             context: Context,
73             element: XTypeElement,
74             bindingScope: PropertyProcessor.BindingScope,
75             parent: EmbeddedProperty?,
76             referenceStack: LinkedHashSet<String> = LinkedHashSet()
77         ): DataClassProcessor {
78             val (dataClassElement, delegate) =
79                 if (element.hasAnnotation(AutoValue::class)) {
80                     val processingEnv = context.processingEnv
81                     val autoValueGeneratedTypeName =
82                         AutoValueDataClassProcessorDelegate.getGeneratedClassName(element)
83                     val autoValueGeneratedElement =
84                         processingEnv.findTypeElement(autoValueGeneratedTypeName)
85                     if (autoValueGeneratedElement != null) {
86                         autoValueGeneratedElement to
87                             AutoValueDataClassProcessorDelegate(context, element)
88                     } else {
89                         context.reportMissingType(autoValueGeneratedTypeName)
90                         element to EmptyDelegate
91                     }
92                 } else {
93                     element to DefaultDelegate(context)
94                 }
95 
96             return DataClassProcessor(
97                 baseContext = context,
98                 element = dataClassElement,
99                 bindingScope = bindingScope,
100                 parent = parent,
101                 referenceStack = referenceStack,
102                 delegate = delegate
103             )
104         }
105     }
106 
107     fun process(): DataClass {
108         return context.cache.dataClasses.get(Cache.DataClassKey(element, bindingScope, parent)) {
109             referenceStack.add(element.qualifiedName)
110             try {
111                 doProcess()
112             } finally {
113                 referenceStack.remove(element.qualifiedName)
114             }
115         }
116     }
117 
118     private fun doProcess(): DataClass {
119         if (!element.validate()) {
120             context.reportMissingTypeReference(element.qualifiedName)
121             return delegate.createDataClass(
122                 element = element,
123                 declaredType = element.type,
124                 properties = emptyList(),
125                 embeddedProperties = emptyList(),
126                 relations = emptyList(),
127                 constructor = null
128             )
129         }
130         delegate.onPreProcess(element)
131 
132         val declaredType = element.type
133         // TODO handle conflicts with super: b/35568142
134         val allProperties =
135             element
136                 .getAllFieldsIncludingPrivateSupers()
137                 .filter {
138                     !it.hasAnnotation(Ignore::class) &&
139                         !it.isStatic() &&
140                         (!it.isTransient() ||
141                             it.hasAnyAnnotation(
142                                 ColumnInfo::class,
143                                 Embedded::class,
144                                 Relation::class
145                             ))
146                 }
147                 .groupBy { property ->
148                     context.checker.check(
149                         PROCESSED_ANNOTATIONS.count { property.hasAnnotation(it) } < 2,
150                         property,
151                         ProcessorErrors.CANNOT_USE_MORE_THAN_ONE_DATA_CLASS_PROPERTY_ANNOTATION
152                     )
153                     if (property.hasAnnotation(Embedded::class)) {
154                         Embedded::class
155                     } else if (property.hasAnnotation(Relation::class)) {
156                         Relation::class
157                     } else {
158                         null
159                     }
160                 }
161 
162         val ignoredColumns =
163             element
164                 .getAnnotation(androidx.room.Entity::class)
165                 ?.get("ignoredColumns")
166                 ?.asStringList()
167                 ?.toSet() ?: emptySet()
168         val propertyBindingErrors = mutableMapOf<Property, String>()
169         val unfilteredMyProperties =
170             allProperties[null]?.map {
171                 PropertyProcessor(
172                         baseContext = context,
173                         containing = declaredType,
174                         element = it,
175                         bindingScope = bindingScope,
176                         propertyParent = parent,
177                         onBindingError = { property, errorMsg ->
178                             propertyBindingErrors[property] = errorMsg
179                         }
180                     )
181                     .process()
182             } ?: emptyList()
183         val myProperties =
184             unfilteredMyProperties.filterNot { ignoredColumns.contains(it.columnName) }
185         myProperties.forEach { property ->
186             propertyBindingErrors[property]?.let { context.logger.e(property.element, it) }
187         }
188         val unfilteredEmbeddedProperties =
189             allProperties[Embedded::class]?.mapNotNull { processEmbeddedField(declaredType, it) }
190                 ?: emptyList()
191         val embeddedProperties =
192             unfilteredEmbeddedProperties.filterNot {
193                 ignoredColumns.contains(it.property.columnName)
194             }
195 
196         val subProperties = embeddedProperties.flatMap { it.dataClass.properties }
197         val propertys = myProperties + subProperties
198 
199         val unfilteredCombinedProperties =
200             unfilteredMyProperties + unfilteredEmbeddedProperties.map { it.property }
201         val missingIgnoredColumns =
202             ignoredColumns.filterNot { ignoredColumn ->
203                 unfilteredCombinedProperties.any { it.columnName == ignoredColumn }
204             }
205         context.checker.check(
206             missingIgnoredColumns.isEmpty(),
207             element,
208             ProcessorErrors.missingIgnoredColumns(missingIgnoredColumns)
209         )
210 
211         val myRelationsList =
212             allProperties[Relation::class]?.mapNotNull {
213                 processRelationField(propertys, declaredType, it)
214             } ?: emptyList()
215 
216         val subRelations = embeddedProperties.flatMap { it.dataClass.relations }
217         val relations = myRelationsList + subRelations
218 
219         propertys
220             .groupBy { it.columnName }
221             .filter { it.value.size > 1 }
222             .forEach {
223                 context.logger.e(
224                     element,
225                     ProcessorErrors.dataClassDuplicatePropertyNames(
226                         it.key,
227                         it.value.map(Property::getPath)
228                     )
229                 )
230                 it.value.forEach {
231                     context.logger.e(it.element, DATA_CLASS_PROPERTY_HAS_DUPLICATE_COLUMN_NAME)
232                 }
233             }
234 
235         val methods =
236             element
237                 .getAllNonPrivateInstanceMethods()
238                 .asSequence()
239                 .filter { !it.isAbstract() && !it.hasAnnotation(Ignore::class) }
240                 .map {
241                     DataClassFunctionProcessor(
242                             context = context,
243                             element = it,
244                             owner = declaredType
245                         )
246                         .process()
247                 }
248                 .toList()
249 
250         val getterCandidates =
251             methods.filter {
252                 it.element.parameters.size == 0 && it.resolvedType.returnType.isNotVoid()
253             }
254 
255         val setterCandidates =
256             methods.filter {
257                 it.element.parameters.size == 1 && it.resolvedType.returnType.isVoid()
258             }
259 
260         // don't try to find a constructor for binding to statement.
261         val constructor =
262             if (bindingScope == PropertyProcessor.BindingScope.BIND_TO_STMT) {
263                 // we don't need to construct this data class.
264                 null
265             } else {
266                 chooseConstructor(myProperties, embeddedProperties, relations)
267             }
268 
269         assignGetters(myProperties, getterCandidates)
270         assignSetters(myProperties, setterCandidates, constructor)
271 
272         embeddedProperties.forEach {
273             assignGetter(it.property, getterCandidates)
274             assignSetter(it.property, setterCandidates, constructor)
275         }
276 
277         myRelationsList.forEach {
278             assignGetter(it.property, getterCandidates)
279             assignSetter(it.property, setterCandidates, constructor)
280         }
281 
282         return delegate.createDataClass(
283             element,
284             declaredType,
285             propertys,
286             embeddedProperties,
287             relations,
288             constructor
289         )
290     }
291 
292     private fun chooseConstructor(
293         myProperties: List<Property>,
294         embedded: List<EmbeddedProperty>,
295         relations: List<androidx.room.vo.Relation>
296     ): Constructor? {
297         val constructors = delegate.findConstructors(element)
298         val propertyMap = myProperties.associateBy { it.name }
299         val embeddedMap = embedded.associateBy { it.property.name }
300         val relationMap = relations.associateBy { it.property.name }
301         // list of param names -> matched params pairs for each failed constructor
302         val failedConstructors = arrayListOf<FailedConstructor>()
303         val goodConstructors =
304             constructors
305                 .map { constructor ->
306                     val parameterNames = constructor.parameters.map { it.name }
307                     val params =
308                         constructor.parameters.mapIndexed param@{ index, param ->
309                             val paramName = parameterNames[index]
310                             val paramType = param.type
311 
312                             val matches =
313                                 fun(property: Property?): Boolean {
314                                     return if (property == null) {
315                                         false
316                                     } else if (!property.nameWithVariations.contains(paramName)) {
317                                         false
318                                     } else {
319                                         // see: b/69164099
320                                         property.type.isAssignableFromWithoutVariance(paramType)
321                                     }
322                                 }
323 
324                             val exactFieldMatch = propertyMap[paramName]
325                             if (matches(exactFieldMatch)) {
326                                 return@param Constructor.Param.PropertyParam(exactFieldMatch!!)
327                             }
328                             val exactEmbeddedMatch = embeddedMap[paramName]
329                             if (matches(exactEmbeddedMatch?.property)) {
330                                 return@param Constructor.Param.EmbeddedParam(exactEmbeddedMatch!!)
331                             }
332                             val exactRelationMatch = relationMap[paramName]
333                             if (matches(exactRelationMatch?.property)) {
334                                 return@param Constructor.Param.RelationParam(exactRelationMatch!!)
335                             }
336 
337                             val matchingProperties = myProperties.filter { matches(it) }
338                             val embeddedMatches = embedded.filter { matches(it.property) }
339                             val relationMatches = relations.filter { matches(it.property) }
340                             when (
341                                 matchingProperties.size +
342                                     embeddedMatches.size +
343                                     relationMatches.size
344                             ) {
345                                 0 -> null
346                                 1 ->
347                                     when {
348                                         matchingProperties.isNotEmpty() ->
349                                             Constructor.Param.PropertyParam(
350                                                 matchingProperties.first()
351                                             )
352                                         embeddedMatches.isNotEmpty() ->
353                                             Constructor.Param.EmbeddedParam(embeddedMatches.first())
354                                         else ->
355                                             Constructor.Param.RelationParam(relationMatches.first())
356                                     }
357                                 else -> {
358                                     context.logger.e(
359                                         param,
360                                         ProcessorErrors.ambiguousConstructor(
361                                             dataClass = element.qualifiedName,
362                                             paramName = paramName,
363                                             matchingProperties =
364                                                 matchingProperties.map { it.getPath() } +
365                                                     embeddedMatches.map { it.property.getPath() } +
366                                                     relationMatches.map { it.property.getPath() }
367                                         )
368                                     )
369                                     null
370                                 }
371                             }
372                         }
373                     if (params.any { it == null }) {
374                         failedConstructors.add(
375                             FailedConstructor(constructor, parameterNames, params)
376                         )
377                         null
378                     } else {
379                         @Suppress("UNCHECKED_CAST")
380                         Constructor(constructor, params as List<Constructor.Param>)
381                     }
382                 }
383                 .filterNotNull()
384         when {
385             goodConstructors.isEmpty() -> {
386                 if (failedConstructors.isNotEmpty()) {
387                     val failureMsg = failedConstructors.joinToString("\n") { entry -> entry.log() }
388                     context.logger.e(
389                         element,
390                         ProcessorErrors.MISSING_DATA_CLASS_CONSTRUCTOR +
391                             "\nTried the following constructors but they failed to match:" +
392                             "\n$failureMsg"
393                     )
394                 }
395                 context.logger.e(element, ProcessorErrors.MISSING_DATA_CLASS_CONSTRUCTOR)
396                 return null
397             }
398             goodConstructors.size > 1 -> {
399                 // if the class is a Kotlin data class (not a POJO) then pick its primary
400                 // constructor. This is better than picking the no-arg constructor and forcing
401                 // users to define propertys as
402                 // vars.
403                 val primaryConstructor =
404                     element.findPrimaryConstructor()?.let { primary ->
405                         goodConstructors.firstOrNull { candidate -> candidate.element == primary }
406                     }
407                 if (primaryConstructor != null) {
408                     return primaryConstructor
409                 }
410                 // if there is a no-arg constructor, pick it. Even though it is weird, easily
411                 // happens
412                 // with kotlin data classes.
413                 val noArg = goodConstructors.firstOrNull { it.params.isEmpty() }
414                 if (noArg != null) {
415                     context.logger.w(
416                         Warning.DEFAULT_CONSTRUCTOR,
417                         element,
418                         ProcessorErrors.TOO_MANY_DATA_CLASS_CONSTRUCTORS_CHOOSING_NO_ARG
419                     )
420                     return noArg
421                 }
422                 goodConstructors.forEach {
423                     context.logger.e(it.element, ProcessorErrors.TOO_MANY_DATA_CLASS_CONSTRUCTORS)
424                 }
425                 return null
426             }
427             else -> return goodConstructors.first()
428         }
429     }
430 
431     private fun processEmbeddedField(
432         declaredType: XType,
433         variableElement: XFieldElement
434     ): EmbeddedProperty? {
435         val asMemberType = variableElement.asMemberOf(declaredType)
436         val asTypeElement = asMemberType.typeElement
437         if (asTypeElement == null) {
438             context.logger.e(
439                 variableElement,
440                 ProcessorErrors.EMBEDDED_TYPES_MUST_BE_A_CLASS_OR_INTERFACE
441             )
442             return null
443         }
444 
445         if (detectReferenceRecursion(asTypeElement)) {
446             return null
447         }
448 
449         val embeddedAnnotation = variableElement.getAnnotation(Embedded::class)
450         val propertyPrefix = embeddedAnnotation?.get("prefix")?.asString() ?: ""
451         val inheritedPrefix = parent?.prefix ?: ""
452         val embeddedProperty =
453             Property(
454                 variableElement,
455                 variableElement.name,
456                 type = asMemberType,
457                 affinity = null,
458                 parent = parent
459             )
460         val subParent =
461             EmbeddedProperty(
462                 property = embeddedProperty,
463                 prefix = inheritedPrefix + propertyPrefix,
464                 parent = parent
465             )
466         subParent.dataClass =
467             createFor(
468                     context = context.fork(variableElement),
469                     element = asTypeElement,
470                     bindingScope = bindingScope,
471                     parent = subParent,
472                     referenceStack = referenceStack
473                 )
474                 .process()
475         return subParent
476     }
477 
478     private fun processRelationField(
479         myProperties: List<Property>,
480         container: XType,
481         relationElement: XFieldElement
482     ): androidx.room.vo.Relation? {
483         val annotation = relationElement.requireAnnotation(Relation::class)
484 
485         val parentColumnName = annotation.getAsString("parentColumn")
486         val parentField = myProperties.firstOrNull { it.columnName == parentColumnName }
487         if (parentField == null) {
488             context.logger.e(
489                 relationElement,
490                 ProcessorErrors.relationCannotFindParentEntityProperty(
491                     entityName = element.qualifiedName,
492                     columnName = parentColumnName,
493                     availableColumns = myProperties.map { it.columnName }
494                 )
495             )
496             return null
497         }
498         // parse it as an entity.
499         val asMember = relationElement.asMemberOf(container)
500         val asType =
501             if (asMember.isCollection()) {
502                 asMember.typeArguments.first().extendsBoundOrSelf()
503             } else {
504                 asMember
505             }
506         val typeElement = asType.typeElement
507         if (typeElement == null) {
508             context.logger.e(
509                 relationElement,
510                 ProcessorErrors.RELATION_TYPE_MUST_BE_A_CLASS_OR_INTERFACE
511             )
512             return null
513         }
514 
515         val entityClassInput = annotation["entity"]?.asType()
516 
517         // do we need to decide on the entity?
518         val inferEntity = (entityClassInput == null || entityClassInput.isTypeOf(Any::class))
519         val entityElement =
520             if (inferEntity) {
521                 typeElement
522             } else {
523                 entityClassInput.typeElement
524             }
525         if (entityElement == null) {
526             // this should not happen as we check for declared above but for compile time
527             // null safety, it is still good to have this additional check here.
528             context.logger.e(
529                 typeElement,
530                 ProcessorErrors.RELATION_TYPE_MUST_BE_A_CLASS_OR_INTERFACE
531             )
532             return null
533         }
534 
535         if (detectReferenceRecursion(entityElement)) {
536             return null
537         }
538 
539         val entity = EntityOrViewProcessor(context, entityElement, referenceStack).process()
540 
541         // now find the property in the entity.
542         val entityColumnName = annotation.getAsString("entityColumn")
543         val entityField = entity.findPropertyByColumnName(entityColumnName)
544         if (entityField == null) {
545             context.logger.e(
546                 relationElement,
547                 ProcessorErrors.relationCannotFindEntityProperty(
548                     entityName = entity.typeName.toString(context.codeLanguage),
549                     columnName = entityColumnName,
550                     availableColumns = entity.columnNames
551                 )
552             )
553             return null
554         }
555 
556         // do we have a join entity?
557         val junctionAnnotation = annotation["associateBy"]?.asAnnotation()
558         val junctionClassInput = junctionAnnotation?.getAsType("value")
559         val junctionElement: XTypeElement? =
560             if (junctionClassInput != null && !junctionClassInput.isTypeOf(Any::class)) {
561                 junctionClassInput.typeElement.also {
562                     if (it == null) {
563                         context.logger.e(relationElement, ProcessorErrors.NOT_ENTITY_OR_VIEW)
564                     }
565                 }
566             } else {
567                 null
568             }
569         val junction =
570             if (junctionAnnotation != null && junctionElement != null) {
571                 val entityOrView =
572                     EntityOrViewProcessor(context, junctionElement, referenceStack).process()
573 
574                 fun findAndValidateJunctionColumn(
575                     columnName: String,
576                     onMissingField: () -> Unit
577                 ): Property? {
578                     val property = entityOrView.findPropertyByColumnName(columnName)
579                     if (property == null) {
580                         onMissingField()
581                         return null
582                     }
583                     if (entityOrView is Entity) {
584                         // warn about not having indices in the junction columns, only considering
585                         // 1st column in composite primary key and indices, since order matters.
586                         val coveredColumns =
587                             entityOrView.primaryKey.properties.columnNames.first() +
588                                 entityOrView.indices.map { it.columnNames.first() }
589                         if (!coveredColumns.contains(property.columnName)) {
590                             context.logger.w(
591                                 Warning.MISSING_INDEX_ON_JUNCTION,
592                                 property.element,
593                                 ProcessorErrors.junctionColumnWithoutIndex(
594                                     entityName =
595                                         entityOrView.typeName.toString(context.codeLanguage),
596                                     columnName = columnName
597                                 )
598                             )
599                         }
600                     }
601                     return property
602                 }
603 
604                 val junctionParentColumnName = junctionAnnotation["parentColumn"]?.asString() ?: ""
605                 val junctionParentColumn =
606                     if (junctionParentColumnName.isNotEmpty()) {
607                         junctionParentColumnName
608                     } else {
609                         parentField.columnName
610                     }
611                 val junctionParentField =
612                     findAndValidateJunctionColumn(
613                         columnName = junctionParentColumn,
614                         onMissingField = {
615                             context.logger.e(
616                                 junctionElement,
617                                 ProcessorErrors.relationCannotFindJunctionParentProperty(
618                                     entityName =
619                                         entityOrView.typeName.toString(context.codeLanguage),
620                                     columnName = junctionParentColumn,
621                                     availableColumns = entityOrView.columnNames
622                                 )
623                             )
624                         }
625                     )
626 
627                 val junctionEntityColumnName = junctionAnnotation["entityColumn"]?.asString() ?: ""
628                 val junctionEntityColumn =
629                     if (junctionEntityColumnName.isNotEmpty()) {
630                         junctionEntityColumnName
631                     } else {
632                         entityField.columnName
633                     }
634                 val junctionEntityField =
635                     findAndValidateJunctionColumn(
636                         columnName = junctionEntityColumn,
637                         onMissingField = {
638                             context.logger.e(
639                                 junctionElement,
640                                 ProcessorErrors.relationCannotFindJunctionEntityProperty(
641                                     entityName =
642                                         entityOrView.typeName.toString(context.codeLanguage),
643                                     columnName = junctionEntityColumn,
644                                     availableColumns = entityOrView.columnNames
645                                 )
646                             )
647                         }
648                     )
649 
650                 if (junctionParentField == null || junctionEntityField == null) {
651                     return null
652                 }
653 
654                 androidx.room.vo.Junction(
655                     entity = entityOrView,
656                     parentProperty = junctionParentField,
657                     entityProperty = junctionEntityField
658                 )
659             } else {
660                 null
661             }
662 
663         val property =
664             Property(
665                 element = relationElement,
666                 name = relationElement.name,
667                 type = relationElement.asMemberOf(container),
668                 affinity = null,
669                 parent = parent
670             )
671 
672         val projectionNames = annotation["projection"]?.asStringList() ?: emptyList()
673         val projection =
674             if (projectionNames.isEmpty()) {
675                 // we need to infer the projection from inputs.
676                 createRelationshipProjection(inferEntity, asType, entity, entityField, typeElement)
677             } else {
678                 // make sure projection makes sense
679                 validateRelationshipProjection(projectionNames, entity, relationElement)
680                 projectionNames
681             }
682         // if types don't match, row adapter prints a warning
683         return androidx.room.vo.Relation(
684             entity = entity,
685             dataClassType = asType,
686             property = property,
687             parentProperty = parentField,
688             entityProperty = entityField,
689             junction = junction,
690             projection = projection
691         )
692     }
693 
694     private fun validateRelationshipProjection(
695         projectionInput: List<String>,
696         entity: EntityOrView,
697         relationElement: XVariableElement
698     ) {
699         val missingColumns = projectionInput.toList() - entity.columnNames
700         if (missingColumns.isNotEmpty()) {
701             context.logger.e(
702                 relationElement,
703                 ProcessorErrors.relationBadProject(
704                     entity.typeName.toString(context.codeLanguage),
705                     missingColumns,
706                     entity.columnNames
707                 )
708             )
709         }
710     }
711 
712     /**
713      * Create the projection column list based on the relationship args.
714      *
715      * if entity property in the annotation is not specified, it is the method return type if it is
716      * specified in the annotation: still check the method return type, if the same, use it if not,
717      * check to see if we can find a column Adapter, if so use the childField last resort, try to
718      * parse it as a data class to infer it.
719      */
720     private fun createRelationshipProjection(
721         inferEntity: Boolean,
722         typeArg: XType,
723         entity: EntityOrView,
724         entityField: Property,
725         typeArgElement: XTypeElement
726     ): List<String> {
727         return if (inferEntity || typeArg.asTypeName() == entity.typeName) {
728             entity.columnNames
729         } else {
730             val columnAdapter = context.typeAdapterStore.findStatementValueReader(typeArg, null)
731             if (columnAdapter != null) {
732                 // nice, there is a column adapter for this, assume single column response
733                 listOf(entityField.name)
734             } else {
735                 // last resort, it needs to be a data class
736                 val dataClass =
737                     createFor(
738                             context = context,
739                             element = typeArgElement,
740                             bindingScope = PropertyProcessor.BindingScope.READ_FROM_STMT,
741                             parent = parent,
742                             referenceStack = referenceStack
743                         )
744                         .process()
745                 dataClass.columnNames
746             }
747         }
748     }
749 
750     private fun detectReferenceRecursion(typeElement: XTypeElement): Boolean {
751         if (referenceStack.contains(typeElement.qualifiedName)) {
752             context.logger.e(
753                 typeElement,
754                 ProcessorErrors.RECURSIVE_REFERENCE_DETECTED.format(
755                     computeReferenceRecursionString(typeElement)
756                 )
757             )
758             return true
759         }
760         return false
761     }
762 
763     private fun computeReferenceRecursionString(typeElement: XTypeElement): String {
764         val recursiveTailTypeName = typeElement.qualifiedName
765 
766         val referenceRecursionList = mutableListOf<String>()
767         with(referenceRecursionList) {
768             add(recursiveTailTypeName)
769             addAll(referenceStack.toList().takeLastWhile { it != recursiveTailTypeName })
770             add(recursiveTailTypeName)
771         }
772 
773         return referenceRecursionList.joinToString(" -> ")
774     }
775 
776     private fun assignGetters(
777         propertys: List<Property>,
778         getterCandidates: List<DataClassFunction>
779     ) {
780         propertys.forEach { property -> assignGetter(property, getterCandidates) }
781     }
782 
783     private fun assignGetter(property: Property, getterCandidates: List<DataClassFunction>) {
784         val success =
785             chooseAssignment(
786                 property = property,
787                 candidates = getterCandidates,
788                 nameVariations = property.getterNameWithVariations,
789                 getType = { method -> method.resolvedType.returnType },
790                 assignFromField = {
791                     property.getter =
792                         PropertyGetter(
793                             propertyName = property.name,
794                             jvmName = property.name,
795                             type = property.type,
796                             callType = CallType.PROPERTY
797                         )
798                 },
799                 assignFromMethod = { match ->
800                     property.getter =
801                         PropertyGetter(
802                             propertyName = property.name,
803                             jvmName = match.element.jvmName,
804                             type = match.resolvedType.returnType,
805                             callType =
806                                 if (match.element.isKotlinPropertyMethod()) {
807                                     CallType.SYNTHETIC_FUNCTION
808                                 } else {
809                                     CallType.FUNCTION
810                                 }
811                         )
812                 },
813                 reportAmbiguity = { matching ->
814                     context.logger.e(
815                         property.element,
816                         ProcessorErrors.tooManyMatchingGetters(property, matching)
817                     )
818                 }
819             )
820         context.checker.check(
821             success || bindingScope == PropertyProcessor.BindingScope.READ_FROM_STMT,
822             property.element,
823             CANNOT_FIND_GETTER_FOR_PROPERTY
824         )
825         if (success && !property.getter.type.isSameType(property.type)) {
826             // getter's parameter type is not exactly the same as the property type.
827             // put a warning and update the value statement binder.
828             context.logger.w(
829                 warning = Warning.MISMATCHED_GETTER_TYPE,
830                 element = property.element,
831                 msg =
832                     ProcessorErrors.mismatchedGetter(
833                         propertyName = property.name,
834                         ownerType = element.type.asTypeName().toString(context.codeLanguage),
835                         getterType =
836                             property.getter.type.asTypeName().toString(context.codeLanguage),
837                         propertyType = property.typeName.toString(context.codeLanguage)
838                     )
839             )
840             property.statementBinder =
841                 context.typeAdapterStore.findStatementValueBinder(
842                     input = property.getter.type,
843                     affinity = property.affinity
844                 )
845         }
846     }
847 
848     private fun assignSetters(
849         propertys: List<Property>,
850         setterCandidates: List<DataClassFunction>,
851         constructor: Constructor?
852     ) {
853         propertys.forEach { property -> assignSetter(property, setterCandidates, constructor) }
854     }
855 
856     private fun assignSetter(
857         property: Property,
858         setterCandidates: List<DataClassFunction>,
859         constructor: Constructor?
860     ) {
861         if (constructor != null && constructor.hasProperty(property)) {
862             property.setter =
863                 PropertySetter(
864                     propertyName = property.name,
865                     jvmName = property.name,
866                     type = property.type,
867                     callType = CallType.CONSTRUCTOR
868                 )
869             return
870         }
871         val success =
872             chooseAssignment(
873                 property = property,
874                 candidates = setterCandidates,
875                 nameVariations = property.setterNameWithVariations,
876                 getType = { method -> method.resolvedType.parameterTypes.first() },
877                 assignFromField = {
878                     property.setter =
879                         PropertySetter(
880                             propertyName = property.name,
881                             jvmName = property.name,
882                             type = property.type,
883                             callType = CallType.PROPERTY
884                         )
885                 },
886                 assignFromMethod = { match ->
887                     val paramType = match.resolvedType.parameterTypes.first()
888                     property.setter =
889                         PropertySetter(
890                             propertyName = property.name,
891                             jvmName = match.element.jvmName,
892                             type = paramType,
893                             callType =
894                                 if (match.element.isKotlinPropertyMethod()) {
895                                     CallType.SYNTHETIC_FUNCTION
896                                 } else {
897                                     CallType.FUNCTION
898                                 }
899                         )
900                 },
901                 reportAmbiguity = { matching ->
902                     context.logger.e(
903                         property.element,
904                         ProcessorErrors.tooManyMatchingSetter(property, matching)
905                     )
906                 }
907             )
908         context.checker.check(
909             success || bindingScope == PropertyProcessor.BindingScope.BIND_TO_STMT,
910             property.element,
911             CANNOT_FIND_SETTER_FOR_PROPERTY
912         )
913         if (success && !property.setter.type.isSameType(property.type)) {
914             // setter's parameter type is not exactly the same as the property type.
915             // put a warning and update the value reader adapter.
916             context.logger.w(
917                 warning = Warning.MISMATCHED_SETTER_TYPE,
918                 element = property.element,
919                 msg =
920                     ProcessorErrors.mismatchedSetter(
921                         propertyName = property.name,
922                         ownerType = element.type.asTypeName().toString(context.codeLanguage),
923                         setterType =
924                             property.setter.type.asTypeName().toString(context.codeLanguage),
925                         propertyType = property.typeName.toString(context.codeLanguage)
926                     )
927             )
928             property.statementValueReader =
929                 context.typeAdapterStore.findStatementValueReader(
930                     output = property.setter.type,
931                     affinity = property.affinity
932                 )
933         }
934     }
935 
936     /**
937      * Finds a setter/getter from available list of methods. It returns true if assignment is
938      * successful, false otherwise. At worst case, it sets to the property as if it is accessible so
939      * that the rest of the compilation can continue.
940      */
941     private fun chooseAssignment(
942         property: Property,
943         candidates: List<DataClassFunction>,
944         nameVariations: List<String>,
945         getType: (DataClassFunction) -> XType,
946         assignFromField: () -> Unit,
947         assignFromMethod: (DataClassFunction) -> Unit,
948         reportAmbiguity: (List<String>) -> Unit
949     ): Boolean {
950         if (property.element.isPublic()) {
951             assignFromField()
952             return true
953         }
954 
955         val matching =
956             candidates
957                 .filter {
958                     // b/69164099
959                     // use names in source (rather than jvmName) for matching since that is what
960                     // user
961                     // sees in code
962                     property.type.isAssignableFromWithoutVariance(getType(it)) &&
963                         (property.nameWithVariations.contains(it.element.name) ||
964                             nameVariations.contains(it.element.name))
965                 }
966                 .groupBy { it.element.isPublic() }
967         if (matching.isEmpty()) {
968             // we always assign to avoid NPEs in the rest of the compilation.
969             assignFromField()
970             // if property is not private, assume it works (if we are on the same package).
971             // if not, compiler will tell, we didn't have any better alternative anyways.
972             return !property.element.isPrivate()
973         }
974         // first try public ones, then try non-public
975         val match =
976             verifyAndChooseOneFrom(matching[true], reportAmbiguity)
977                 ?: verifyAndChooseOneFrom(matching[false], reportAmbiguity)
978         if (match == null) {
979             assignFromField()
980             return false
981         } else {
982             assignFromMethod(match)
983             return true
984         }
985     }
986 
987     private fun verifyAndChooseOneFrom(
988         candidates: List<DataClassFunction>?,
989         reportAmbiguity: (List<String>) -> Unit
990     ): DataClassFunction? {
991         if (candidates == null) {
992             return null
993         }
994         if (candidates.size > 1) {
995             reportAmbiguity(candidates.map { it.element.name })
996         }
997         return candidates.first()
998     }
999 
1000     interface Delegate {
1001 
1002         fun onPreProcess(element: XTypeElement)
1003 
1004         /**
1005          * Constructors are XExecutableElement rather than XConstructorElement to account for
1006          * factory methods.
1007          */
1008         fun findConstructors(element: XTypeElement): List<XExecutableElement>
1009 
1010         fun createDataClass(
1011             element: XTypeElement,
1012             declaredType: XType,
1013             properties: List<Property>,
1014             embeddedProperties: List<EmbeddedProperty>,
1015             relations: List<androidx.room.vo.Relation>,
1016             constructor: Constructor?
1017         ): DataClass
1018     }
1019 
1020     private class DefaultDelegate(private val context: Context) : Delegate {
1021         override fun onPreProcess(element: XTypeElement) {
1022             // If data class is not a record then check that certain Room annotations with
1023             // @Target(METHOD) are not used since the data class is not annotated with AutoValue
1024             // where Room column annotations are allowed in methods.
1025             if (!element.isRecordClass()) {
1026                 element
1027                     .getAllMethods()
1028                     .filter { it.hasAnyAnnotation(*TARGET_METHOD_ANNOTATIONS) }
1029                     .forEach { method ->
1030                         val annotationName =
1031                             TARGET_METHOD_ANNOTATIONS.first { columnAnnotation ->
1032                                     method.hasAnnotation(columnAnnotation)
1033                                 }
1034                                 .java
1035                                 .simpleName
1036                         context.logger.e(
1037                             method,
1038                             ProcessorErrors.invalidAnnotationTarget(
1039                                 annotationName,
1040                                 method.kindName()
1041                             )
1042                         )
1043                     }
1044             }
1045         }
1046 
1047         override fun findConstructors(element: XTypeElement) =
1048             element.getConstructors().filterNot {
1049                 it.hasAnnotation(Ignore::class) || it.isPrivate()
1050             }
1051 
1052         override fun createDataClass(
1053             element: XTypeElement,
1054             declaredType: XType,
1055             properties: List<Property>,
1056             embeddedProperties: List<EmbeddedProperty>,
1057             relations: List<androidx.room.vo.Relation>,
1058             constructor: Constructor?
1059         ): DataClass {
1060             return DataClass(
1061                 element = element,
1062                 type = declaredType,
1063                 properties = properties,
1064                 embeddedProperties = embeddedProperties,
1065                 relations = relations,
1066                 constructor = constructor
1067             )
1068         }
1069     }
1070 
1071     private object EmptyDelegate : Delegate {
1072         override fun onPreProcess(element: XTypeElement) {}
1073 
1074         override fun findConstructors(element: XTypeElement): List<XExecutableElement> = emptyList()
1075 
1076         override fun createDataClass(
1077             element: XTypeElement,
1078             declaredType: XType,
1079             properties: List<Property>,
1080             embeddedProperties: List<EmbeddedProperty>,
1081             relations: List<androidx.room.vo.Relation>,
1082             constructor: Constructor?
1083         ): DataClass {
1084             return DataClass(
1085                 element = element,
1086                 type = declaredType,
1087                 properties = emptyList(),
1088                 embeddedProperties = emptyList(),
1089                 relations = emptyList(),
1090                 constructor = null
1091             )
1092         }
1093     }
1094 
1095     private data class FailedConstructor(
1096         val method: XExecutableElement,
1097         val params: List<String>,
1098         val matches: List<Constructor.Param?>
1099     ) {
1100         fun log(): String {
1101             val logPerParam =
1102                 params.withIndex().joinToString(", ") {
1103                     "param:${it.value} -> matched property:" +
1104                         (matches[it.index]?.log() ?: "unmatched")
1105                 }
1106             return "$method -> [$logPerParam]"
1107         }
1108     }
1109 }
1110