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.vo
18 
19 import androidx.room.ext.AndroidTypeNames
20 import androidx.room.ext.CommonTypeNames
21 import androidx.room.ext.L
22 import androidx.room.ext.N
23 import androidx.room.ext.T
24 import androidx.room.ext.typeName
25 import androidx.room.parser.ParsedQuery
26 import androidx.room.parser.SQLTypeAffinity
27 import androidx.room.parser.SqlParser
28 import androidx.room.processor.Context
29 import androidx.room.processor.ProcessorErrors.CANNOT_FIND_QUERY_RESULT_ADAPTER
30 import androidx.room.processor.ProcessorErrors.relationAffinityMismatch
31 import androidx.room.solver.CodeGenScope
32 import androidx.room.solver.query.result.RowAdapter
33 import androidx.room.solver.query.result.SingleColumnRowAdapter
34 import androidx.room.verifier.DatabaseVerificaitonErrors
35 import androidx.room.writer.QueryWriter
36 import androidx.room.writer.RelationCollectorMethodWriter
37 import com.squareup.javapoet.ArrayTypeName
38 import com.squareup.javapoet.ClassName
39 import com.squareup.javapoet.CodeBlock
40 import com.squareup.javapoet.ParameterizedTypeName
41 import com.squareup.javapoet.TypeName
42 import stripNonJava
43 import java.util.ArrayList
44 import java.util.HashSet
45 import javax.lang.model.type.TypeKind
46 import javax.lang.model.type.TypeMirror
47 
48 /**
49  * Internal class that is used to manage fetching 1/N to N relationships.
50  */
51 data class RelationCollector(val relation: Relation,
52                              val affinity: SQLTypeAffinity,
53                              val mapTypeName: ParameterizedTypeName,
54                              val keyTypeName: TypeName,
55                              val collectionTypeName: ParameterizedTypeName,
56                              val queryWriter: QueryWriter,
57                              val rowAdapter: RowAdapter,
58                              val loadAllQuery: ParsedQuery) {
59     // set when writing the code generator in writeInitCode
60     lateinit var varName: String
61 
62     fun writeInitCode(scope: CodeGenScope) {
63         val tmpVar = scope.getTmpVar(
64                 "_collection${relation.field.getPath().stripNonJava().capitalize()}")
65         scope.builder().addStatement("final $T $L = new $T()", mapTypeName, tmpVar, mapTypeName)
66         varName = tmpVar
67     }
68 
69     // called after reading each item to extract the key if it exists
70     fun writeReadParentKeyCode(cursorVarName: String, itemVar: String,
71                                fieldsWithIndices: List<FieldWithIndex>, scope: CodeGenScope) {
72         val indexVar = fieldsWithIndices.firstOrNull {
73             it.field === relation.parentField
74         }?.indexVar
75         scope.builder().apply {
76             readKey(
77                     cursorVarName = cursorVarName,
78                     indexVar = indexVar,
79                     scope = scope
80             ) { tmpVar ->
81                 val tmpCollectionVar = scope.getTmpVar("_tmpCollection")
82                 addStatement("$T $L = $L.get($L)", collectionTypeName, tmpCollectionVar,
83                         varName, tmpVar)
84                 beginControlFlow("if($L == null)", tmpCollectionVar).apply {
85                     addStatement("$L = new $T()", tmpCollectionVar, collectionTypeName)
86                     addStatement("$L.put($L, $L)", varName, tmpVar, tmpCollectionVar)
87                 }
88                 endControlFlow()
89                 // set it on the item
90                 relation.field.setter.writeSet(itemVar, tmpCollectionVar, this)
91             }
92         }
93     }
94 
95     fun writeCollectionCode(scope: CodeGenScope) {
96         val method = scope.writer
97                 .getOrCreateMethod(RelationCollectorMethodWriter(this))
98         scope.builder().apply {
99             addStatement("$N($L)", method, varName)
100         }
101     }
102 
103     fun readKey(cursorVarName: String, indexVar: String?, scope: CodeGenScope,
104                 postRead: CodeBlock.Builder.(String) -> Unit) {
105         val cursorGetter = when (affinity) {
106             SQLTypeAffinity.INTEGER -> "getLong"
107             SQLTypeAffinity.REAL -> "getDouble"
108             SQLTypeAffinity.TEXT -> "getString"
109             SQLTypeAffinity.BLOB -> "getBlob"
110             else -> {
111                 "getString"
112             }
113         }
114         scope.builder().apply {
115             beginControlFlow("if (!$L.isNull($L))", cursorVarName, indexVar).apply {
116                 val tmpVar = scope.getTmpVar("_tmpKey")
117                 addStatement("final $T $L = $L.$L($L)", keyTypeName,
118                         tmpVar, cursorVarName, cursorGetter, indexVar)
119                 this.postRead(tmpVar)
120             }
121             endControlFlow()
122         }
123     }
124 
125     companion object {
126         fun createCollectors(
127                 baseContext: Context,
128                 relations: List<Relation>
129         ): List<RelationCollector> {
130             return relations.map { relation ->
131                 // decide on the affinity
132                 val context = baseContext.fork(relation.field.element)
133                 val parentAffinity = relation.parentField.cursorValueReader?.affinity()
134                 val childAffinity = relation.entityField.cursorValueReader?.affinity()
135                 val affinity = if (parentAffinity != null && parentAffinity == childAffinity) {
136                     parentAffinity
137                 } else {
138                     context.logger.w(Warning.RELATION_TYPE_MISMATCH, relation.field.element,
139                             relationAffinityMismatch(
140                                     parentColumn = relation.parentField.columnName,
141                                     childColumn = relation.entityField.columnName,
142                                     parentAffinity = parentAffinity,
143                                     childAffinity = childAffinity))
144                     SQLTypeAffinity.TEXT
145                 }
146                 val keyType = keyTypeFor(context, affinity)
147                 val collectionTypeName = if (relation.field.typeName is ParameterizedTypeName) {
148                     val paramType = relation.field.typeName as ParameterizedTypeName
149                     if (paramType.rawType == CommonTypeNames.LIST) {
150                         ParameterizedTypeName.get(ClassName.get(ArrayList::class.java),
151                                 relation.pojoTypeName)
152                     } else if (paramType.rawType == CommonTypeNames.SET) {
153                         ParameterizedTypeName.get(ClassName.get(HashSet::class.java),
154                                 relation.pojoTypeName)
155                     } else {
156                         ParameterizedTypeName.get(ClassName.get(ArrayList::class.java),
157                                 relation.pojoTypeName)
158                     }
159                 } else {
160                     ParameterizedTypeName.get(ClassName.get(ArrayList::class.java),
161                             relation.pojoTypeName)
162                 }
163 
164                 val canUseArrayMap = context.processingEnv.elementUtils
165                         .getTypeElement(AndroidTypeNames.ARRAY_MAP.toString()) != null
166                 val mapClass = if (canUseArrayMap) {
167                     AndroidTypeNames.ARRAY_MAP
168                 } else {
169                     ClassName.get(java.util.HashMap::class.java)
170                 }
171                 val tmpMapType = ParameterizedTypeName.get(mapClass, keyType, collectionTypeName)
172                 val keyTypeMirror = keyTypeMirrorFor(context, affinity)
173                 val set = context.processingEnv.elementUtils.getTypeElement("java.util.Set")
174                 val keySet = context.processingEnv.typeUtils.getDeclaredType(set, keyTypeMirror)
175                 val loadAllQuery = relation.createLoadAllSql()
176                 val parsedQuery = SqlParser.parse(loadAllQuery)
177                 context.checker.check(parsedQuery.errors.isEmpty(), relation.field.element,
178                         parsedQuery.errors.joinToString("\n"))
179                 if (parsedQuery.errors.isEmpty()) {
180                     val resultInfo = context.databaseVerifier?.analyze(loadAllQuery)
181                     parsedQuery.resultInfo = resultInfo
182                     if (resultInfo?.error != null) {
183                         context.logger.e(relation.field.element,
184                                 DatabaseVerificaitonErrors.cannotVerifyQuery(resultInfo.error))
185                     }
186                 }
187                 val resultInfo = parsedQuery.resultInfo
188 
189                 val queryParam = QueryParameter(
190                         name = RelationCollectorMethodWriter.KEY_SET_VARIABLE,
191                         sqlName = RelationCollectorMethodWriter.KEY_SET_VARIABLE,
192                         type = keySet,
193                         queryParamAdapter =
194                                 context.typeAdapterStore.findQueryParameterAdapter(keySet))
195                 val queryWriter = QueryWriter(
196                         parameters = listOf(queryParam),
197                         sectionToParamMapping = listOf(Pair(parsedQuery.bindSections.first(),
198                                 queryParam)),
199                         query = parsedQuery
200                 )
201 
202                 // row adapter that matches full response
203                 fun getDefaultRowAdapter(): RowAdapter? {
204                     return context.typeAdapterStore.findRowAdapter(relation.pojoType, parsedQuery)
205                 }
206                 val rowAdapter = if (relation.projection.size == 1 && resultInfo != null &&
207                         (resultInfo.columns.size == 1 || resultInfo.columns.size == 2)) {
208                     // check for a column adapter first
209                     val cursorReader = context.typeAdapterStore.findCursorValueReader(
210                             relation.pojoType, resultInfo.columns.first().type)
211                     if (cursorReader == null) {
212                         getDefaultRowAdapter()
213                     } else {
214                         context.logger.d("Choosing cursor adapter for the return value since" +
215                                 " the query returns only 1 or 2 columns and there is a cursor" +
216                                 " adapter for the return type.")
217                         SingleColumnRowAdapter(cursorReader)
218                     }
219                 } else {
220                     getDefaultRowAdapter()
221                 }
222 
223                 if (rowAdapter == null) {
224                     context.logger.e(relation.field.element, CANNOT_FIND_QUERY_RESULT_ADAPTER)
225                     null
226                 } else {
227                     RelationCollector(
228                             relation = relation,
229                             affinity = affinity,
230                             mapTypeName = tmpMapType,
231                             keyTypeName = keyType,
232                             collectionTypeName = collectionTypeName,
233                             queryWriter = queryWriter,
234                             rowAdapter = rowAdapter,
235                             loadAllQuery = parsedQuery
236                     )
237                 }
238             }.filterNotNull()
239         }
240 
241         private fun keyTypeMirrorFor(context: Context, affinity: SQLTypeAffinity): TypeMirror {
242             val types = context.processingEnv.typeUtils
243             val elements = context.processingEnv.elementUtils
244             return when (affinity) {
245                 SQLTypeAffinity.INTEGER -> elements.getTypeElement("java.lang.Long").asType()
246                 SQLTypeAffinity.REAL -> elements.getTypeElement("java.lang.Double").asType()
247                 SQLTypeAffinity.TEXT -> context.COMMON_TYPES.STRING
248                 SQLTypeAffinity.BLOB -> types.getArrayType(types.getPrimitiveType(TypeKind.BYTE))
249                 else -> {
250                     context.COMMON_TYPES.STRING
251                 }
252             }
253         }
254 
255         private fun keyTypeFor(context: Context, affinity: SQLTypeAffinity): TypeName {
256             return when (affinity) {
257                 SQLTypeAffinity.INTEGER -> TypeName.LONG.box()
258                 SQLTypeAffinity.REAL -> TypeName.DOUBLE.box()
259                 SQLTypeAffinity.TEXT -> TypeName.get(String::class.java)
260                 SQLTypeAffinity.BLOB -> ArrayTypeName.of(TypeName.BYTE)
261                 else -> {
262                     // no affinity select from type
263                     context.COMMON_TYPES.STRING.typeName()
264                 }
265             }
266         }
267     }
268 }
269