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.vo
18 
19 import androidx.room.ext.isNonNull
20 import androidx.room.ext.typeName
21 import androidx.room.migration.bundle.FieldBundle
22 import androidx.room.parser.Collate
23 import androidx.room.parser.SQLTypeAffinity
24 import androidx.room.solver.types.CursorValueReader
25 import androidx.room.solver.types.StatementValueBinder
26 import com.squareup.javapoet.TypeName
27 import javax.lang.model.element.Element
28 import javax.lang.model.type.TypeMirror
29 // used in cache matching, must stay as a data class or implement equals
30 data class Field(val element: Element, val name: String, val type: TypeMirror,
31                  var affinity: SQLTypeAffinity?,
32                  val collate: Collate? = null,
33                  val columnName: String = name,
34                  /* means that this field does not belong to parent, instead, it belongs to a
35                  * embedded child of the main Pojo*/
36                  val parent: EmbeddedField? = null,
37                  // index might be removed when being merged into an Entity
38                  var indexed: Boolean = false) : HasSchemaIdentity {
39     lateinit var getter: FieldGetter
40     lateinit var setter: FieldSetter
41     // binds the field into a statement
42     var statementBinder: StatementValueBinder? = null
43     // reads this field from a cursor column
44     var cursorValueReader: CursorValueReader? = null
<lambda>null45     val typeName: TypeName by lazy { type.typeName() }
46 
47     /** Whether the table column for this field should be NOT NULL */
48     val nonNull = element.isNonNull() && (parent == null || parent.isNonNullRecursively())
49 
getIdKeynull50     override fun getIdKey(): String {
51         // we don't get the collate information from sqlite so ignoring it here.
52         return "$columnName-${affinity?.name ?: SQLTypeAffinity.TEXT.name}-$nonNull"
53     }
54 
55     /**
56      * Used when reporting errors on duplicate names
57      */
getPathnull58     fun getPath(): String {
59         return if (parent == null) {
60             name
61         } else {
62             "${parent.field.getPath()} > $name"
63         }
64     }
65 
<lambda>null66     private val pathWithDotNotation: String by lazy {
67         if (parent == null) {
68             name
69         } else {
70             "${parent.field.pathWithDotNotation}.$name"
71         }
72     }
73 
74     /**
75      * List of names that include variations.
76      * e.g. if it is mUser, user is added to the list
77      * or if it is isAdmin, admin is added to the list
78      */
<lambda>null79     val nameWithVariations by lazy {
80         val result = arrayListOf(name)
81         if (name.length > 1) {
82             if (name.startsWith('_')) {
83                 result.add(name.substring(1))
84             }
85             if (name.startsWith("m") && name[1].isUpperCase()) {
86                 result.add(name.substring(1).decapitalize())
87             }
88 
89             if (typeName == TypeName.BOOLEAN || typeName == TypeName.BOOLEAN.box()) {
90                 if (name.length > 2 && name.startsWith("is") && name[2].isUpperCase()) {
91                     result.add(name.substring(2).decapitalize())
92                 }
93                 if (name.length > 3 && name.startsWith("has") && name[3].isUpperCase()) {
94                     result.add(name.substring(3).decapitalize())
95                 }
96             }
97         }
98         result
99     }
100 
<lambda>null101     val getterNameWithVariations by lazy {
102         nameWithVariations.map { "get${it.capitalize()}" } +
103                 if (typeName == TypeName.BOOLEAN || typeName == TypeName.BOOLEAN.box()) {
104                     nameWithVariations.flatMap {
105                         listOf("is${it.capitalize()}", "has${it.capitalize()}")
106                     }
107                 } else {
108                     emptyList()
109                 }
110     }
111 
<lambda>null112     val setterNameWithVariations by lazy {
113         nameWithVariations.map { "set${it.capitalize()}" }
114     }
115 
116     /**
117      * definition to be used in create query
118      */
databaseDefinitionnull119     fun databaseDefinition(autoIncrementPKey: Boolean): String {
120         val columnSpec = StringBuilder("")
121         if (autoIncrementPKey) {
122             columnSpec.append(" PRIMARY KEY AUTOINCREMENT")
123         }
124         if (nonNull) {
125             columnSpec.append(" NOT NULL")
126         }
127         if (collate != null) {
128             columnSpec.append(" COLLATE ${collate.name}")
129         }
130         return "`$columnName` ${(affinity ?: SQLTypeAffinity.TEXT).name}$columnSpec"
131     }
132 
toBundlenull133     fun toBundle(): FieldBundle = FieldBundle(pathWithDotNotation, columnName,
134             affinity?.name ?: SQLTypeAffinity.TEXT.name, nonNull
135     )
136 }
137