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.lifecycle
18 
19 import androidx.lifecycle.model.AdapterClass
20 import androidx.lifecycle.model.EventMethodCall
21 import androidx.lifecycle.model.getAdapterName
22 import com.squareup.javapoet.AnnotationSpec
23 import com.squareup.javapoet.ClassName
24 import com.squareup.javapoet.FieldSpec
25 import com.squareup.javapoet.JavaFile
26 import com.squareup.javapoet.MethodSpec
27 import com.squareup.javapoet.ParameterSpec
28 import com.squareup.javapoet.TypeName
29 import com.squareup.javapoet.TypeSpec
30 import javax.annotation.processing.ProcessingEnvironment
31 import javax.lang.model.element.Modifier
32 import javax.lang.model.element.TypeElement
33 import javax.tools.StandardLocation
34 
35 fun writeModels(infos: List<AdapterClass>, processingEnv: ProcessingEnvironment) {
36     infos.forEach({ writeAdapter(it, processingEnv) })
37 }
38 
39 private val GENERATED_PACKAGE = "javax.annotation"
40 private val GENERATED_NAME = "Generated"
41 private val LIFECYCLE_EVENT = Lifecycle.Event::class.java
42 
43 private val T = "\$T"
44 private val N = "\$N"
45 private val L = "\$L"
46 private val S = "\$S"
47 
48 private val OWNER_PARAM: ParameterSpec =
49     ParameterSpec.builder(ClassName.get(LifecycleOwner::class.java), "owner").build()
50 private val EVENT_PARAM: ParameterSpec =
51     ParameterSpec.builder(ClassName.get(LIFECYCLE_EVENT), "event").build()
52 private val ON_ANY_PARAM: ParameterSpec = ParameterSpec.builder(TypeName.BOOLEAN, "onAny").build()
53 
54 private val METHODS_LOGGER: ParameterSpec =
55     ParameterSpec.builder(ClassName.get(MethodCallsLogger::class.java), "logger").build()
56 
57 private const val HAS_LOGGER_VAR = "hasLogger"
58 
writeAdapternull59 private fun writeAdapter(adapter: AdapterClass, processingEnv: ProcessingEnvironment) {
60     val receiverField: FieldSpec =
61         FieldSpec.builder(ClassName.get(adapter.type), "mReceiver", Modifier.FINAL).build()
62     val dispatchMethodBuilder =
63         MethodSpec.methodBuilder("callMethods")
64             .returns(TypeName.VOID)
65             .addParameter(OWNER_PARAM)
66             .addParameter(EVENT_PARAM)
67             .addParameter(ON_ANY_PARAM)
68             .addParameter(METHODS_LOGGER)
69             .addModifiers(Modifier.PUBLIC)
70             .addAnnotation(Override::class.java)
71     val dispatchMethod =
72         dispatchMethodBuilder
73             .apply {
74                 addStatement("boolean $L = $N != null", HAS_LOGGER_VAR, METHODS_LOGGER)
75                 val callsByEventType = adapter.calls.groupBy { it.method.onLifecycleEvent.value }
76                 beginControlFlow("if ($N)", ON_ANY_PARAM)
77                     .apply {
78                         writeMethodCalls(
79                             callsByEventType[Lifecycle.Event.ON_ANY] ?: emptyList(),
80                             receiverField
81                         )
82                     }
83                     .endControlFlow()
84 
85                 callsByEventType
86                     .filterKeys { key -> key != Lifecycle.Event.ON_ANY }
87                     .forEach { (event, calls) ->
88                         beginControlFlow("if ($N == $T.$L)", EVENT_PARAM, LIFECYCLE_EVENT, event)
89                         writeMethodCalls(calls, receiverField)
90                         endControlFlow()
91                     }
92             }
93             .build()
94 
95     val receiverParam = ParameterSpec.builder(ClassName.get(adapter.type), "receiver").build()
96 
97     val syntheticMethods =
98         adapter.syntheticMethods.map {
99             val method =
100                 MethodSpec.methodBuilder(syntheticName(it))
101                     .returns(TypeName.VOID)
102                     .addModifiers(Modifier.PUBLIC)
103                     .addModifiers(Modifier.STATIC)
104                     .addParameter(receiverParam)
105             if (it.parameters.size >= 1) {
106                 method.addParameter(OWNER_PARAM)
107             }
108             if (it.parameters.size == 2) {
109                 method.addParameter(EVENT_PARAM)
110             }
111 
112             val count = it.parameters.size
113             val paramString = generateParamString(count)
114             method.addStatement(
115                 "$N.$L($paramString)",
116                 receiverParam,
117                 it.name(),
118                 *takeParams(count, OWNER_PARAM, EVENT_PARAM)
119             )
120             method.build()
121         }
122 
123     val constructor =
124         MethodSpec.constructorBuilder()
125             .addParameter(receiverParam)
126             .addStatement("this.$N = $N", receiverField, receiverParam)
127             .build()
128 
129     val adapterName = getAdapterName(adapter.type)
130     val adapterTypeSpecBuilder =
131         TypeSpec.classBuilder(adapterName)
132             .addModifiers(Modifier.PUBLIC)
133             .addSuperinterface(ClassName.get(GeneratedAdapter::class.java))
134             .addField(receiverField)
135             .addMethod(constructor)
136             .addMethod(dispatchMethod)
137             .addMethods(syntheticMethods)
138             .addOriginatingElement(adapter.type)
139 
140     addGeneratedAnnotationIfAvailable(adapterTypeSpecBuilder, processingEnv)
141 
142     JavaFile.builder(adapter.type.getPackageQName(), adapterTypeSpecBuilder.build())
143         .build()
144         .writeTo(processingEnv.filer)
145 
146     generateKeepRule(adapter.type, processingEnv)
147 }
148 
addGeneratedAnnotationIfAvailablenull149 private fun addGeneratedAnnotationIfAvailable(
150     adapterTypeSpecBuilder: TypeSpec.Builder,
151     processingEnv: ProcessingEnvironment
152 ) {
153     val generatedAnnotationAvailable =
154         processingEnv.elementUtils.getTypeElement(GENERATED_PACKAGE + "." + GENERATED_NAME) != null
155     if (generatedAnnotationAvailable) {
156         val generatedAnnotationSpec =
157             AnnotationSpec.builder(ClassName.get(GENERATED_PACKAGE, GENERATED_NAME))
158                 .addMember("value", S, LifecycleProcessor::class.java.canonicalName)
159                 .build()
160         adapterTypeSpecBuilder.addAnnotation(generatedAnnotationSpec)
161     }
162 }
163 
generateKeepRulenull164 private fun generateKeepRule(type: TypeElement, processingEnv: ProcessingEnvironment) {
165     val adapterClass = type.getPackageQName() + "." + getAdapterName(type)
166     val observerClass = type.toString()
167     val keepRule =
168         """# Generated keep rule for Lifecycle observer adapter.
169         |-if class $observerClass {
170         |    <init>(...);
171         |}
172         |-keep class $adapterClass {
173         |    <init>(...);
174         |}
175         |"""
176             .trimMargin()
177 
178     // Write the keep rule to the META-INF/proguard directory of the Jar file. The file name
179     // contains the fully qualified observer name so that file names are unique. This will allow any
180     // jar file merging to not overwrite keep rule files.
181     val path = "META-INF/proguard/$observerClass.pro"
182     val out = processingEnv.filer.createResource(StandardLocation.CLASS_OUTPUT, "", path, type)
183     out.openWriter().use { it.write(keepRule) }
184 }
185 
writeMethodCallsnull186 private fun MethodSpec.Builder.writeMethodCalls(
187     calls: List<EventMethodCall>,
188     receiverField: FieldSpec
189 ) {
190     calls.forEach { (method, syntheticAccess) ->
191         val count = method.method.parameters.size
192         val callType = 1 shl count
193         val methodName = method.method.name()
194         beginControlFlow(
195                 "if (!$L || $N.approveCall($S, $callType))",
196                 HAS_LOGGER_VAR,
197                 METHODS_LOGGER,
198                 methodName
199             )
200             .apply {
201                 if (syntheticAccess == null) {
202                     val paramString = generateParamString(count)
203                     addStatement(
204                         "$N.$L($paramString)",
205                         receiverField,
206                         methodName,
207                         *takeParams(count, OWNER_PARAM, EVENT_PARAM)
208                     )
209                 } else {
210                     val originalType = syntheticAccess
211                     val paramString = generateParamString(count + 1)
212                     val className =
213                         ClassName.get(originalType.getPackageQName(), getAdapterName(originalType))
214                     addStatement(
215                         "$T.$L($paramString)",
216                         className,
217                         syntheticName(method.method),
218                         *takeParams(count + 1, receiverField, OWNER_PARAM, EVENT_PARAM)
219                     )
220                 }
221             }
222             .endControlFlow()
223     }
224     addStatement("return")
225 }
226 
takeParamsnull227 private fun takeParams(count: Int, vararg params: Any) = params.take(count).toTypedArray()
228 
229 private fun generateParamString(count: Int) = (0 until count).joinToString(",") { N }
230