Home | History | Annotate | Download | only in processor
      1 /*
      2  * Copyright 2018 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.processor
     18 
     19 import androidx.room.RawQuery
     20 import androidx.room.Transaction
     21 import androidx.room.ext.SupportDbTypeNames
     22 import androidx.room.ext.hasAnnotation
     23 import androidx.room.ext.toListOfClassTypes
     24 import androidx.room.ext.typeName
     25 import androidx.room.parser.SqlParser
     26 import androidx.room.processor.ProcessorErrors.RAW_QUERY_STRING_PARAMETER_REMOVED
     27 import androidx.room.vo.RawQueryMethod
     28 import com.google.auto.common.AnnotationMirrors
     29 import com.google.auto.common.MoreElements
     30 import com.google.auto.common.MoreTypes
     31 import com.squareup.javapoet.TypeName
     32 import javax.lang.model.element.ExecutableElement
     33 import javax.lang.model.type.DeclaredType
     34 
     35 class RawQueryMethodProcessor(
     36         baseContext: Context,
     37         val containing: DeclaredType,
     38         val executableElement: ExecutableElement) {
     39     val context = baseContext.fork(executableElement)
     40     fun process(): RawQueryMethod {
     41         val types = context.processingEnv.typeUtils
     42         val asMember = types.asMemberOf(containing, executableElement)
     43         val executableType = MoreTypes.asExecutable(asMember)
     44 
     45         val annotation = MoreElements.getAnnotationMirror(executableElement,
     46                 RawQuery::class.java).orNull()
     47         context.checker.check(annotation != null, executableElement,
     48                 ProcessorErrors.MISSING_RAWQUERY_ANNOTATION)
     49 
     50         val returnTypeName = TypeName.get(executableType.returnType)
     51         context.checker.notUnbound(returnTypeName, executableElement,
     52                 ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_QUERY_METHODS)
     53         val observedTableNames = processObservedTables()
     54         val query = SqlParser.rawQueryForTables(observedTableNames)
     55         // build the query but don't calculate result info since we just guessed it.
     56         val resultBinder = context.typeAdapterStore
     57                 .findQueryResultBinder(executableType.returnType, query)
     58 
     59         val runtimeQueryParam = findRuntimeQueryParameter()
     60         val inTransaction = executableElement.hasAnnotation(Transaction::class)
     61         val rawQueryMethod = RawQueryMethod(
     62                 element = executableElement,
     63                 name = executableElement.simpleName.toString(),
     64                 observedTableNames = observedTableNames,
     65                 returnType = executableType.returnType,
     66                 runtimeQueryParam = runtimeQueryParam,
     67                 inTransaction = inTransaction,
     68                 queryResultBinder = resultBinder
     69         )
     70         context.checker.check(rawQueryMethod.returnsValue, executableElement,
     71                 ProcessorErrors.RAW_QUERY_BAD_RETURN_TYPE)
     72         return rawQueryMethod
     73     }
     74 
     75     private fun processObservedTables(): Set<String> {
     76         val annotation = MoreElements
     77                 .getAnnotationMirror(executableElement,
     78                         androidx.room.RawQuery::class.java)
     79                 .orNull() ?: return emptySet()
     80         val entityList = AnnotationMirrors.getAnnotationValue(annotation, "observedEntities")
     81         return entityList
     82                 .toListOfClassTypes()
     83                 .map {
     84                     MoreTypes.asTypeElement(it)
     85                 }
     86                 .flatMap {
     87                     if (it.hasAnnotation(androidx.room.Entity::class)) {
     88                         val entity = EntityProcessor(
     89                                 baseContext = context,
     90                                 element = it
     91                         ).process()
     92                         arrayListOf(entity.tableName)
     93                     } else {
     94                         val pojo = PojoProcessor(
     95                                 baseContext = context,
     96                                 element = it,
     97                                 bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
     98                                 parent = null
     99                         ).process()
    100                         val tableNames = pojo.accessedTableNames()
    101                         // if it is empty, report error as it does not make sense
    102                         if (tableNames.isEmpty()) {
    103                             context.logger.e(executableElement,
    104                                     ProcessorErrors.rawQueryBadEntity(it.asType().typeName()))
    105                         }
    106                         tableNames
    107                     }
    108                 }.toSet()
    109     }
    110 
    111     private fun findRuntimeQueryParameter(): RawQueryMethod.RuntimeQueryParameter? {
    112         val types = context.processingEnv.typeUtils
    113         if (executableElement.parameters.size == 1 && !executableElement.isVarArgs) {
    114             val param = MoreTypes.asMemberOf(
    115                     types,
    116                     containing,
    117                     executableElement.parameters[0])
    118             val elementUtils = context.processingEnv.elementUtils
    119             val supportQueryType = elementUtils
    120                     .getTypeElement(SupportDbTypeNames.QUERY.toString()).asType()
    121             val isSupportSql = types.isAssignable(param, supportQueryType)
    122             if (isSupportSql) {
    123                 return RawQueryMethod.RuntimeQueryParameter(
    124                         paramName = executableElement.parameters[0].simpleName.toString(),
    125                         type = supportQueryType.typeName())
    126             }
    127             val stringType = elementUtils.getTypeElement("java.lang.String").asType()
    128             val isString = types.isAssignable(param, stringType)
    129             if (isString) {
    130                 // special error since this was initially allowed but removed in 1.1 beta1
    131                 context.logger.e(executableElement, RAW_QUERY_STRING_PARAMETER_REMOVED)
    132                 return null
    133             }
    134         }
    135         context.logger.e(executableElement, ProcessorErrors.RAW_QUERY_BAD_PARAMS)
    136         return null
    137     }
    138 }