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