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