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 = ParameterSpec.builder(
49 ClassName.get(LifecycleOwner::class.java), "owner").build()
50 private val EVENT_PARAM: ParameterSpec = ParameterSpec.builder(
51 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 = ParameterSpec.builder(
55 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 = FieldSpec.builder(ClassName.get(adapter.type), "mReceiver",
61 Modifier.FINAL).build()
62 val dispatchMethodBuilder = MethodSpec.methodBuilder("callMethods")
63 .returns(TypeName.VOID)
64 .addParameter(OWNER_PARAM)
65 .addParameter(EVENT_PARAM)
66 .addParameter(ON_ANY_PARAM)
67 .addParameter(METHODS_LOGGER)
68 .addModifiers(Modifier.PUBLIC)
69 .addAnnotation(Override::class.java)
70 val dispatchMethod = dispatchMethodBuilder.apply {
71
72 addStatement("boolean $L = $N != null", HAS_LOGGER_VAR, METHODS_LOGGER)
73 val callsByEventType = adapter.calls.groupBy { it.method.onLifecycleEvent.value }
74 beginControlFlow("if ($N)", ON_ANY_PARAM).apply {
75 writeMethodCalls(callsByEventType[Lifecycle.Event.ON_ANY] ?: emptyList(), receiverField)
76 }.endControlFlow()
77
78 callsByEventType
79 .filterKeys { key -> key != Lifecycle.Event.ON_ANY }
80 .forEach { (event, calls) ->
81 beginControlFlow("if ($N == $T.$L)", EVENT_PARAM, LIFECYCLE_EVENT, event)
82 writeMethodCalls(calls, receiverField)
83 endControlFlow()
84 }
85 }.build()
86
87 val receiverParam = ParameterSpec.builder(
88 ClassName.get(adapter.type), "receiver").build()
89
90 val syntheticMethods = adapter.syntheticMethods.map {
91 val method = MethodSpec.methodBuilder(syntheticName(it))
92 .returns(TypeName.VOID)
93 .addModifiers(Modifier.PUBLIC)
94 .addModifiers(Modifier.STATIC)
95 .addParameter(receiverParam)
96 if (it.parameters.size >= 1) {
97 method.addParameter(OWNER_PARAM)
98 }
99 if (it.parameters.size == 2) {
100 method.addParameter(EVENT_PARAM)
101 }
102
103 val count = it.parameters.size
104 val paramString = generateParamString(count)
105 method.addStatement("$N.$L($paramString)", receiverParam, it.name(),
106 *takeParams(count, OWNER_PARAM, EVENT_PARAM))
107 method.build()
108 }
109
110 val constructor = MethodSpec.constructorBuilder()
111 .addParameter(receiverParam)
112 .addStatement("this.$N = $N", receiverField, receiverParam)
113 .build()
114
115 val adapterName = getAdapterName(adapter.type)
116 val adapterTypeSpecBuilder = TypeSpec.classBuilder(adapterName)
117 .addModifiers(Modifier.PUBLIC)
118 .addSuperinterface(ClassName.get(GeneratedAdapter::class.java))
119 .addField(receiverField)
120 .addMethod(constructor)
121 .addMethod(dispatchMethod)
122 .addMethods(syntheticMethods)
123
124 addGeneratedAnnotationIfAvailable(adapterTypeSpecBuilder, processingEnv)
125
126 JavaFile.builder(adapter.type.getPackageQName(), adapterTypeSpecBuilder.build())
127 .build().writeTo(processingEnv.filer)
128
129 generateKeepRule(adapter.type, processingEnv)
130 }
131
addGeneratedAnnotationIfAvailablenull132 private fun addGeneratedAnnotationIfAvailable(adapterTypeSpecBuilder: TypeSpec.Builder,
133 processingEnv: ProcessingEnvironment) {
134 val generatedAnnotationAvailable = processingEnv
135 .elementUtils
136 .getTypeElement(GENERATED_PACKAGE + "." + GENERATED_NAME) != null
137 if (generatedAnnotationAvailable) {
138 val generatedAnnotationSpec =
139 AnnotationSpec.builder(ClassName.get(GENERATED_PACKAGE, GENERATED_NAME)).addMember(
140 "value",
141 S,
142 LifecycleProcessor::class.java.canonicalName).build()
143 adapterTypeSpecBuilder.addAnnotation(generatedAnnotationSpec)
144 }
145 }
146
generateKeepRulenull147 private fun generateKeepRule(type: TypeElement, processingEnv: ProcessingEnvironment) {
148 val adapterClass = type.getPackageQName() + "." + getAdapterName(type)
149 val observerClass = type.toString()
150 val keepRule = """# Generated keep rule for Lifecycle observer adapter.
151 |-if class $observerClass {
152 | <init>(...);
153 |}
154 |-keep class $adapterClass {
155 | <init>(...);
156 |}
157 |""".trimMargin()
158
159 // Write the keep rule to the META-INF/proguard directory of the Jar file. The file name
160 // contains the fully qualified observer name so that file names are unique. This will allow any
161 // jar file merging to not overwrite keep rule files.
162 val path = "META-INF/proguard/$observerClass.pro"
163 val out = processingEnv.filer.createResource(StandardLocation.CLASS_OUTPUT, "", path)
164 out.openWriter().use { it.write(keepRule) }
165 }
166
writeMethodCallsnull167 private fun MethodSpec.Builder.writeMethodCalls(calls: List<EventMethodCall>,
168 receiverField: FieldSpec) {
169 calls.forEach { (method, syntheticAccess) ->
170 val count = method.method.parameters.size
171 val callType = 1 shl count
172 val methodName = method.method.name()
173 beginControlFlow("if (!$L || $N.approveCall($S, $callType))",
174 HAS_LOGGER_VAR, METHODS_LOGGER, methodName).apply {
175
176 if (syntheticAccess == null) {
177 val paramString = generateParamString(count)
178 addStatement("$N.$L($paramString)", receiverField,
179 methodName,
180 *takeParams(count, OWNER_PARAM, EVENT_PARAM))
181 } else {
182 val originalType = syntheticAccess
183 val paramString = generateParamString(count + 1)
184 val className = ClassName.get(originalType.getPackageQName(),
185 getAdapterName(originalType))
186 addStatement("$T.$L($paramString)", className,
187 syntheticName(method.method),
188 *takeParams(count + 1, receiverField, OWNER_PARAM, EVENT_PARAM))
189 }
190 }.endControlFlow()
191 }
192 addStatement("return")
193 }
194
takeParamsnull195 private fun takeParams(count: Int, vararg params: Any) = params.take(count).toTypedArray()
196
197 private fun generateParamString(count: Int) = (0 until count).joinToString(",") { N }
198