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