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.ext.AndroidTypeNames
20 import androidx.room.ext.L
21 import androidx.room.ext.N
22 import androidx.room.ext.RoomTypeNames
23 import androidx.room.ext.S
24 import androidx.room.ext.T
25 import androidx.room.solver.CodeGenScope
26 import androidx.room.vo.RelationCollector
27 import com.squareup.javapoet.ClassName
28 import com.squareup.javapoet.MethodSpec
29 import com.squareup.javapoet.ParameterSpec
30 import com.squareup.javapoet.ParameterizedTypeName
31 import com.squareup.javapoet.TypeName
32 import stripNonJava
33 import javax.lang.model.element.Modifier
34 
35 /**
36  * Writes the method that fetches the relations of a POJO and assigns them into the given map.
37  */
38 class RelationCollectorMethodWriter(private val collector: RelationCollector)
39     : ClassWriter.SharedMethodSpec(
40         "fetchRelationship${collector.relation.entity.tableName.stripNonJava()}" +
41                 "As${collector.relation.pojoTypeName.toString().stripNonJava()}") {
42     companion object {
43         val KEY_SET_VARIABLE = "__mapKeySet"
44     }
45     override fun getUniqueKey(): String {
46         val relation = collector.relation
47         return "RelationCollectorMethodWriter" +
48                 "-${collector.mapTypeName}" +
49                 "-${relation.entity.typeName}" +
50                 "-${relation.entityField.columnName}" +
51                 "-${relation.pojoTypeName}" +
52                 "-${relation.createLoadAllSql()}"
53     }
54 
55     override fun prepare(methodName: String, writer: ClassWriter, builder: MethodSpec.Builder) {
56         val scope = CodeGenScope(writer)
57         val relation = collector.relation
58 
59         val param = ParameterSpec.builder(collector.mapTypeName, "_map")
60                 .addModifiers(Modifier.FINAL)
61                 .build()
62         val sqlQueryVar = scope.getTmpVar("_sql")
63         val keySetVar = KEY_SET_VARIABLE
64 
65         val cursorVar = "_cursor"
66         val itemKeyIndexVar = "_itemKeyIndex"
67         val stmtVar = scope.getTmpVar("_stmt")
68         scope.builder().apply {
69 
70             val keySetType = ParameterizedTypeName.get(
71                     ClassName.get(Set::class.java), collector.keyTypeName
72             )
73             addStatement("final $T $L = $N.keySet()", keySetType, keySetVar, param)
74             beginControlFlow("if ($L.isEmpty())", keySetVar).apply {
75                 addStatement("return")
76             }
77             endControlFlow()
78             addStatement("// check if the size is too big, if so divide")
79             beginControlFlow("if($N.size() > $T.MAX_BIND_PARAMETER_CNT)",
80                     param, RoomTypeNames.ROOM_DB).apply {
81                 // divide it into chunks
82                 val tmpMapVar = scope.getTmpVar("_tmpInnerMap")
83                 addStatement("$T $L = new $T($L.MAX_BIND_PARAMETER_CNT)",
84                         collector.mapTypeName, tmpMapVar,
85                         collector.mapTypeName, RoomTypeNames.ROOM_DB)
86                 val mapIndexVar = scope.getTmpVar("_mapIndex")
87                 val tmpIndexVar = scope.getTmpVar("_tmpIndex")
88                 val limitVar = scope.getTmpVar("_limit")
89                 addStatement("$T $L = 0", TypeName.INT, mapIndexVar)
90                 addStatement("$T $L = 0", TypeName.INT, tmpIndexVar)
91                 addStatement("final $T $L = $N.size()", TypeName.INT, limitVar, param)
92                 beginControlFlow("while($L < $L)", mapIndexVar, limitVar).apply {
93                     addStatement("$L.put($N.keyAt($L), $N.valueAt($L))",
94                             tmpMapVar, param, mapIndexVar, param, mapIndexVar)
95                     addStatement("$L++", mapIndexVar)
96                     addStatement("$L++", tmpIndexVar)
97                     beginControlFlow("if($L == $T.MAX_BIND_PARAMETER_CNT)",
98                             tmpIndexVar, RoomTypeNames.ROOM_DB).apply {
99                         // recursively load that batch
100                         addStatement("$L($L)", methodName, tmpMapVar)
101                         // clear nukes the backing data hence we create a new one
102                         addStatement("$L = new $T($T.MAX_BIND_PARAMETER_CNT)",
103                                 tmpMapVar, collector.mapTypeName, RoomTypeNames.ROOM_DB)
104                         addStatement("$L = 0", tmpIndexVar)
105                     }.endControlFlow()
106                 }.endControlFlow()
107                 beginControlFlow("if($L > 0)", tmpIndexVar).apply {
108                     // load the last batch
109                     addStatement("$L($L)", methodName, tmpMapVar)
110                 }.endControlFlow()
111                 addStatement("return")
112             }.endControlFlow()
113             collector.queryWriter.prepareReadAndBind(sqlQueryVar, stmtVar, scope)
114 
115             addStatement("final $T $L = $N.query($L)", AndroidTypeNames.CURSOR, cursorVar,
116                     DaoWriter.dbField, stmtVar)
117 
118             beginControlFlow("try").apply {
119                 addStatement("final $T $L = $L.getColumnIndex($S)",
120                         TypeName.INT, itemKeyIndexVar, cursorVar, relation.entityField.columnName)
121 
122                 beginControlFlow("if ($L == -1)", itemKeyIndexVar).apply {
123                     addStatement("return")
124                 }
125                 endControlFlow()
126 
127                 collector.rowAdapter.onCursorReady(cursorVar, scope)
128                 val tmpVarName = scope.getTmpVar("_item")
129                 beginControlFlow("while($L.moveToNext())", cursorVar).apply {
130                     // read key from the cursor
131                     collector.readKey(
132                             cursorVarName = cursorVar,
133                             indexVar = itemKeyIndexVar,
134                             scope = scope
135                     ) { keyVar ->
136                         val collectionVar = scope.getTmpVar("_tmpCollection")
137                         addStatement("$T $L = $N.get($L)", collector.collectionTypeName,
138                                 collectionVar, param, keyVar)
139                         beginControlFlow("if ($L != null)", collectionVar).apply {
140                             addStatement("final $T $L", relation.pojoTypeName, tmpVarName)
141                             collector.rowAdapter.convert(tmpVarName, cursorVar, scope)
142                             addStatement("$L.add($L)", collectionVar, tmpVarName)
143                         }
144                         endControlFlow()
145                     }
146                 }
147                 endControlFlow()
148                 collector.rowAdapter.onCursorFinished()?.invoke(scope)
149             }
150             nextControlFlow("finally").apply {
151                 addStatement("$L.close()", cursorVar)
152             }
153             endControlFlow()
154         }
155         builder.apply {
156             addModifiers(Modifier.PRIVATE)
157             addParameter(param)
158             returns(TypeName.VOID)
159             addCode(scope.builder().build())
160         }
161     }
162 }
163