1 /*
<lambda>null2  * Copyright 2022 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.solver.shortcut.result
18 
19 import androidx.room.compiler.codegen.CodeLanguage
20 import androidx.room.compiler.codegen.XCodeBlock
21 import androidx.room.compiler.codegen.XPropertySpec
22 import androidx.room.compiler.codegen.XTypeName
23 import androidx.room.compiler.processing.XType
24 import androidx.room.compiler.processing.isArray
25 import androidx.room.compiler.processing.isKotlinUnit
26 import androidx.room.compiler.processing.isLong
27 import androidx.room.compiler.processing.isVoid
28 import androidx.room.compiler.processing.isVoidObject
29 import androidx.room.ext.CommonTypeNames
30 import androidx.room.ext.KotlinTypeNames
31 import androidx.room.ext.isList
32 import androidx.room.ext.isNotKotlinUnit
33 import androidx.room.ext.isNotVoid
34 import androidx.room.ext.isNotVoidObject
35 import androidx.room.processor.Context
36 import androidx.room.processor.ProcessorErrors
37 import androidx.room.solver.CodeGenScope
38 import androidx.room.vo.ShortcutQueryParameter
39 
40 class InsertOrUpsertFunctionAdapter private constructor(private val functionInfo: FunctionInfo) {
41     internal val returnType = functionInfo.returnType
42 
43     companion object {
44         fun createInsert(
45             context: Context,
46             returnType: XType,
47             params: List<ShortcutQueryParameter>
48         ): InsertOrUpsertFunctionAdapter? {
49             return createFunction(
50                 context = context,
51                 returnType = returnType,
52                 params = params,
53                 functionInfoClass = ::InsertFunctionInfo,
54                 multiParamSingleReturnError =
55                     ProcessorErrors.INSERT_MULTI_PARAM_SINGLE_RETURN_MISMATCH,
56                 singleParamMultiReturnError =
57                     ProcessorErrors.INSERT_SINGLE_PARAM_MULTI_RETURN_MISMATCH
58             )
59         }
60 
61         fun createUpsert(
62             context: Context,
63             returnType: XType,
64             params: List<ShortcutQueryParameter>
65         ): InsertOrUpsertFunctionAdapter? {
66             return createFunction(
67                 context = context,
68                 returnType = returnType,
69                 params = params,
70                 functionInfoClass = ::UpsertFunctionInfo,
71                 multiParamSingleReturnError =
72                     ProcessorErrors.UPSERT_MULTI_PARAM_SINGLE_RETURN_MISMATCH,
73                 singleParamMultiReturnError =
74                     ProcessorErrors.UPSERT_SINGLE_PARAM_MULTI_RETURN_MISMATCH
75             )
76         }
77 
78         private fun createFunction(
79             context: Context,
80             returnType: XType,
81             params: List<ShortcutQueryParameter>,
82             functionInfoClass: (returnInfo: ReturnInfo, returnType: XType) -> FunctionInfo,
83             multiParamSingleReturnError: String,
84             singleParamMultiReturnError: String
85         ): InsertOrUpsertFunctionAdapter? {
86             val functionReturnType = getReturnType(returnType)
87             if (
88                 functionReturnType != null &&
89                     isReturnValid(
90                         context,
91                         functionReturnType,
92                         params,
93                         multiParamSingleReturnError,
94                         singleParamMultiReturnError
95                     )
96             ) {
97                 val functionInfo = functionInfoClass(functionReturnType, returnType)
98                 return InsertOrUpsertFunctionAdapter(functionInfo = functionInfo)
99             }
100             return null
101         }
102 
103         private fun isReturnValid(
104             context: Context,
105             returnInfo: ReturnInfo,
106             params: List<ShortcutQueryParameter>,
107             multiParamSingleReturnError: String,
108             singleParamMultiReturnError: String
109         ): Boolean {
110             if (params.isEmpty() || params.size > 1) {
111                 return returnInfo == ReturnInfo.VOID ||
112                     returnInfo == ReturnInfo.UNIT ||
113                     returnInfo == ReturnInfo.VOID_OBJECT
114             }
115             if (params.first().isMultiple) {
116                 val isValid = returnInfo in MULTIPLE_ITEM_SET
117                 if (!isValid) {
118                     context.logger.e(multiParamSingleReturnError)
119                 }
120                 return isValid
121             } else {
122                 val isValid =
123                     (returnInfo == ReturnInfo.VOID ||
124                         returnInfo == ReturnInfo.VOID_OBJECT ||
125                         returnInfo == ReturnInfo.UNIT ||
126                         returnInfo == ReturnInfo.SINGLE_ID)
127                 if (!isValid) {
128                     context.logger.e(singleParamMultiReturnError)
129                 }
130                 return isValid
131             }
132         }
133 
134         private val MULTIPLE_ITEM_SET by lazy {
135             setOf(
136                 ReturnInfo.VOID,
137                 ReturnInfo.VOID_OBJECT,
138                 ReturnInfo.UNIT,
139                 ReturnInfo.ID_ARRAY,
140                 ReturnInfo.ID_ARRAY_BOX,
141                 ReturnInfo.ID_LIST
142             )
143         }
144 
145         private fun getReturnType(returnType: XType): ReturnInfo? {
146             return if (returnType.isVoid()) {
147                 ReturnInfo.VOID
148             } else if (returnType.isVoidObject()) {
149                 ReturnInfo.VOID_OBJECT
150             } else if (returnType.isKotlinUnit()) {
151                 ReturnInfo.UNIT
152             } else if (returnType.isArray()) {
153                 val param = returnType.componentType
154                 if (param.isLong()) {
155                     if (param.asTypeName() == XTypeName.PRIMITIVE_LONG) {
156                         ReturnInfo.ID_ARRAY
157                     } else {
158                         ReturnInfo.ID_ARRAY_BOX
159                     }
160                 } else {
161                     null
162                 }
163             } else if (returnType.isList()) {
164                 val param = returnType.typeArguments.first()
165                 if (param.isLong()) {
166                     ReturnInfo.ID_LIST
167                 } else {
168                     null
169                 }
170             } else if (returnType.isLong()) {
171                 ReturnInfo.SINGLE_ID
172             } else {
173                 null
174             }
175         }
176     }
177 
178     fun generateFunctionBody(
179         scope: CodeGenScope,
180         connectionVar: String,
181         parameters: List<ShortcutQueryParameter>,
182         adapters: Map<String, Pair<XPropertySpec, Any>>
183     ) {
184         scope.builder.apply {
185             val hasReturnValue =
186                 returnType.isNotVoid() &&
187                     returnType.isNotVoidObject() &&
188                     returnType.isNotKotlinUnit()
189             val resultVar =
190                 if (hasReturnValue) {
191                     scope.getTmpVar("_result")
192                 } else {
193                     null
194                 }
195             parameters.forEach { param ->
196                 val upsertAdapter = adapters.getValue(param.name).first
197                 val resultFormat =
198                     XCodeBlock.of(
199                             "%L.%L(%L, %L)",
200                             upsertAdapter.name,
201                             functionInfo.functionName,
202                             connectionVar,
203                             param.name
204                         )
205                         .let {
206                             if (
207                                 scope.language == CodeLanguage.KOTLIN &&
208                                     functionInfo.returnInfo == ReturnInfo.ID_ARRAY_BOX &&
209                                     functionInfo.returnType.asTypeName() ==
210                                         functionInfo.returnInfo.typeName
211                             ) {
212                                 XCodeBlock.ofCast(
213                                     typeName = functionInfo.returnInfo.typeName,
214                                     expressionBlock = it
215                                 )
216                             } else {
217                                 it
218                             }
219                         }
220                 when (scope.language) {
221                     CodeLanguage.JAVA -> {
222                         when (functionInfo.returnInfo) {
223                             ReturnInfo.VOID,
224                             ReturnInfo.VOID_OBJECT -> {
225                                 if (param == parameters.last()) {
226                                     addStatement("%L", resultFormat)
227                                     addStatement("return null")
228                                 } else {
229                                     addStatement("%L", resultFormat)
230                                 }
231                             }
232                             ReturnInfo.UNIT -> {
233                                 if (param == parameters.last()) {
234                                     addStatement("%L", resultFormat)
235                                     addStatement("return %T.INSTANCE", KotlinTypeNames.UNIT)
236                                 } else {
237                                     addStatement("%L", resultFormat)
238                                 }
239                             }
240                             else -> addStatement("return %L", resultFormat)
241                         }
242                     }
243                     CodeLanguage.KOTLIN -> {
244                         if (resultVar != null) {
245                             // if it has more than 1 parameter, we would've already printed the
246                             // error
247                             // so we don't care about re-declaring the variable here
248                             addLocalVariable(
249                                 name = resultVar,
250                                 typeName = returnType.asTypeName(),
251                                 assignExpr = resultFormat
252                             )
253                         } else {
254                             addStatement("%L", resultFormat)
255                         }
256                     }
257                 }
258             }
259             if (scope.language == CodeLanguage.KOTLIN && resultVar != null) {
260                 addStatement("%L", resultVar)
261             }
262         }
263     }
264 
265     sealed class FunctionInfo(val returnInfo: ReturnInfo, val returnType: XType) {
266         abstract val functionName: String
267     }
268 
269     class InsertFunctionInfo(returnInfo: ReturnInfo, returnType: XType) :
270         FunctionInfo(returnInfo, returnType) {
271         override val functionName: String = "insert${returnInfo.functionSuffix}"
272     }
273 
274     class UpsertFunctionInfo(returnInfo: ReturnInfo, returnType: XType) :
275         FunctionInfo(returnInfo, returnType) {
276         override val functionName: String = "upsert${returnInfo.functionSuffix}"
277     }
278 
279     enum class ReturnInfo(val functionSuffix: String, val typeName: XTypeName) {
280         VOID("", XTypeName.UNIT_VOID), // return void
281         VOID_OBJECT("", CommonTypeNames.VOID), // return Void
282         UNIT("", XTypeName.UNIT_VOID), // return kotlin.Unit.INSTANCE
283         SINGLE_ID("AndReturnId", XTypeName.PRIMITIVE_LONG), // return long
284         ID_ARRAY(
285             "AndReturnIdsArray",
286             XTypeName.getArrayName(XTypeName.PRIMITIVE_LONG)
287         ), // return long[]
288         ID_ARRAY_BOX(
289             "AndReturnIdsArrayBox",
290             XTypeName.getArrayName(XTypeName.BOXED_LONG)
291         ), // return Long[]
292         ID_LIST(
293             "AndReturnIdsList",
294             CommonTypeNames.LIST.parametrizedBy(XTypeName.BOXED_LONG)
295         ), // return List<Long>
296     }
297 }
298