1 /*
<lambda>null2  * 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