1/* 2 * 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 17package android.arch.persistence.room.processor 18 19import android.arch.persistence.room.Relation 20import android.arch.persistence.room.ColumnInfo 21import android.arch.persistence.room.Embedded 22import android.arch.persistence.room.Ignore 23import android.arch.persistence.room.ext.getAllFieldsIncludingPrivateSupers 24import android.arch.persistence.room.ext.getAnnotationValue 25import android.arch.persistence.room.ext.getAsString 26import android.arch.persistence.room.ext.getAsStringList 27import android.arch.persistence.room.ext.hasAnnotation 28import android.arch.persistence.room.ext.hasAnyOf 29import android.arch.persistence.room.ext.isCollection 30import android.arch.persistence.room.ext.toClassType 31import android.arch.persistence.room.processor.ProcessorErrors.CANNOT_FIND_GETTER_FOR_FIELD 32import android.arch.persistence.room.processor.ProcessorErrors.CANNOT_FIND_SETTER_FOR_FIELD 33import android.arch.persistence.room.processor.ProcessorErrors.CANNOT_FIND_TYPE 34import android.arch.persistence.room.processor.ProcessorErrors.POJO_FIELD_HAS_DUPLICATE_COLUMN_NAME 35import android.arch.persistence.room.processor.cache.Cache 36import android.arch.persistence.room.vo.CallType 37import android.arch.persistence.room.vo.Constructor 38import android.arch.persistence.room.vo.Field 39import android.arch.persistence.room.vo.FieldGetter 40import android.arch.persistence.room.vo.EmbeddedField 41import android.arch.persistence.room.vo.Entity 42import android.arch.persistence.room.vo.FieldSetter 43import android.arch.persistence.room.vo.Pojo 44import com.google.auto.common.AnnotationMirrors 45import com.google.auto.common.MoreElements 46import com.google.auto.common.MoreTypes 47import javax.lang.model.element.Element 48import javax.lang.model.element.ExecutableElement 49import javax.lang.model.element.Modifier 50import javax.lang.model.element.Modifier.ABSTRACT 51import javax.lang.model.element.Modifier.PRIVATE 52import javax.lang.model.element.Modifier.PROTECTED 53import javax.lang.model.element.Modifier.PUBLIC 54import javax.lang.model.element.Modifier.STATIC 55import javax.lang.model.element.Modifier.TRANSIENT 56import javax.lang.model.element.TypeElement 57import javax.lang.model.element.VariableElement 58import javax.lang.model.type.DeclaredType 59import javax.lang.model.type.TypeKind 60import javax.lang.model.type.TypeMirror 61import javax.lang.model.util.ElementFilter 62 63/** 64 * Processes any class as if it is a Pojo. 65 */ 66class PojoProcessor(baseContext: Context, val element: TypeElement, 67 val bindingScope: FieldProcessor.BindingScope, 68 val parent: EmbeddedField?) { 69 val context = baseContext.fork(element) 70 companion object { 71 val PROCESSED_ANNOTATIONS = listOf(ColumnInfo::class, Embedded::class, 72 Relation::class) 73 } 74 fun process() : Pojo { 75 return context.cache.pojos.get(Cache.PojoKey(element, bindingScope, parent), { 76 doProcess() 77 }) 78 } 79 80 private fun doProcess(): Pojo { 81 // TODO handle recursion: b/35980205 82 val declaredType = MoreTypes.asDeclared(element.asType()) 83 // TODO handle conflicts with super: b/35568142 84 val allFields = element.getAllFieldsIncludingPrivateSupers(context.processingEnv) 85 .filter { 86 !it.hasAnnotation(Ignore::class) 87 && !it.hasAnyOf(STATIC) 88 && (!it.hasAnyOf(TRANSIENT) 89 || it.hasAnnotation(ColumnInfo::class) 90 || it.hasAnnotation(Embedded::class) 91 || it.hasAnnotation(Relation::class)) 92 } 93 .groupBy { field -> 94 context.checker.check( 95 PROCESSED_ANNOTATIONS.count { field.hasAnnotation(it) } < 2, field, 96 ProcessorErrors.CANNOT_USE_MORE_THAN_ONE_POJO_FIELD_ANNOTATION 97 ) 98 if (field.hasAnnotation(Embedded::class)) { 99 Embedded::class 100 } else if (field.hasAnnotation(Relation::class)) { 101 Relation::class 102 } else { 103 null 104 } 105 } 106 val myFields = allFields[null] 107 ?.map { 108 FieldProcessor( 109 baseContext = context, 110 containing = declaredType, 111 element = it, 112 bindingScope = bindingScope, 113 fieldParent = parent).process() 114 } ?: emptyList() 115 116 val embeddedFields = allFields[Embedded::class] 117 ?.map { 118 processEmbeddedField(declaredType, it) 119 } ?: emptyList() 120 val subFields = embeddedFields.flatMap { it.pojo.fields } 121 122 val fields = myFields + subFields 123 124 val myRelationsList = allFields[Relation::class] 125 ?.map { 126 processRelationField(fields, declaredType, it) 127 } 128 ?.filterNotNull() ?: emptyList() 129 130 val subRelations = embeddedFields.flatMap { it.pojo.relations } 131 132 val relations = myRelationsList + subRelations 133 134 fields.groupBy { it.columnName } 135 .filter { it.value.size > 1 } 136 .forEach { 137 context.logger.e(element, ProcessorErrors.pojoDuplicateFieldNames( 138 it.key, it.value.map(Field::getPath) 139 )) 140 it.value.forEach { 141 context.logger.e(it.element, POJO_FIELD_HAS_DUPLICATE_COLUMN_NAME) 142 } 143 } 144 val methods = MoreElements.getLocalAndInheritedMethods(element, 145 context.processingEnv.elementUtils) 146 .filter { 147 !it.hasAnyOf(PRIVATE, ABSTRACT, STATIC) 148 && !it.hasAnnotation(Ignore::class) 149 } 150 .map { MoreElements.asExecutable(it) } 151 152 val getterCandidates = methods.filter { 153 it.parameters.size == 0 && it.returnType.kind != TypeKind.VOID 154 } 155 156 val setterCandidates = methods.filter { 157 it.parameters.size == 1 && it.returnType.kind == TypeKind.VOID 158 } 159 // don't try to find a constructor for binding to statement. 160 val constructor = if (bindingScope == FieldProcessor.BindingScope.BIND_TO_STMT) { 161 // we don't need to construct this POJO. 162 null 163 } else { 164 chooseConstructor(myFields, embeddedFields) 165 } 166 167 assignGetters(myFields, getterCandidates) 168 assignSetters(myFields, setterCandidates, constructor) 169 170 embeddedFields.forEach { 171 assignGetter(it.field, getterCandidates) 172 assignSetter(it.field, setterCandidates, constructor) 173 } 174 175 myRelationsList.forEach { 176 assignGetter(it.field, getterCandidates) 177 assignSetter(it.field, setterCandidates, constructor) 178 } 179 180 val pojo = Pojo(element = element, 181 type = declaredType, 182 fields = fields, 183 embeddedFields = embeddedFields, 184 relations = relations, 185 constructor = constructor) 186 return pojo 187 } 188 189 private fun chooseConstructor(myFields: List<Field>, embedded: List<EmbeddedField>) 190 : Constructor? { 191 val constructors = ElementFilter.constructorsIn(element.enclosedElements) 192 .filterNot { it.hasAnnotation(Ignore::class) || it.hasAnyOf(PRIVATE) } 193 val fieldMap = myFields.associateBy { it.name } 194 val embeddedMap = embedded.associateBy { it.field.name } 195 val typeUtils = context.processingEnv.typeUtils 196 val failedConstructors = mutableMapOf<ExecutableElement, List<Constructor.Param?>>() 197 val goodConstructors = constructors.map { constructor -> 198 val params = constructor.parameters.map param@ { param -> 199 val paramName = param.simpleName.toString() 200 val paramType = param.asType() 201 202 val matches = fun(field: Field?): Boolean { 203 return if (field == null) { 204 false 205 } else if (!field.nameWithVariations.contains(paramName)) { 206 false 207 } else { 208 typeUtils.isAssignable(paramType, field.type) 209 } 210 } 211 212 val exactFieldMatch = fieldMap[paramName] 213 214 if (matches(exactFieldMatch)) { 215 return@param Constructor.FieldParam(exactFieldMatch!!) 216 } 217 val exactEmbeddedMatch = embeddedMap[paramName] 218 if (matches(exactEmbeddedMatch?.field)) { 219 return@param Constructor.EmbeddedParam(exactEmbeddedMatch!!) 220 } 221 222 val matchingFields = myFields.filter { 223 matches(it) 224 } 225 val embeddedMatches = embedded.filter { 226 matches(it.field) 227 } 228 if (matchingFields.isEmpty() && embeddedMatches.isEmpty()) { 229 null 230 } else if (matchingFields.size + embeddedMatches.size == 1) { 231 if (matchingFields.isNotEmpty()) { 232 Constructor.FieldParam(matchingFields.first()) 233 } else { 234 Constructor.EmbeddedParam(embeddedMatches.first()) 235 } 236 } else { 237 context.logger.e(param, ProcessorErrors.ambigiousConstructor( 238 pojo = element.qualifiedName.toString(), 239 paramName = param.simpleName.toString(), 240 matchingFields = matchingFields.map { it.getPath() } 241 + embedded.map { it.field.getPath() } 242 )) 243 null 244 } 245 } 246 if (params.any { it == null }) { 247 failedConstructors.put(constructor, params) 248 null 249 } else { 250 @Suppress("UNCHECKED_CAST") 251 Constructor(constructor, params as List<Constructor.Param>) 252 } 253 }.filterNotNull() 254 if (goodConstructors.isEmpty()) { 255 if (failedConstructors.isNotEmpty()) { 256 val failureMsg = failedConstructors.entries.joinToString("\n") { entry -> 257 val paramsMatching = entry.key.parameters.withIndex().joinToString(", ") { 258 "${it.value.simpleName} : ${entry.value[it.index]?.log()}" 259 } 260 "${entry.key} : [$paramsMatching]" 261 } 262 context.logger.e(element, ProcessorErrors.MISSING_POJO_CONSTRUCTOR + 263 "\nTried the following constructors but they failed to match:\n$failureMsg") 264 } 265 context.logger.e(element, ProcessorErrors.MISSING_POJO_CONSTRUCTOR) 266 return null 267 } 268 if (goodConstructors.size > 1) { 269 goodConstructors.forEach { 270 context.logger.e(it.element, ProcessorErrors.TOO_MANY_POJO_CONSTRUCTORS) 271 } 272 return null 273 } 274 return goodConstructors.first() 275 } 276 277 private fun processEmbeddedField(declaredType: DeclaredType?, it: Element): EmbeddedField { 278 val fieldPrefix = it.getAnnotationValue(Embedded::class.java, "prefix") 279 ?.toString() ?: "" 280 val inheritedPrefix = parent?.prefix ?: "" 281 val embeddedField = Field( 282 it, 283 it.simpleName.toString(), 284 type = context.processingEnv.typeUtils.asMemberOf(declaredType, it), 285 affinity = null, 286 parent = parent) 287 val subParent = EmbeddedField( 288 field = embeddedField, 289 prefix = inheritedPrefix + fieldPrefix, 290 parent = parent) 291 val asVariable = MoreElements.asVariable(it) 292 subParent.pojo = PojoProcessor(baseContext = context.fork(it), 293 element = MoreTypes.asTypeElement(asVariable.asType()), 294 bindingScope = bindingScope, 295 parent = subParent).process() 296 return subParent 297 } 298 299 private fun processRelationField(myFields : List<Field>, container: DeclaredType?, 300 relationElement: VariableElement) 301 : android.arch.persistence.room.vo.Relation? { 302 val annotation = MoreElements.getAnnotationMirror(relationElement, Relation::class.java) 303 .orNull()!! 304 val parentColumnInput = AnnotationMirrors.getAnnotationValue(annotation, "parentColumn") 305 .getAsString("") ?: "" 306 307 val parentField = myFields.firstOrNull { 308 it.columnName == parentColumnInput 309 } 310 if (parentField == null) { 311 context.logger.e(relationElement, 312 ProcessorErrors.relationCannotFindParentEntityField( 313 entityName = element.qualifiedName.toString(), 314 columnName = parentColumnInput, 315 availableColumns = myFields.map { it.columnName })) 316 return null 317 } 318 // parse it as an entity. 319 val asMember = MoreTypes 320 .asMemberOf(context.processingEnv.typeUtils, container, relationElement) 321 if (asMember.kind == TypeKind.ERROR) { 322 context.logger.e(ProcessorErrors.CANNOT_FIND_TYPE, element) 323 return null 324 } 325 val declared = MoreTypes.asDeclared(asMember) 326 if (!declared.isCollection()) { 327 context.logger.e(relationElement, ProcessorErrors.RELATION_NOT_COLLECTION) 328 return null 329 } 330 val typeArg = declared.typeArguments.first() 331 if (typeArg.kind == TypeKind.ERROR) { 332 context.logger.e(MoreTypes.asTypeElement(typeArg), CANNOT_FIND_TYPE) 333 return null 334 } 335 val typeArgElement = MoreTypes.asTypeElement(typeArg) 336 val entityClassInput = AnnotationMirrors 337 .getAnnotationValue(annotation, "entity").toClassType() 338 val pojo : Pojo 339 val entity : Entity 340 if (entityClassInput == null 341 || MoreTypes.isTypeOf(Any::class.java, entityClassInput)) { 342 entity = EntityProcessor(context, typeArgElement).process() 343 pojo = entity 344 } else { 345 entity = EntityProcessor(context, MoreTypes.asTypeElement(entityClassInput)).process() 346 pojo = PojoProcessor(baseContext = context, 347 element = typeArgElement, 348 bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR, 349 parent = parent).process() 350 } 351 // now find the field in the entity. 352 val entityColumnInput = AnnotationMirrors.getAnnotationValue(annotation, "entityColumn") 353 .getAsString() ?: "" 354 val entityField = entity.fields.firstOrNull { 355 it.columnName == entityColumnInput 356 } 357 358 if (entityField == null) { 359 context.logger.e(relationElement, 360 ProcessorErrors.relationCannotFindEntityField( 361 entityName = entity.typeName.toString(), 362 columnName = entityColumnInput, 363 availableColumns = entity.fields.map { it.columnName })) 364 return null 365 } 366 367 val field = Field( 368 element = relationElement, 369 name = relationElement.simpleName.toString(), 370 type = context.processingEnv.typeUtils.asMemberOf(container, relationElement), 371 affinity = null, 372 parent = parent) 373 374 val projection = AnnotationMirrors.getAnnotationValue(annotation, "projection") 375 .getAsStringList() 376 if(projection.isNotEmpty()) { 377 val missingColumns = projection.filterNot { columnName -> 378 entity.fields.any { columnName == it.columnName } 379 } 380 if (missingColumns.isNotEmpty()) { 381 context.logger.e(relationElement, 382 ProcessorErrors.relationBadProject(entity.typeName.toString(), 383 missingColumns, entity.fields.map { it.columnName })) 384 } 385 } 386 387 // if types don't match, row adapter prints a warning 388 return android.arch.persistence.room.vo.Relation( 389 entity = entity, 390 pojo = pojo, 391 field = field, 392 parentField = parentField, 393 entityField = entityField, 394 projection = projection 395 ) 396 } 397 398 private fun assignGetters(fields: List<Field>, getterCandidates: List<ExecutableElement>) { 399 fields.forEach { field -> 400 assignGetter(field, getterCandidates) 401 } 402 } 403 404 private fun assignGetter(field: Field, getterCandidates: List<ExecutableElement>) { 405 val success = chooseAssignment(field = field, 406 candidates = getterCandidates, 407 nameVariations = field.getterNameWithVariations, 408 getType = { method -> 409 method.returnType 410 }, 411 assignFromField = { 412 field.getter = FieldGetter( 413 name = field.name, 414 type = field.type, 415 callType = CallType.FIELD) 416 }, 417 assignFromMethod = { match -> 418 field.getter = FieldGetter( 419 name = match.simpleName.toString(), 420 type = match.returnType, 421 callType = CallType.METHOD) 422 }, 423 reportAmbiguity = { matching -> 424 context.logger.e(field.element, 425 ProcessorErrors.tooManyMatchingGetters(field, matching)) 426 }) 427 context.checker.check(success, field.element, CANNOT_FIND_GETTER_FOR_FIELD) 428 } 429 430 private fun assignSetters(fields: List<Field>, setterCandidates: List<ExecutableElement>, 431 constructor : Constructor?) { 432 fields.forEach { field -> 433 assignSetter(field, setterCandidates, constructor) 434 } 435 } 436 437 private fun assignSetter(field: Field, setterCandidates: List<ExecutableElement>, 438 constructor: Constructor?) { 439 if (constructor != null && constructor.hasField(field)) { 440 field.setter = FieldSetter(field.name, field.type, CallType.CONSTRUCTOR) 441 return 442 } 443 val success = chooseAssignment(field = field, 444 candidates = setterCandidates, 445 nameVariations = field.setterNameWithVariations, 446 getType = { method -> 447 method.parameters.first().asType() 448 }, 449 assignFromField = { 450 field.setter = FieldSetter( 451 name = field.name, 452 type = field.type, 453 callType = CallType.FIELD) 454 }, 455 assignFromMethod = { match -> 456 val paramType = match.parameters.first().asType() 457 field.setter = FieldSetter( 458 name = match.simpleName.toString(), 459 type = paramType, 460 callType = CallType.METHOD) 461 }, 462 reportAmbiguity = { matching -> 463 context.logger.e(field.element, 464 ProcessorErrors.tooManyMatchingSetter(field, matching)) 465 }) 466 context.checker.check(success, field.element, CANNOT_FIND_SETTER_FOR_FIELD) 467 } 468 469 /** 470 * Finds a setter/getter from available list of methods. 471 * It returns true if assignment is successful, false otherwise. 472 * At worst case, it sets to the field as if it is accessible so that the rest of the 473 * compilation can continue. 474 */ 475 private fun chooseAssignment(field: Field, candidates: List<ExecutableElement>, 476 nameVariations: List<String>, 477 getType: (ExecutableElement) -> TypeMirror, 478 assignFromField: () -> Unit, 479 assignFromMethod: (ExecutableElement) -> Unit, 480 reportAmbiguity: (List<String>) -> Unit): Boolean { 481 if (field.element.hasAnyOf(PUBLIC)) { 482 assignFromField() 483 return true 484 } 485 val types = context.processingEnv.typeUtils 486 487 val matching = candidates 488 .filter { 489 types.isAssignable(getType(it), field.element.asType()) 490 && (field.nameWithVariations.contains(it.simpleName.toString()) 491 || nameVariations.contains(it.simpleName.toString())) 492 } 493 .groupBy { 494 if (it.hasAnyOf(PUBLIC)) PUBLIC else PROTECTED 495 } 496 if (matching.isEmpty()) { 497 // we always assign to avoid NPEs in the rest of the compilation. 498 assignFromField() 499 // if field is not private, assume it works (if we are on the same package). 500 // if not, compiler will tell, we didn't have any better alternative anyways. 501 return !field.element.hasAnyOf(PRIVATE) 502 } 503 val match = verifyAndChooseOneFrom(matching[PUBLIC], reportAmbiguity) ?: 504 verifyAndChooseOneFrom(matching[PROTECTED], reportAmbiguity) 505 if (match == null) { 506 assignFromField() 507 return false 508 } else { 509 assignFromMethod(match) 510 return true 511 } 512 } 513 514 private fun verifyAndChooseOneFrom(candidates: List<ExecutableElement>?, 515 reportAmbiguity: (List<String>) -> Unit) 516 : ExecutableElement? { 517 if (candidates == null) { 518 return null 519 } 520 if (candidates.size > 1) { 521 reportAmbiguity(candidates.map { it.simpleName.toString() }) 522 } 523 return candidates.first() 524 } 525} 526