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