1/* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package androidx.room.processor 18 19import androidx.room.ext.getAsBoolean 20import androidx.room.ext.getAsInt 21import androidx.room.ext.getAsString 22import androidx.room.ext.getAsStringList 23import androidx.room.ext.toType 24import androidx.room.parser.SQLTypeAffinity 25import androidx.room.parser.SqlParser 26import androidx.room.processor.ProcessorErrors.INDEX_COLUMNS_CANNOT_BE_EMPTY 27import androidx.room.processor.ProcessorErrors.RELATION_IN_ENTITY 28import androidx.room.processor.cache.Cache 29import androidx.room.vo.EmbeddedField 30import androidx.room.vo.Entity 31import androidx.room.vo.Field 32import androidx.room.vo.ForeignKey 33import androidx.room.vo.ForeignKeyAction 34import androidx.room.vo.Index 35import androidx.room.vo.Pojo 36import androidx.room.vo.PrimaryKey 37import androidx.room.vo.Warning 38import com.google.auto.common.AnnotationMirrors 39import com.google.auto.common.AnnotationMirrors.getAnnotationValue 40import com.google.auto.common.MoreElements 41import com.google.auto.common.MoreTypes 42import javax.lang.model.element.AnnotationMirror 43import javax.lang.model.element.AnnotationValue 44import javax.lang.model.element.Name 45import javax.lang.model.element.TypeElement 46import javax.lang.model.type.TypeKind 47import javax.lang.model.type.TypeMirror 48import javax.lang.model.util.SimpleAnnotationValueVisitor6 49 50class EntityProcessor(baseContext: Context, 51 val element: TypeElement, 52 private val referenceStack: LinkedHashSet<Name> = LinkedHashSet()) { 53 val context = baseContext.fork(element) 54 55 fun process(): Entity { 56 return context.cache.entities.get(Cache.EntityKey(element), { 57 doProcess() 58 }) 59 } 60 private fun doProcess(): Entity { 61 context.checker.hasAnnotation(element, androidx.room.Entity::class, 62 ProcessorErrors.ENTITY_MUST_BE_ANNOTATED_WITH_ENTITY) 63 val pojo = PojoProcessor( 64 baseContext = context, 65 element = element, 66 bindingScope = FieldProcessor.BindingScope.TWO_WAY, 67 parent = null, 68 referenceStack = referenceStack).process() 69 context.checker.check(pojo.relations.isEmpty(), element, RELATION_IN_ENTITY) 70 val annotation = MoreElements.getAnnotationMirror(element, 71 androidx.room.Entity::class.java).orNull() 72 val tableName: String 73 val entityIndices: List<IndexInput> 74 val foreignKeyInputs: List<ForeignKeyInput> 75 val inheritSuperIndices: Boolean 76 if (annotation != null) { 77 tableName = extractTableName(element, annotation) 78 entityIndices = extractIndices(annotation, tableName) 79 inheritSuperIndices = AnnotationMirrors 80 .getAnnotationValue(annotation, "inheritSuperIndices").getAsBoolean(false) 81 foreignKeyInputs = extractForeignKeys(annotation) 82 } else { 83 tableName = element.simpleName.toString() 84 foreignKeyInputs = emptyList() 85 entityIndices = emptyList() 86 inheritSuperIndices = false 87 } 88 context.checker.notBlank(tableName, element, 89 ProcessorErrors.ENTITY_TABLE_NAME_CANNOT_BE_EMPTY) 90 91 val fieldIndices = pojo.fields 92 .filter { it.indexed }.mapNotNull { 93 if (it.parent != null) { 94 it.indexed = false 95 context.logger.w(Warning.INDEX_FROM_EMBEDDED_FIELD_IS_DROPPED, it.element, 96 ProcessorErrors.droppedEmbeddedFieldIndex( 97 it.getPath(), element.qualifiedName.toString())) 98 null 99 } else if (it.element.enclosingElement != element && !inheritSuperIndices) { 100 it.indexed = false 101 context.logger.w(Warning.INDEX_FROM_PARENT_FIELD_IS_DROPPED, 102 ProcessorErrors.droppedSuperClassFieldIndex( 103 it.columnName, element.toString(), 104 it.element.enclosingElement.toString() 105 )) 106 null 107 } else { 108 IndexInput( 109 name = createIndexName(listOf(it.columnName), tableName), 110 unique = false, 111 columnNames = listOf(it.columnName) 112 ) 113 } 114 } 115 val superIndices = loadSuperIndices(element.superclass, tableName, inheritSuperIndices) 116 val indexInputs = entityIndices + fieldIndices + superIndices 117 val indices = validateAndCreateIndices(indexInputs, pojo) 118 119 val primaryKey = findAndValidatePrimaryKey(pojo.fields, pojo.embeddedFields) 120 val affinity = primaryKey.fields.firstOrNull()?.affinity ?: SQLTypeAffinity.TEXT 121 context.checker.check( 122 !primaryKey.autoGenerateId || affinity == SQLTypeAffinity.INTEGER, 123 primaryKey.fields.firstOrNull()?.element ?: element, 124 ProcessorErrors.AUTO_INCREMENTED_PRIMARY_KEY_IS_NOT_INT 125 ) 126 127 val entityForeignKeys = validateAndCreateForeignKeyReferences(foreignKeyInputs, pojo) 128 checkIndicesForForeignKeys(entityForeignKeys, primaryKey, indices) 129 130 context.checker.check(SqlParser.isValidIdentifier(tableName), element, 131 ProcessorErrors.INVALID_TABLE_NAME) 132 pojo.fields.forEach { 133 context.checker.check(SqlParser.isValidIdentifier(it.columnName), it.element, 134 ProcessorErrors.INVALID_COLUMN_NAME) 135 } 136 137 val entity = Entity(element = element, 138 tableName = tableName, 139 type = pojo.type, 140 fields = pojo.fields, 141 embeddedFields = pojo.embeddedFields, 142 indices = indices, 143 primaryKey = primaryKey, 144 foreignKeys = entityForeignKeys, 145 constructor = pojo.constructor) 146 147 return entity 148 } 149 150 private fun checkIndicesForForeignKeys(entityForeignKeys: List<ForeignKey>, 151 primaryKey: PrimaryKey, 152 indices: List<Index>) { 153 fun covers(columnNames: List<String>, fields: List<Field>): Boolean = 154 fields.size >= columnNames.size && columnNames.withIndex().all { 155 fields[it.index].columnName == it.value 156 } 157 158 entityForeignKeys.forEach { fKey -> 159 val columnNames = fKey.childFields.map { it.columnName } 160 val exists = covers(columnNames, primaryKey.fields) || indices.any { index -> 161 covers(columnNames, index.fields) 162 } 163 if (!exists) { 164 if (columnNames.size == 1) { 165 context.logger.w(Warning.MISSING_INDEX_ON_FOREIGN_KEY_CHILD, element, 166 ProcessorErrors.foreignKeyMissingIndexInChildColumn(columnNames[0])) 167 } else { 168 context.logger.w(Warning.MISSING_INDEX_ON_FOREIGN_KEY_CHILD, element, 169 ProcessorErrors.foreignKeyMissingIndexInChildColumns(columnNames)) 170 } 171 } 172 } 173 } 174 175 /** 176 * Does a validation on foreign keys except the parent table's columns. 177 */ 178 private fun validateAndCreateForeignKeyReferences(foreignKeyInputs: List<ForeignKeyInput>, 179 pojo: Pojo): List<ForeignKey> { 180 return foreignKeyInputs.map { 181 if (it.onUpdate == null) { 182 context.logger.e(element, ProcessorErrors.INVALID_FOREIGN_KEY_ACTION) 183 return@map null 184 } 185 if (it.onDelete == null) { 186 context.logger.e(element, ProcessorErrors.INVALID_FOREIGN_KEY_ACTION) 187 return@map null 188 } 189 if (it.childColumns.isEmpty()) { 190 context.logger.e(element, ProcessorErrors.FOREIGN_KEY_EMPTY_CHILD_COLUMN_LIST) 191 return@map null 192 } 193 if (it.parentColumns.isEmpty()) { 194 context.logger.e(element, ProcessorErrors.FOREIGN_KEY_EMPTY_PARENT_COLUMN_LIST) 195 return@map null 196 } 197 if (it.childColumns.size != it.parentColumns.size) { 198 context.logger.e(element, ProcessorErrors.foreignKeyColumnNumberMismatch( 199 it.childColumns, it.parentColumns 200 )) 201 return@map null 202 } 203 val parentElement = try { 204 MoreTypes.asElement(it.parent) as TypeElement 205 } catch (noClass: IllegalArgumentException) { 206 context.logger.e(element, ProcessorErrors.FOREIGN_KEY_CANNOT_FIND_PARENT) 207 return@map null 208 } 209 val parentAnnotation = MoreElements.getAnnotationMirror(parentElement, 210 androidx.room.Entity::class.java).orNull() 211 if (parentAnnotation == null) { 212 context.logger.e(element, 213 ProcessorErrors.foreignKeyNotAnEntity(parentElement.toString())) 214 return@map null 215 } 216 val tableName = extractTableName(parentElement, parentAnnotation) 217 val fields = it.childColumns.mapNotNull { columnName -> 218 val field = pojo.fields.find { it.columnName == columnName } 219 if (field == null) { 220 context.logger.e(pojo.element, 221 ProcessorErrors.foreignKeyChildColumnDoesNotExist(columnName, 222 pojo.fields.map { it.columnName })) 223 } 224 field 225 } 226 if (fields.size != it.childColumns.size) { 227 return@map null 228 } 229 ForeignKey( 230 parentTable = tableName, 231 childFields = fields, 232 parentColumns = it.parentColumns, 233 onDelete = it.onDelete, 234 onUpdate = it.onUpdate, 235 deferred = it.deferred 236 ) 237 }.filterNotNull() 238 } 239 240 private fun findAndValidatePrimaryKey( 241 fields: List<Field>, embeddedFields: List<EmbeddedField>): PrimaryKey { 242 val candidates = collectPrimaryKeysFromEntityAnnotations(element, fields) + 243 collectPrimaryKeysFromPrimaryKeyAnnotations(fields) + 244 collectPrimaryKeysFromEmbeddedFields(embeddedFields) 245 246 context.checker.check(candidates.isNotEmpty(), element, ProcessorErrors.MISSING_PRIMARY_KEY) 247 248 // 1. If a key is not autogenerated, but is Primary key or is part of Primary key we 249 // force the @NonNull annotation. If the key is a single Primary Key, Integer or Long, we 250 // don't force the @NonNull annotation since SQLite will automatically generate IDs. 251 // 2. If a key is autogenerate, we generate NOT NULL in table spec, but we don't require 252 // @NonNull annotation on the field itself. 253 candidates.filter { candidate -> !candidate.autoGenerateId } 254 .map { candidate -> 255 candidate.fields.map { field -> 256 if (candidate.fields.size > 1 || 257 (candidate.fields.size == 1 258 && field.affinity != SQLTypeAffinity.INTEGER)) { 259 context.checker.check(field.nonNull, field.element, 260 ProcessorErrors.primaryKeyNull(field.getPath())) 261 // Validate parents for nullability 262 var parent = field.parent 263 while (parent != null) { 264 val parentField = parent.field 265 context.checker.check(parentField.nonNull, 266 parentField.element, 267 ProcessorErrors.primaryKeyNull(parentField.getPath())) 268 parent = parentField.parent 269 } 270 } 271 } 272 } 273 274 if (candidates.size == 1) { 275 // easy :) 276 return candidates.first() 277 } 278 279 return choosePrimaryKey(candidates, element) 280 } 281 282 /** 283 * Check fields for @PrimaryKey. 284 */ 285 private fun collectPrimaryKeysFromPrimaryKeyAnnotations(fields: List<Field>): List<PrimaryKey> { 286 return fields.mapNotNull { field -> 287 MoreElements.getAnnotationMirror(field.element, 288 androidx.room.PrimaryKey::class.java).orNull()?.let { 289 if (field.parent != null) { 290 // the field in the entity that contains this error. 291 val grandParentField = field.parent.mRootParent.field.element 292 // bound for entity. 293 context.fork(grandParentField).logger.w( 294 Warning.PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED, 295 grandParentField, 296 ProcessorErrors.embeddedPrimaryKeyIsDropped( 297 element.qualifiedName.toString(), field.name)) 298 null 299 } else { 300 PrimaryKey(declaredIn = field.element.enclosingElement, 301 fields = listOf(field), 302 autoGenerateId = AnnotationMirrors 303 .getAnnotationValue(it, "autoGenerate") 304 .getAsBoolean(false)) 305 } 306 } 307 } 308 } 309 310 /** 311 * Check classes for @Entity(primaryKeys = ?). 312 */ 313 private fun collectPrimaryKeysFromEntityAnnotations( 314 typeElement: TypeElement, availableFields: List<Field>): List<PrimaryKey> { 315 val myPkeys = MoreElements.getAnnotationMirror(typeElement, 316 androidx.room.Entity::class.java).orNull()?.let { 317 val primaryKeyColumns = AnnotationMirrors.getAnnotationValue(it, "primaryKeys") 318 .getAsStringList() 319 if (primaryKeyColumns.isEmpty()) { 320 emptyList() 321 } else { 322 val fields = primaryKeyColumns.mapNotNull { pKeyColumnName -> 323 val field = availableFields.firstOrNull { it.columnName == pKeyColumnName } 324 context.checker.check(field != null, typeElement, 325 ProcessorErrors.primaryKeyColumnDoesNotExist(pKeyColumnName, 326 availableFields.map { it.columnName })) 327 field 328 } 329 listOf(PrimaryKey(declaredIn = typeElement, 330 fields = fields, 331 autoGenerateId = false)) 332 } 333 } ?: emptyList() 334 // checks supers. 335 val mySuper = typeElement.superclass 336 val superPKeys = if (mySuper != null && mySuper.kind != TypeKind.NONE) { 337 // my super cannot see my fields so remove them. 338 val remainingFields = availableFields.filterNot { 339 it.element.enclosingElement == typeElement 340 } 341 collectPrimaryKeysFromEntityAnnotations( 342 MoreTypes.asTypeElement(mySuper), remainingFields) 343 } else { 344 emptyList() 345 } 346 return superPKeys + myPkeys 347 } 348 349 private fun collectPrimaryKeysFromEmbeddedFields( 350 embeddedFields: List<EmbeddedField>): List<PrimaryKey> { 351 return embeddedFields.mapNotNull { embeddedField -> 352 MoreElements.getAnnotationMirror(embeddedField.field.element, 353 androidx.room.PrimaryKey::class.java).orNull()?.let { 354 val autoGenerate = AnnotationMirrors 355 .getAnnotationValue(it, "autoGenerate").getAsBoolean(false) 356 context.checker.check(!autoGenerate || embeddedField.pojo.fields.size == 1, 357 embeddedField.field.element, 358 ProcessorErrors.AUTO_INCREMENT_EMBEDDED_HAS_MULTIPLE_FIELDS) 359 PrimaryKey(declaredIn = embeddedField.field.element.enclosingElement, 360 fields = embeddedField.pojo.fields, 361 autoGenerateId = autoGenerate) 362 } 363 } 364 } 365 366 // start from my element and check if anywhere in the list we can find the only well defined 367 // pkey, if so, use it. 368 private fun choosePrimaryKey( 369 candidates: List<PrimaryKey>, typeElement: TypeElement): PrimaryKey { 370 // If 1 of these primary keys is declared in this class, then it is the winner. Just print 371 // a note for the others. 372 // If 0 is declared, check the parent. 373 // If more than 1 primary key is declared in this class, it is an error. 374 val myPKeys = candidates.filter { candidate -> 375 candidate.declaredIn == typeElement 376 } 377 return if (myPKeys.size == 1) { 378 // just note, this is not worth an error or warning 379 (candidates - myPKeys).forEach { 380 context.logger.d(element, 381 "${it.toHumanReadableString()} is" + 382 " overridden by ${myPKeys.first().toHumanReadableString()}") 383 } 384 myPKeys.first() 385 } else if (myPKeys.isEmpty()) { 386 // i have not declared anything, delegate to super 387 val mySuper = typeElement.superclass 388 if (mySuper != null && mySuper.kind != TypeKind.NONE) { 389 return choosePrimaryKey(candidates, MoreTypes.asTypeElement(mySuper)) 390 } 391 PrimaryKey.MISSING 392 } else { 393 context.logger.e(element, ProcessorErrors.multiplePrimaryKeyAnnotations( 394 myPKeys.map(PrimaryKey::toHumanReadableString))) 395 PrimaryKey.MISSING 396 } 397 } 398 399 private fun validateAndCreateIndices( 400 inputs: List<IndexInput>, pojo: Pojo): List<Index> { 401 // check for columns 402 val indices = inputs.mapNotNull { input -> 403 context.checker.check(input.columnNames.isNotEmpty(), element, 404 INDEX_COLUMNS_CANNOT_BE_EMPTY) 405 val fields = input.columnNames.mapNotNull { columnName -> 406 val field = pojo.fields.firstOrNull { 407 it.columnName == columnName 408 } 409 context.checker.check(field != null, element, 410 ProcessorErrors.indexColumnDoesNotExist( 411 columnName, pojo.fields.map { it.columnName } 412 )) 413 field 414 } 415 if (fields.isEmpty()) { 416 null 417 } else { 418 Index(name = input.name, unique = input.unique, fields = fields) 419 } 420 } 421 422 // check for duplicate indices 423 indices 424 .groupBy { it.name } 425 .filter { it.value.size > 1 } 426 .forEach { 427 context.logger.e(element, ProcessorErrors.duplicateIndexInEntity(it.key)) 428 } 429 430 // see if any embedded field is an entity with indices, if so, report a warning 431 pojo.embeddedFields.forEach { embedded -> 432 val embeddedElement = embedded.pojo.element 433 val subEntityAnnotation = MoreElements.getAnnotationMirror(embeddedElement, 434 androidx.room.Entity::class.java).orNull() 435 subEntityAnnotation?.let { 436 val subIndices = extractIndices(subEntityAnnotation, "") 437 if (subIndices.isNotEmpty()) { 438 context.logger.w(Warning.INDEX_FROM_EMBEDDED_ENTITY_IS_DROPPED, 439 embedded.field.element, ProcessorErrors.droppedEmbeddedIndex( 440 entityName = embedded.pojo.typeName.toString(), 441 fieldPath = embedded.field.getPath(), 442 grandParent = element.qualifiedName.toString())) 443 } 444 } 445 } 446 return indices 447 } 448 449 // check if parent is an Entity, if so, report its annotation indices 450 private fun loadSuperIndices( 451 typeMirror: TypeMirror?, tableName: String, inherit: Boolean): List<IndexInput> { 452 if (typeMirror == null || typeMirror.kind == TypeKind.NONE) { 453 return emptyList() 454 } 455 val parentElement = MoreTypes.asTypeElement(typeMirror) 456 val myIndices = MoreElements.getAnnotationMirror(parentElement, 457 androidx.room.Entity::class.java).orNull()?.let { annotation -> 458 val indices = extractIndices(annotation, tableName = "super") 459 if (indices.isEmpty()) { 460 emptyList() 461 } else if (inherit) { 462 // rename them 463 indices.map { 464 IndexInput( 465 name = createIndexName(it.columnNames, tableName), 466 unique = it.unique, 467 columnNames = it.columnNames) 468 } 469 } else { 470 context.logger.w(Warning.INDEX_FROM_PARENT_IS_DROPPED, 471 parentElement, 472 ProcessorErrors.droppedSuperClassIndex( 473 childEntity = element.qualifiedName.toString(), 474 superEntity = parentElement.qualifiedName.toString())) 475 emptyList() 476 } 477 } ?: emptyList() 478 return myIndices + loadSuperIndices(parentElement.superclass, tableName, inherit) 479 } 480 481 companion object { 482 fun extractTableName(element: TypeElement, annotation: AnnotationMirror): String { 483 val annotationValue = AnnotationMirrors 484 .getAnnotationValue(annotation, "tableName").value.toString() 485 return if (annotationValue == "") { 486 element.simpleName.toString() 487 } else { 488 annotationValue 489 } 490 } 491 492 private fun extractIndices( 493 annotation: AnnotationMirror, tableName: String): List<IndexInput> { 494 val arrayOfIndexAnnotations = AnnotationMirrors.getAnnotationValue(annotation, 495 "indices") 496 return INDEX_LIST_VISITOR.visit(arrayOfIndexAnnotations, tableName) 497 } 498 499 private val INDEX_LIST_VISITOR = object 500 : SimpleAnnotationValueVisitor6<List<IndexInput>, String>() { 501 override fun visitArray( 502 values: MutableList<out AnnotationValue>?, 503 tableName: String 504 ): List<IndexInput> { 505 return values?.mapNotNull { 506 INDEX_VISITOR.visit(it, tableName) 507 } ?: emptyList() 508 } 509 } 510 511 private val INDEX_VISITOR = object : SimpleAnnotationValueVisitor6<IndexInput?, String>() { 512 override fun visitAnnotation(a: AnnotationMirror?, tableName: String): IndexInput? { 513 val fieldInput = getAnnotationValue(a, "value").getAsStringList() 514 val unique = getAnnotationValue(a, "unique").getAsBoolean(false) 515 val nameValue = getAnnotationValue(a, "name") 516 .getAsString("") 517 val name = if (nameValue == null || nameValue == "") { 518 createIndexName(fieldInput, tableName) 519 } else { 520 nameValue 521 } 522 return IndexInput(name, unique, fieldInput) 523 } 524 } 525 526 private fun createIndexName(columnNames: List<String>, tableName: String): String { 527 return Index.DEFAULT_PREFIX + tableName + "_" + columnNames.joinToString("_") 528 } 529 530 private fun extractForeignKeys(annotation: AnnotationMirror): List<ForeignKeyInput> { 531 val arrayOfForeignKeyAnnotations = getAnnotationValue(annotation, "foreignKeys") 532 return FOREIGN_KEY_LIST_VISITOR.visit(arrayOfForeignKeyAnnotations) 533 } 534 535 private val FOREIGN_KEY_LIST_VISITOR = object 536 : SimpleAnnotationValueVisitor6<List<ForeignKeyInput>, Void?>() { 537 override fun visitArray( 538 values: MutableList<out AnnotationValue>?, 539 void: Void? 540 ): List<ForeignKeyInput> { 541 return values?.mapNotNull { 542 FOREIGN_KEY_VISITOR.visit(it) 543 } ?: emptyList() 544 } 545 } 546 547 private val FOREIGN_KEY_VISITOR = object : SimpleAnnotationValueVisitor6<ForeignKeyInput?, 548 Void?>() { 549 override fun visitAnnotation(a: AnnotationMirror?, void: Void?): ForeignKeyInput? { 550 val entityClass = try { 551 getAnnotationValue(a, "entity").toType() 552 } catch (notPresent: TypeNotPresentException) { 553 return null 554 } 555 val parentColumns = getAnnotationValue(a, "parentColumns").getAsStringList() 556 val childColumns = getAnnotationValue(a, "childColumns").getAsStringList() 557 val onDeleteInput = getAnnotationValue(a, "onDelete").getAsInt() 558 val onUpdateInput = getAnnotationValue(a, "onUpdate").getAsInt() 559 val deferred = getAnnotationValue(a, "deferred").getAsBoolean(true) 560 val onDelete = ForeignKeyAction.fromAnnotationValue(onDeleteInput) 561 val onUpdate = ForeignKeyAction.fromAnnotationValue(onUpdateInput) 562 return ForeignKeyInput( 563 parent = entityClass, 564 parentColumns = parentColumns, 565 childColumns = childColumns, 566 onDelete = onDelete, 567 onUpdate = onUpdate, 568 deferred = deferred) 569 } 570 } 571 } 572 573 /** 574 * processed Index annotation output 575 */ 576 data class IndexInput(val name: String, val unique: Boolean, val columnNames: List<String>) 577 578 /** 579 * ForeignKey, before it is processed in the context of a database. 580 */ 581 data class ForeignKeyInput( 582 val parent: TypeMirror, 583 val parentColumns: List<String>, 584 val childColumns: List<String>, 585 val onDelete: ForeignKeyAction?, 586 val onUpdate: ForeignKeyAction?, 587 val deferred: Boolean) 588} 589