1/* 2 * Copyright (C) 2016 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@file:Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") 18 19package androidx.room.processor 20 21import androidx.annotation.VisibleForTesting 22import androidx.room.Insert 23import androidx.room.OnConflictStrategy.IGNORE 24import androidx.room.OnConflictStrategy.REPLACE 25import androidx.room.vo.InsertionMethod 26import androidx.room.vo.InsertionMethod.Type 27import androidx.room.vo.ShortcutQueryParameter 28import com.google.auto.common.MoreTypes 29import com.squareup.javapoet.TypeName 30import javax.lang.model.element.ExecutableElement 31import javax.lang.model.type.DeclaredType 32import javax.lang.model.type.TypeKind 33import javax.lang.model.type.TypeKind.LONG 34import javax.lang.model.type.TypeKind.VOID 35import javax.lang.model.type.TypeMirror 36 37class InsertionMethodProcessor(baseContext: Context, 38 val containing: DeclaredType, 39 val executableElement: ExecutableElement) { 40 val context = baseContext.fork(executableElement) 41 fun process(): InsertionMethod { 42 val delegate = ShortcutMethodProcessor(context, containing, executableElement) 43 val annotation = delegate.extractAnnotation(Insert::class, 44 ProcessorErrors.MISSING_INSERT_ANNOTATION) 45 46 val onConflict = OnConflictProcessor.extractFrom(annotation) 47 context.checker.check(onConflict <= IGNORE && onConflict >= REPLACE, 48 executableElement, ProcessorErrors.INVALID_ON_CONFLICT_VALUE) 49 50 val returnType = delegate.extractReturnType() 51 val returnTypeName = TypeName.get(returnType) 52 context.checker.notUnbound(returnTypeName, executableElement, 53 ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_INSERTION_METHODS) 54 55 val (entities, params) = delegate.extractParams( 56 missingParamError = ProcessorErrors 57 .INSERTION_DOES_NOT_HAVE_ANY_PARAMETERS_TO_INSERT 58 ) 59 60 // TODO we can support more types 61 var insertionType = getInsertionType(returnType) 62 context.checker.check(insertionType != null, executableElement, 63 ProcessorErrors.INVALID_INSERTION_METHOD_RETURN_TYPE) 64 65 if (insertionType != null) { 66 val acceptable = acceptableTypes(params) 67 if (insertionType !in acceptable) { 68 context.logger.e(executableElement, 69 ProcessorErrors.insertionMethodReturnTypeMismatch( 70 insertionType.returnTypeName, 71 acceptable.map { it.returnTypeName })) 72 // clear it, no reason to generate code for it. 73 insertionType = null 74 } 75 } 76 return InsertionMethod( 77 element = executableElement, 78 name = executableElement.simpleName.toString(), 79 returnType = returnType, 80 entities = entities, 81 parameters = params, 82 onConflict = onConflict, 83 insertionType = insertionType 84 ) 85 } 86 87 @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") 88 private fun getInsertionType(returnType: TypeMirror): InsertionMethod.Type? { 89 // TODO we need to support more types here. 90 fun isLongPrimitiveType(typeMirror: TypeMirror) = typeMirror.kind == LONG 91 92 fun isLongBoxType(typeMirror: TypeMirror) = 93 MoreTypes.isType(typeMirror) && 94 MoreTypes.isTypeOf(java.lang.Long::class.java, typeMirror) 95 96 fun isLongType(typeMirror: TypeMirror) = 97 isLongPrimitiveType(typeMirror) || isLongBoxType(typeMirror) 98 99 return if (returnType.kind == VOID) { 100 Type.INSERT_VOID 101 } else if (returnType.kind == TypeKind.ARRAY) { 102 val arrayType = MoreTypes.asArray(returnType) 103 val param = arrayType.componentType 104 if (isLongPrimitiveType(param)) { 105 Type.INSERT_ID_ARRAY 106 } else if (isLongBoxType(param)) { 107 Type.INSERT_ID_ARRAY_BOX 108 } else { 109 null 110 } 111 } else if (MoreTypes.isType(returnType) 112 && MoreTypes.isTypeOf(List::class.java, returnType)) { 113 val declared = MoreTypes.asDeclared(returnType) 114 val param = declared.typeArguments.first() 115 if (isLongBoxType(param)) { 116 Type.INSERT_ID_LIST 117 } else { 118 null 119 } 120 } else if (isLongType(returnType)) { 121 Type.INSERT_SINGLE_ID 122 } else { 123 null 124 } 125 } 126 127 companion object { 128 @VisibleForTesting 129 val VOID_SET by lazy { setOf(Type.INSERT_VOID) } 130 @VisibleForTesting 131 val SINGLE_ITEM_SET by lazy { setOf(Type.INSERT_VOID, Type.INSERT_SINGLE_ID) } 132 @VisibleForTesting 133 val MULTIPLE_ITEM_SET by lazy { 134 setOf(Type.INSERT_VOID, Type.INSERT_ID_ARRAY, Type.INSERT_ID_ARRAY_BOX, 135 Type.INSERT_ID_LIST) 136 } 137 fun acceptableTypes(params: List<ShortcutQueryParameter>): Set<InsertionMethod.Type> { 138 if (params.isEmpty()) { 139 return VOID_SET 140 } 141 if (params.size > 1) { 142 return VOID_SET 143 } 144 if (params.first().isMultiple) { 145 return MULTIPLE_ITEM_SET 146 } else { 147 return SINGLE_ITEM_SET 148 } 149 } 150 } 151} 152