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