Home | History | Annotate | Download | only in processor
      1 /*
      2  * Copyright (C) 2017 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.TypeConverter
     20 import androidx.room.TypeConverters
     21 import androidx.room.ext.hasAnnotation
     22 import androidx.room.ext.hasAnyOf
     23 import androidx.room.ext.toListOfClassTypes
     24 import androidx.room.ext.typeName
     25 import androidx.room.processor.ProcessorErrors.TYPE_CONVERTER_BAD_RETURN_TYPE
     26 import androidx.room.processor.ProcessorErrors.TYPE_CONVERTER_EMPTY_CLASS
     27 import androidx.room.processor.ProcessorErrors
     28         .TYPE_CONVERTER_MISSING_NOARG_CONSTRUCTOR
     29 import androidx.room.processor.ProcessorErrors.TYPE_CONVERTER_MUST_BE_PUBLIC
     30 import androidx.room.processor.ProcessorErrors.TYPE_CONVERTER_MUST_RECEIVE_1_PARAM
     31 import androidx.room.processor.ProcessorErrors.TYPE_CONVERTER_UNBOUND_GENERIC
     32 import androidx.room.solver.types.CustomTypeConverterWrapper
     33 import androidx.room.vo.CustomTypeConverter
     34 import com.google.auto.common.AnnotationMirrors
     35 import com.google.auto.common.MoreElements
     36 import com.google.auto.common.MoreTypes
     37 import java.util.LinkedHashSet
     38 import javax.lang.model.element.Element
     39 import javax.lang.model.element.ExecutableElement
     40 import javax.lang.model.element.Modifier
     41 import javax.lang.model.element.TypeElement
     42 import javax.lang.model.type.DeclaredType
     43 import javax.lang.model.type.TypeKind
     44 import javax.lang.model.type.TypeMirror
     45 import javax.lang.model.util.ElementFilter
     46 
     47 /**
     48  * Processes classes that are referenced in TypeConverters annotations.
     49  */
     50 class CustomConverterProcessor(val context: Context, val element: TypeElement) {
     51     companion object {
     52         private val INVALID_RETURN_TYPES = setOf(TypeKind.ERROR, TypeKind.VOID, TypeKind.NONE)
     53         fun findConverters(context: Context, element: Element): ProcessResult {
     54             val annotation = MoreElements.getAnnotationMirror(element,
     55                     TypeConverters::class.java).orNull()
     56             return annotation?.let {
     57                 val classes = AnnotationMirrors.getAnnotationValue(annotation, "value")
     58                         ?.toListOfClassTypes()
     59                         ?.filter {
     60                             MoreTypes.isType(it)
     61                         }?.mapTo(LinkedHashSet(), { it }) ?: LinkedHashSet<TypeMirror>()
     62                 val converters = classes
     63                         .flatMap {
     64                             CustomConverterProcessor(context, MoreTypes.asTypeElement(it))
     65                                     .process()
     66                         }
     67                 converters.let {
     68                     reportDuplicates(context, converters)
     69                 }
     70                 ProcessResult(classes, converters.map(::CustomTypeConverterWrapper))
     71             } ?: ProcessResult.EMPTY
     72         }
     73 
     74         fun reportDuplicates(context: Context, converters: List<CustomTypeConverter>) {
     75             val groupedByFrom = converters.groupBy { it.from.typeName() }
     76             groupedByFrom.forEach {
     77                 it.value.groupBy { it.to.typeName() }.forEach {
     78                     if (it.value.size > 1) {
     79                         it.value.forEach { converter ->
     80                             context.logger.e(converter.method, ProcessorErrors
     81                                     .duplicateTypeConverters(it.value.minus(converter)))
     82                         }
     83                     }
     84                 }
     85             }
     86         }
     87     }
     88 
     89     fun process(): List<CustomTypeConverter> {
     90         // using element utils instead of MoreElements to include statics.
     91         val methods = ElementFilter
     92                 .methodsIn(context.processingEnv.elementUtils.getAllMembers(element))
     93         val declaredType = MoreTypes.asDeclared(element.asType())
     94         val converterMethods = methods.filter {
     95             it.hasAnnotation(TypeConverter::class)
     96         }
     97         context.checker.check(converterMethods.isNotEmpty(), element, TYPE_CONVERTER_EMPTY_CLASS)
     98         val allStatic = converterMethods.all { it.modifiers.contains(Modifier.STATIC) }
     99         val constructors = ElementFilter.constructorsIn(
    100                 context.processingEnv.elementUtils.getAllMembers(element))
    101         context.checker.check(allStatic || constructors.isEmpty() || constructors.any {
    102             it.parameters.isEmpty()
    103         }, element, TYPE_CONVERTER_MISSING_NOARG_CONSTRUCTOR)
    104         return converterMethods.mapNotNull { processMethod(declaredType, it) }
    105     }
    106 
    107     private fun processMethod(
    108             container: DeclaredType, methodElement: ExecutableElement): CustomTypeConverter? {
    109         val asMember = context.processingEnv.typeUtils.asMemberOf(container, methodElement)
    110         val executableType = MoreTypes.asExecutable(asMember)
    111         val returnType = executableType.returnType
    112         val invalidReturnType = INVALID_RETURN_TYPES.contains(returnType.kind)
    113         context.checker.check(methodElement.hasAnyOf(Modifier.PUBLIC), methodElement,
    114                 TYPE_CONVERTER_MUST_BE_PUBLIC)
    115         if (invalidReturnType) {
    116             context.logger.e(methodElement, TYPE_CONVERTER_BAD_RETURN_TYPE)
    117             return null
    118         }
    119         val returnTypeName = returnType.typeName()
    120         context.checker.notUnbound(returnTypeName, methodElement,
    121                 TYPE_CONVERTER_UNBOUND_GENERIC)
    122         val params = methodElement.parameters
    123         if (params.size != 1) {
    124             context.logger.e(methodElement, TYPE_CONVERTER_MUST_RECEIVE_1_PARAM)
    125             return null
    126         }
    127         val param = params.map {
    128             MoreTypes.asMemberOf(context.processingEnv.typeUtils, container, it)
    129         }.first()
    130         context.checker.notUnbound(param.typeName(), params[0], TYPE_CONVERTER_UNBOUND_GENERIC)
    131         return CustomTypeConverter(container, methodElement, param, returnType)
    132     }
    133 
    134     /**
    135      * Order of classes is important hence they are a LinkedHashSet not a set.
    136      */
    137     open class ProcessResult(
    138             val classes: LinkedHashSet<TypeMirror>,
    139             val converters: List<CustomTypeConverterWrapper>
    140     ) {
    141         object EMPTY : ProcessResult(LinkedHashSet(), emptyList())
    142         operator fun plus(other: ProcessResult): ProcessResult {
    143             val newClasses = LinkedHashSet<TypeMirror>()
    144             newClasses.addAll(classes)
    145             newClasses.addAll(other.classes)
    146             return ProcessResult(newClasses, converters + other.converters)
    147         }
    148     }
    149 }
    150