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 package androidx.room.processor
     18 
     19 import androidx.room.Query
     20 import androidx.room.SkipQueryVerification
     21 import androidx.room.Transaction
     22 import androidx.room.ext.KotlinMetadataProcessor
     23 import androidx.room.ext.hasAnnotation
     24 import androidx.room.parser.ParsedQuery
     25 import androidx.room.parser.QueryType
     26 import androidx.room.parser.SqlParser
     27 import androidx.room.solver.query.result.LiveDataQueryResultBinder
     28 import androidx.room.solver.query.result.PojoRowAdapter
     29 import androidx.room.verifier.DatabaseVerificaitonErrors
     30 import androidx.room.verifier.DatabaseVerifier
     31 import androidx.room.vo.QueryMethod
     32 import androidx.room.vo.QueryParameter
     33 import androidx.room.vo.Warning
     34 import com.google.auto.common.AnnotationMirrors
     35 import com.google.auto.common.MoreElements
     36 import com.google.auto.common.MoreTypes
     37 import com.squareup.javapoet.TypeName
     38 import me.eugeniomarletti.kotlin.metadata.KotlinClassMetadata
     39 import me.eugeniomarletti.kotlin.metadata.kotlinMetadata
     40 import javax.annotation.processing.ProcessingEnvironment
     41 import javax.lang.model.element.ExecutableElement
     42 import javax.lang.model.type.DeclaredType
     43 import javax.lang.model.type.TypeKind
     44 
     45 class QueryMethodProcessor(
     46         baseContext: Context,
     47         val containing: DeclaredType,
     48         val executableElement: ExecutableElement,
     49         val dbVerifier: DatabaseVerifier? = null
     50 ) : KotlinMetadataProcessor {
     51     val context = baseContext.fork(executableElement)
     52 
     53     // for kotlin metadata
     54     override val processingEnv: ProcessingEnvironment
     55         get() = context.processingEnv
     56 
     57     private val classMetadata =
     58             try {
     59                 containing.asElement().kotlinMetadata
     60             } catch (throwable: Throwable) {
     61                 context.logger.d(executableElement,
     62                         "failed to read get kotlin metadata from %s", executableElement)
     63             } as? KotlinClassMetadata
     64 
     65     fun process(): QueryMethod {
     66         val asMember = context.processingEnv.typeUtils.asMemberOf(containing, executableElement)
     67         val executableType = MoreTypes.asExecutable(asMember)
     68 
     69         val annotation = MoreElements.getAnnotationMirror(executableElement,
     70                 Query::class.java).orNull()
     71         context.checker.check(annotation != null, executableElement,
     72                 ProcessorErrors.MISSING_QUERY_ANNOTATION)
     73 
     74         val query = if (annotation != null) {
     75             val query = SqlParser.parse(
     76                     AnnotationMirrors.getAnnotationValue(annotation, "value").value.toString())
     77             context.checker.check(query.errors.isEmpty(), executableElement,
     78                     query.errors.joinToString("\n"))
     79             if (!executableElement.hasAnnotation(SkipQueryVerification::class)) {
     80                 query.resultInfo = dbVerifier?.analyze(query.original)
     81             }
     82             if (query.resultInfo?.error != null) {
     83                 context.logger.e(executableElement,
     84                         DatabaseVerificaitonErrors.cannotVerifyQuery(query.resultInfo!!.error!!))
     85             }
     86 
     87             context.checker.check(executableType.returnType.kind != TypeKind.ERROR,
     88                     executableElement, ProcessorErrors.CANNOT_RESOLVE_RETURN_TYPE,
     89                     executableElement)
     90             query
     91         } else {
     92             ParsedQuery.MISSING
     93         }
     94 
     95         val returnTypeName = TypeName.get(executableType.returnType)
     96         context.checker.notUnbound(returnTypeName, executableElement,
     97                 ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_QUERY_METHODS)
     98 
     99         if (query.type == QueryType.DELETE) {
    100             context.checker.check(
    101                     returnTypeName == TypeName.VOID || returnTypeName == TypeName.INT,
    102                     executableElement,
    103                     ProcessorErrors.DELETION_METHODS_MUST_RETURN_VOID_OR_INT
    104             )
    105         }
    106         val resultBinder = context.typeAdapterStore
    107                 .findQueryResultBinder(executableType.returnType, query)
    108         context.checker.check(resultBinder.adapter != null || query.type != QueryType.SELECT,
    109                 executableElement, ProcessorErrors.CANNOT_FIND_QUERY_RESULT_ADAPTER)
    110         if (resultBinder is LiveDataQueryResultBinder) {
    111             context.checker.check(query.type == QueryType.SELECT, executableElement,
    112                     ProcessorErrors.LIVE_DATA_QUERY_WITHOUT_SELECT)
    113         }
    114 
    115         val inTransaction = when (query.type) {
    116             QueryType.SELECT -> executableElement.hasAnnotation(Transaction::class)
    117             else -> true
    118         }
    119 
    120         if (query.type == QueryType.SELECT && !inTransaction) {
    121             // put a warning if it is has relations and not annotated w/ transaction
    122             resultBinder.adapter?.rowAdapter?.let { rowAdapter ->
    123                 if (rowAdapter is PojoRowAdapter
    124                         && rowAdapter.relationCollectors.isNotEmpty()) {
    125                     context.logger.w(Warning.RELATION_QUERY_WITHOUT_TRANSACTION,
    126                             executableElement, ProcessorErrors.TRANSACTION_MISSING_ON_RELATION)
    127                 }
    128             }
    129         }
    130         val kotlinParameterNames = classMetadata?.getParameterNames(executableElement)
    131 
    132         val parameters = executableElement.parameters
    133                 .mapIndexed { index, variableElement ->
    134                     QueryParameterProcessor(
    135                             baseContext = context,
    136                             containing = containing,
    137                             element = variableElement,
    138                             sqlName = kotlinParameterNames?.getOrNull(index)).process()
    139                 }
    140         val queryMethod = QueryMethod(
    141                 element = executableElement,
    142                 query = query,
    143                 name = executableElement.simpleName.toString(),
    144                 returnType = executableType.returnType,
    145                 parameters = parameters,
    146                 inTransaction = inTransaction,
    147                 queryResultBinder = resultBinder)
    148 
    149         val missing = queryMethod.sectionToParamMapping
    150                 .filter { it.second == null }
    151                 .map { it.first.text }
    152         if (missing.isNotEmpty()) {
    153             context.logger.e(executableElement,
    154                     ProcessorErrors.missingParameterForBindVariable(missing))
    155         }
    156 
    157         val unused = queryMethod.parameters.filterNot { param ->
    158             queryMethod.sectionToParamMapping.any { it.second == param }
    159         }.map(QueryParameter::sqlName)
    160 
    161         if (unused.isNotEmpty()) {
    162             context.logger.e(executableElement, ProcessorErrors.unusedQueryMethodParameter(unused))
    163         }
    164         return queryMethod
    165     }
    166 }
    167