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