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
17package android.arch.lifecycle
18
19import android.arch.lifecycle.model.EventMethod
20import android.arch.lifecycle.model.LifecycleObserverInfo
21import com.google.auto.common.MoreElements
22import com.google.auto.common.MoreTypes
23import javax.annotation.processing.ProcessingEnvironment
24import javax.annotation.processing.RoundEnvironment
25import javax.lang.model.element.Element
26import javax.lang.model.element.ElementKind
27import javax.lang.model.element.ExecutableElement
28import javax.lang.model.element.Modifier
29import javax.lang.model.element.TypeElement
30import javax.lang.model.element.VariableElement
31import javax.tools.Diagnostic
32
33fun collectAndVerifyInput(processingEnv: ProcessingEnvironment,
34                          roundEnv: RoundEnvironment): Map<TypeElement, LifecycleObserverInfo> {
35    val validator = Validator(processingEnv)
36
37    return roundEnv.getElementsAnnotatedWith(OnLifecycleEvent::class.java).map { elem ->
38        if (elem.kind != ElementKind.METHOD) {
39            validator.printErrorMessage(ErrorMessages.INVALID_ANNOTATED_ELEMENT, elem)
40            null
41        } else {
42            val enclosingElement = elem.enclosingElement
43            val onState = elem.getAnnotation(OnLifecycleEvent::class.java)
44            val method = MoreElements.asExecutable(elem)
45            if (validator.validateClass(enclosingElement)
46                    && validator.validateMethod(method, onState.value)) {
47                EventMethod(method, onState, MoreElements.asType(enclosingElement))
48            } else {
49                null
50            }
51        }
52    }
53            .filterNotNull()
54            .groupBy { MoreElements.asType(it.method.enclosingElement) }
55            .mapValues { entry -> LifecycleObserverInfo(entry.key, entry.value) }
56
57}
58
59class Validator(val processingEnv: ProcessingEnvironment) {
60
61    fun printErrorMessage(msg: CharSequence, elem: Element) {
62        processingEnv.messager.printMessage(Diagnostic.Kind.ERROR, msg, elem)
63    }
64
65    fun validateParam(param: VariableElement,
66                      expectedType: Class<*>, errorMsg: String): Boolean {
67        if (!MoreTypes.isTypeOf(expectedType, param.asType())) {
68            printErrorMessage(errorMsg, param)
69            return false
70        }
71        return true
72    }
73
74    fun validateMethod(method: ExecutableElement, event: Lifecycle.Event): Boolean {
75        if (Modifier.PRIVATE in method.modifiers) {
76            printErrorMessage(ErrorMessages.INVALID_METHOD_MODIFIER, method)
77            return false
78        }
79        val params = method.parameters
80        if ((params.size > 2)) {
81            printErrorMessage(ErrorMessages.TOO_MANY_ARGS, method)
82            return false
83        }
84
85        if (params.size == 2 && event != Lifecycle.Event.ON_ANY) {
86            printErrorMessage(ErrorMessages.TOO_MANY_ARGS_NOT_ON_ANY, method)
87            return false
88        }
89
90        if (params.size == 2 && !validateParam(params[1], Lifecycle.Event::class.java,
91                ErrorMessages.INVALID_SECOND_ARGUMENT)) {
92            return false
93        }
94
95        if (params.size > 0) {
96            return validateParam(params[0], LifecycleOwner::class.java,
97                    ErrorMessages.INVALID_FIRST_ARGUMENT)
98        }
99        return true
100    }
101
102    fun validateClass(classElement: Element): Boolean {
103        if (classElement.kind != ElementKind.CLASS && classElement.kind != ElementKind.INTERFACE) {
104            printErrorMessage(ErrorMessages.INVALID_ENCLOSING_ELEMENT, classElement)
105            return false
106        }
107        if (Modifier.PRIVATE in classElement.modifiers) {
108            printErrorMessage(ErrorMessages.INVALID_CLASS_MODIFIER, classElement)
109            return false
110        }
111        return true
112    }
113}
114