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.XCodeBlock.Builder.Companion.applyTo 22 import androidx.room.compiler.codegen.XTypeName 23 import androidx.room.compiler.processing.XNullability 24 import androidx.room.ext.capitalize 25 import androidx.room.ext.defaultValue 26 import androidx.room.solver.CodeGenScope 27 import androidx.room.vo.CallType 28 import androidx.room.vo.Constructor 29 import androidx.room.vo.DataClass 30 import androidx.room.vo.EmbeddedProperty 31 import androidx.room.vo.Property 32 import androidx.room.vo.PropertyWithIndex 33 import androidx.room.vo.RelationCollector 34 import java.util.Locale 35 36 /** Handles writing a property into statement or reading it from statement. */ 37 class PropertyReadWriteWriter(propertyWithIndex: PropertyWithIndex) { 38 val property = propertyWithIndex.property 39 val indexVar = propertyWithIndex.indexVar 40 val alwaysExists = propertyWithIndex.alwaysExists 41 42 companion object { 43 /* 44 * Get all parents including the ones which have grand children in this list but does not 45 * have any direct children in the list. 46 */ 47 fun getAllParents(properties: List<Property>): Set<EmbeddedProperty> { 48 val allParents = mutableSetOf<EmbeddedProperty>() 49 fun addAllParents(property: Property) { 50 var parent = property.parent 51 while (parent != null) { 52 if (allParents.add(parent)) { 53 parent = parent.parent 54 } else { 55 break 56 } 57 } 58 } 59 properties.forEach(::addAllParents) 60 return allParents 61 } 62 63 /** 64 * Convert the properties with indices into a Node tree so that we can recursively process 65 * them. This work is done here instead of parsing because the result may include arbitrary 66 * properties. 67 */ 68 private fun createNodeTree( 69 rootVar: String, 70 propertiesWithIndices: List<PropertyWithIndex>, 71 scope: CodeGenScope 72 ): Node { 73 val allParents = getAllParents(propertiesWithIndices.map { it.property }) 74 val rootNode = Node(rootVar, null) 75 rootNode.directProperties = propertiesWithIndices.filter { it.property.parent == null } 76 val parentNodes = 77 allParents.associateWith { 78 Node( 79 varName = scope.getTmpVar("_tmp${it.property.name.capitalize(Locale.US)}"), 80 propertyParent = it 81 ) 82 } 83 parentNodes.values.forEach { node -> 84 val propertyParent = node.propertyParent!! 85 val grandParent = propertyParent.parent 86 val grandParentNode = grandParent?.let { parentNodes[it] } ?: rootNode 87 node.directProperties = 88 propertiesWithIndices.filter { it.property.parent == propertyParent } 89 node.parentNode = grandParentNode 90 grandParentNode.subNodes.add(node) 91 } 92 return rootNode 93 } 94 95 fun bindToStatement( 96 ownerVar: String, 97 stmtParamVar: String, 98 propertiesWithIndices: List<PropertyWithIndex>, 99 scope: CodeGenScope 100 ) { 101 fun visitNode(node: Node) { 102 fun bindWithDescendants() { 103 node.directProperties.forEach { 104 PropertyReadWriteWriter(it) 105 .bindToStatement( 106 ownerVar = node.varName, 107 stmtParamVar = stmtParamVar, 108 scope = scope 109 ) 110 } 111 node.subNodes.forEach(::visitNode) 112 } 113 114 val propertyParent = node.propertyParent 115 if (propertyParent != null) { 116 propertyParent.getter.writeGet( 117 ownerVar = node.parentNode!!.varName, 118 outVar = node.varName, 119 builder = scope.builder 120 ) 121 scope.builder.apply { 122 if (propertyParent.nonNull) { 123 bindWithDescendants() 124 } else { 125 beginControlFlow("if (%L != null)", node.varName).apply { 126 bindWithDescendants() 127 } 128 nextControlFlow("else").apply { 129 node.allProperties().forEach { 130 addStatement("%L.bindNull(%L)", stmtParamVar, it.indexVar) 131 } 132 } 133 endControlFlow() 134 } 135 } 136 } else { 137 bindWithDescendants() 138 } 139 } 140 visitNode(createNodeTree(ownerVar, propertiesWithIndices, scope)) 141 } 142 143 /** 144 * Just constructs the given item, does NOT DECLARE. Declaration happens outside the reading 145 * statement since we may never read if the statement does not have necessary columns. 146 */ 147 private fun construct( 148 outVar: String, 149 constructor: Constructor?, 150 typeName: XTypeName, 151 localVariableNames: Map<String, PropertyWithIndex>, 152 localEmbeddeds: List<Node>, 153 localRelations: Map<String, Property>, 154 scope: CodeGenScope 155 ) { 156 if (constructor == null) { 157 // Instantiate with default constructor, best hope for code generation 158 scope.builder.apply { 159 addStatement("%L = %L", outVar, XCodeBlock.ofNewInstance(typeName)) 160 } 161 return 162 } 163 val variableNames = 164 constructor.params.map { param -> 165 when (param) { 166 is Constructor.Param.PropertyParam -> 167 localVariableNames.entries 168 .firstOrNull { it.value.property === param.property } 169 ?.key 170 is Constructor.Param.EmbeddedParam -> 171 localEmbeddeds 172 .firstOrNull { it.propertyParent == param.embedded } 173 ?.varName 174 is Constructor.Param.RelationParam -> 175 localRelations.entries 176 .firstOrNull { it.value === param.relation.property } 177 ?.key 178 } 179 } 180 val args = variableNames.joinToString(",") { it ?: "null" } 181 constructor.writeConstructor(outVar, args, scope.builder) 182 } 183 184 /** Reads the row into the given variable. It does not declare it but constructs it. */ 185 fun readFromStatement( 186 outVar: String, 187 outDataClass: DataClass, 188 stmtVar: String, 189 propertiesWithIndices: List<PropertyWithIndex>, 190 scope: CodeGenScope, 191 relationCollectors: List<RelationCollector> 192 ) { 193 fun visitNode(node: Node) { 194 val propertyParent = node.propertyParent 195 fun readNode() { 196 // read constructor parameters into local properties 197 val constructorProperties = 198 node.directProperties 199 .filter { it.property.setter.callType == CallType.CONSTRUCTOR } 200 .associateBy { fwi -> 201 PropertyReadWriteWriter(fwi) 202 .readIntoTmpVar( 203 stmtVar, 204 fwi.property.setter.type.asTypeName(), 205 scope 206 ) 207 } 208 // read decomposed properties (e.g. embedded) 209 node.subNodes.forEach(::visitNode) 210 // read relationship properties 211 val relationProperties = 212 relationCollectors 213 .filter { (relation) -> relation.property.parent === propertyParent } 214 .associate { 215 it.writeReadCollectionIntoTmpVar( 216 stmtVarName = stmtVar, 217 propertiesWithIndices = propertiesWithIndices, 218 scope = scope 219 ) 220 } 221 222 // construct the object 223 if (propertyParent != null) { 224 construct( 225 outVar = node.varName, 226 constructor = propertyParent.dataClass.constructor, 227 typeName = propertyParent.property.typeName, 228 localEmbeddeds = node.subNodes, 229 localRelations = relationProperties, 230 localVariableNames = constructorProperties, 231 scope = scope 232 ) 233 } else { 234 construct( 235 outVar = node.varName, 236 constructor = outDataClass.constructor, 237 typeName = outDataClass.typeName, 238 localEmbeddeds = node.subNodes, 239 localRelations = relationProperties, 240 localVariableNames = constructorProperties, 241 scope = scope 242 ) 243 } 244 // ready any property that was not part of the constructor 245 node.directProperties 246 .filterNot { it.property.setter.callType == CallType.CONSTRUCTOR } 247 .forEach { fwi -> 248 PropertyReadWriteWriter(fwi) 249 .readFromStatement( 250 ownerVar = node.varName, 251 stmtVar = stmtVar, 252 scope = scope 253 ) 254 } 255 // assign sub nodes to properties if they were not part of the constructor. 256 node.subNodes 257 .mapNotNull { 258 val setter = it.propertyParent?.setter 259 if (setter != null && setter.callType != CallType.CONSTRUCTOR) { 260 Pair(it.varName, setter) 261 } else { 262 null 263 } 264 } 265 .forEach { (varName, setter) -> 266 setter.writeSet( 267 ownerVar = node.varName, 268 inVar = varName, 269 builder = scope.builder 270 ) 271 } 272 // assign relation properties that were not part of the constructor 273 relationProperties 274 .filterNot { (_, property) -> 275 property.setter.callType == CallType.CONSTRUCTOR 276 } 277 .forEach { (varName, property) -> 278 property.setter.writeSet( 279 ownerVar = node.varName, 280 inVar = varName, 281 builder = scope.builder 282 ) 283 } 284 } 285 if (propertyParent == null) { 286 // root element 287 // always declared by the caller so we don't declare this 288 readNode() 289 } else { 290 // always declare, we'll set below 291 scope.builder.addLocalVariable(node.varName, propertyParent.property.typeName) 292 if (propertyParent.nonNull) { 293 readNode() 294 } else { 295 val myDescendants = node.allProperties() 296 val allNullCheck = 297 myDescendants.joinToString(" && ") { 298 if (it.alwaysExists) { 299 "$stmtVar.isNull(${it.indexVar})" 300 } else { 301 "(${it.indexVar} == -1 || $stmtVar.isNull(${it.indexVar}))" 302 } 303 } 304 scope.builder.apply { 305 beginControlFlow("if (!(%L))", allNullCheck).apply { readNode() } 306 nextControlFlow("else").apply { 307 addStatement("%L = null", node.varName) 308 } 309 endControlFlow() 310 } 311 } 312 } 313 } 314 visitNode(createNodeTree(outVar, propertiesWithIndices, scope)) 315 } 316 } 317 318 /** 319 * @param ownerVar The entity / pojo variable that owns this property. It must own this 320 * property! (not the container pojo) 321 * @param stmtParamVar The statement variable 322 * @param scope The code generation scope 323 */ 324 private fun bindToStatement(ownerVar: String, stmtParamVar: String, scope: CodeGenScope) { 325 val binder = property.statementBinder ?: return 326 property.getter.writeGetToStatement(ownerVar, stmtParamVar, indexVar, binder, scope) 327 } 328 329 /** 330 * @param ownerVar The entity / pojo variable that owns this property. It must own this property 331 * (not the container pojo) 332 * @param stmtVar The statement variable 333 * @param scope The code generation scope 334 */ 335 private fun readFromStatement(ownerVar: String, stmtVar: String, scope: CodeGenScope) { 336 fun doRead() { 337 val reader = property.statementValueReader ?: return 338 property.setter.writeSetFromStatement(ownerVar, stmtVar, indexVar, reader, scope) 339 } 340 if (alwaysExists) { 341 doRead() 342 } else { 343 scope.builder.apply { 344 beginControlFlow("if (%L != -1)", indexVar).apply { doRead() } 345 endControlFlow() 346 } 347 } 348 } 349 350 /** Reads the value into a temporary local variable. */ 351 fun readIntoTmpVar(stmtVar: String, typeName: XTypeName, scope: CodeGenScope): String { 352 val tmpProperty = scope.getTmpVar("_tmp${property.name.capitalize(Locale.US)}") 353 scope.builder.apply { 354 addLocalVariable(tmpProperty, typeName) 355 if (alwaysExists) { 356 property.statementValueReader?.readFromStatement( 357 tmpProperty, 358 stmtVar, 359 indexVar, 360 scope 361 ) 362 } else { 363 beginControlFlow("if (%L == -1)", indexVar).applyTo { language -> 364 val defaultValue = typeName.defaultValue() 365 if ( 366 language == CodeLanguage.KOTLIN && 367 typeName.nullability == XNullability.NONNULL && 368 defaultValue == "null" 369 ) { 370 addStatement( 371 "error(%S)", 372 "Missing value for a NON-NULL column '${property.columnName}', " + 373 "found NULL value instead." 374 ) 375 } else { 376 addStatement("%L = %L", tmpProperty, defaultValue) 377 } 378 } 379 nextControlFlow("else").apply { 380 property.statementValueReader?.readFromStatement( 381 tmpProperty, 382 stmtVar, 383 indexVar, 384 scope 385 ) 386 } 387 endControlFlow() 388 } 389 } 390 return tmpProperty 391 } 392 393 /** On demand node which is created based on the properties that were passed into this class. */ 394 private class Node( 395 // root for me 396 val varName: String, 397 // set if I'm a PropertyParent 398 val propertyParent: EmbeddedProperty? 399 ) { 400 // whom do i belong 401 var parentNode: Node? = null 402 // these properties are my direct properties 403 lateinit var directProperties: List<PropertyWithIndex> 404 // these nodes are under me 405 val subNodes = mutableListOf<Node>() 406 407 fun allProperties(): List<PropertyWithIndex> { 408 return directProperties + subNodes.flatMap { it.allProperties() } 409 } 410 } 411 } 412