1 /*
<lambda>null2  * Copyright (C) 2017 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.writer
18 
19 import androidx.room.compiler.codegen.CodeLanguage
20 import androidx.room.compiler.codegen.XCodeBlock
21 import androidx.room.compiler.codegen.buildCodeBlock
22 import androidx.room.ext.CommonTypeNames
23 import androidx.room.ext.KotlinCollectionMemberNames
24 import androidx.room.ext.RoomMemberNames
25 import androidx.room.ext.RoomTypeNames
26 import androidx.room.ext.capitalize
27 import androidx.room.ext.stripNonJava
28 import androidx.room.parser.SQLTypeAffinity
29 import androidx.room.vo.Entity
30 import androidx.room.vo.columnNames
31 import java.util.Locale
32 
33 class TableInfoValidationWriter(val entity: Entity) : ValidationWriter() {
34 
35     companion object {
36         const val CREATED_FROM_ENTITY = "CREATED_FROM_ENTITY"
37     }
38 
39     override fun write(connectionParamName: String, scope: CountingCodeGenScope) {
40         val suffix = entity.tableName.stripNonJava().capitalize(Locale.US)
41         val expectedInfoVar = scope.getTmpVar("_info$suffix")
42         scope.builder.apply {
43             val columnListVar = scope.getTmpVar("_columns$suffix")
44             val columnListType =
45                 CommonTypeNames.MUTABLE_MAP.parametrizedBy(
46                     CommonTypeNames.STRING,
47                     RoomTypeNames.TABLE_INFO_COLUMN
48                 )
49             addLocalVariable(
50                 name = columnListVar,
51                 typeName = columnListType,
52                 assignExpr =
53                     buildCodeBlock { language ->
54                         when (language) {
55                             CodeLanguage.JAVA ->
56                                 add(
57                                     "new %T(%L)",
58                                     CommonTypeNames.HASH_MAP.parametrizedBy(
59                                         CommonTypeNames.STRING,
60                                         RoomTypeNames.TABLE_INFO_COLUMN
61                                     ),
62                                     entity.properties.size
63                                 )
64                             CodeLanguage.KOTLIN ->
65                                 add("%M()", KotlinCollectionMemberNames.MUTABLE_MAP_OF)
66                         }
67                     }
68             )
69             entity.properties.forEach { field ->
70                 addStatement(
71                     "%L.put(%S, %L)",
72                     columnListVar,
73                     field.columnName,
74                     XCodeBlock.ofNewInstance(
75                         RoomTypeNames.TABLE_INFO_COLUMN,
76                         "%S, %S, %L, %L, %S, %T.%L",
77                         field.columnName, // name
78                         field.affinity?.name ?: SQLTypeAffinity.TEXT.name, // type
79                         field.nonNull, // nonNull
80                         entity.primaryKey.properties.indexOf(field) + 1, // pkeyPos
81                         field.defaultValue, // defaultValue
82                         RoomTypeNames.TABLE_INFO,
83                         CREATED_FROM_ENTITY // createdFrom
84                     )
85                 )
86             }
87 
88             val foreignKeySetVar = scope.getTmpVar("_foreignKeys$suffix")
89             val foreignKeySetType =
90                 CommonTypeNames.MUTABLE_SET.parametrizedBy(RoomTypeNames.TABLE_INFO_FOREIGN_KEY)
91             addLocalVariable(
92                 name = foreignKeySetVar,
93                 typeName = foreignKeySetType,
94                 assignExpr =
95                     buildCodeBlock { language ->
96                         when (language) {
97                             CodeLanguage.JAVA ->
98                                 add(
99                                     "new %T(%L)",
100                                     CommonTypeNames.HASH_SET.parametrizedBy(
101                                         RoomTypeNames.TABLE_INFO_FOREIGN_KEY
102                                     ),
103                                     entity.foreignKeys.size
104                                 )
105                             CodeLanguage.KOTLIN ->
106                                 add("%M()", KotlinCollectionMemberNames.MUTABLE_SET_OF)
107                         }
108                     }
109             )
110             entity.foreignKeys.forEach {
111                 addStatement(
112                     "%L.add(%L)",
113                     foreignKeySetVar,
114                     XCodeBlock.ofNewInstance(
115                         RoomTypeNames.TABLE_INFO_FOREIGN_KEY,
116                         "%S, %S, %S, %L, %L",
117                         it.parentTable, // parent table
118                         it.onDelete.sqlName, // on delete
119                         it.onUpdate.sqlName, // on update
120                         listOfStrings(it.childProperties.map { it.columnName }), // parent names
121                         listOfStrings(it.parentColumns) // parent column names
122                     )
123                 )
124             }
125 
126             val indicesSetVar = scope.getTmpVar("_indices$suffix")
127             val indicesType =
128                 CommonTypeNames.MUTABLE_SET.parametrizedBy(RoomTypeNames.TABLE_INFO_INDEX)
129             addLocalVariable(
130                 name = indicesSetVar,
131                 typeName = indicesType,
132                 assignExpr =
133                     buildCodeBlock { language ->
134                         when (language) {
135                             CodeLanguage.JAVA ->
136                                 add(
137                                     "new %T(%L)",
138                                     CommonTypeNames.HASH_SET.parametrizedBy(
139                                         RoomTypeNames.TABLE_INFO_INDEX
140                                     ),
141                                     entity.indices.size
142                                 )
143                             CodeLanguage.KOTLIN ->
144                                 add("%M()", KotlinCollectionMemberNames.MUTABLE_SET_OF)
145                         }
146                     }
147             )
148             entity.indices.forEach { index ->
149                 val orders =
150                     if (index.orders.isEmpty()) {
151                         index.columnNames.map { "ASC" }
152                     } else {
153                         index.orders.map { it.name }
154                     }
155                 addStatement(
156                     "%L.add(%L)",
157                     indicesSetVar,
158                     XCodeBlock.ofNewInstance(
159                         RoomTypeNames.TABLE_INFO_INDEX,
160                         "%S, %L, %L, %L",
161                         index.name, // name
162                         index.unique, // unique
163                         listOfStrings(index.columnNames), // columns
164                         listOfStrings(orders) // orders
165                     )
166                 )
167             }
168 
169             addLocalVariable(
170                 name = expectedInfoVar,
171                 typeName = RoomTypeNames.TABLE_INFO,
172                 assignExpr =
173                     XCodeBlock.ofNewInstance(
174                         RoomTypeNames.TABLE_INFO,
175                         "%S, %L, %L, %L",
176                         entity.tableName,
177                         columnListVar,
178                         foreignKeySetVar,
179                         indicesSetVar
180                     )
181             )
182 
183             val existingVar = scope.getTmpVar("_existing$suffix")
184             addLocalVal(
185                 existingVar,
186                 RoomTypeNames.TABLE_INFO,
187                 "%M(%L, %S)",
188                 RoomMemberNames.TABLE_INFO_READ,
189                 connectionParamName,
190                 entity.tableName
191             )
192 
193             beginControlFlow("if (!%L.equals(%L))", expectedInfoVar, existingVar).apply {
194                 addStatement(
195                     "return %L",
196                     XCodeBlock.ofNewInstance(
197                         RoomTypeNames.ROOM_OPEN_DELEGATE_VALIDATION_RESULT,
198                         "false, %S + %L + %S + %L",
199                         "${entity.tableName}(${entity.element.qualifiedName}).\n Expected:\n",
200                         expectedInfoVar,
201                         "\n Found:\n",
202                         existingVar
203                     )
204                 )
205             }
206             endControlFlow()
207         }
208     }
209 
210     private fun listOfStrings(strings: List<String>) = buildCodeBlock { language ->
211         val placeholders = List(strings.size) { "%S" }.joinToString()
212         val function: Any =
213             when (language) {
214                 CodeLanguage.JAVA -> XCodeBlock.of("%T.asList", CommonTypeNames.ARRAYS)
215                 CodeLanguage.KOTLIN -> "listOf"
216             }
217         add("%L($placeholders)", function, *strings.toTypedArray())
218     }
219 }
220