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