Home | History | Annotate | Download | only in processor
      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 
     19 package androidx.room.processor
     20 
     21 import androidx.annotation.VisibleForTesting
     22 import androidx.room.Insert
     23 import androidx.room.OnConflictStrategy.IGNORE
     24 import androidx.room.OnConflictStrategy.REPLACE
     25 import androidx.room.vo.InsertionMethod
     26 import androidx.room.vo.InsertionMethod.Type
     27 import androidx.room.vo.ShortcutQueryParameter
     28 import com.google.auto.common.MoreTypes
     29 import com.squareup.javapoet.TypeName
     30 import javax.lang.model.element.ExecutableElement
     31 import javax.lang.model.type.DeclaredType
     32 import javax.lang.model.type.TypeKind
     33 import javax.lang.model.type.TypeKind.LONG
     34 import javax.lang.model.type.TypeKind.VOID
     35 import javax.lang.model.type.TypeMirror
     36 
     37 class 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