Home | History | Annotate | Download | only in processor
      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 
     17 package androidx.room.processor
     18 
     19 import androidx.room.ext.getAsBoolean
     20 import androidx.room.ext.getAsInt
     21 import androidx.room.ext.getAsString
     22 import androidx.room.ext.getAsStringList
     23 import androidx.room.ext.toType
     24 import androidx.room.parser.SQLTypeAffinity
     25 import androidx.room.parser.SqlParser
     26 import androidx.room.processor.ProcessorErrors.INDEX_COLUMNS_CANNOT_BE_EMPTY
     27 import androidx.room.processor.ProcessorErrors.RELATION_IN_ENTITY
     28 import androidx.room.processor.cache.Cache
     29 import androidx.room.vo.EmbeddedField
     30 import androidx.room.vo.Entity
     31 import androidx.room.vo.Field
     32 import androidx.room.vo.ForeignKey
     33 import androidx.room.vo.ForeignKeyAction
     34 import androidx.room.vo.Index
     35 import androidx.room.vo.Pojo
     36 import androidx.room.vo.PrimaryKey
     37 import androidx.room.vo.Warning
     38 import com.google.auto.common.AnnotationMirrors
     39 import com.google.auto.common.AnnotationMirrors.getAnnotationValue
     40 import com.google.auto.common.MoreElements
     41 import com.google.auto.common.MoreTypes
     42 import javax.lang.model.element.AnnotationMirror
     43 import javax.lang.model.element.AnnotationValue
     44 import javax.lang.model.element.Name
     45 import javax.lang.model.element.TypeElement
     46 import javax.lang.model.type.TypeKind
     47 import javax.lang.model.type.TypeMirror
     48 import javax.lang.model.util.SimpleAnnotationValueVisitor6
     49 
     50 class 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