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