1 /*
<lambda>null2  * Copyright 2022 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 @file:JvmName("ComposableMethodKt")
18 
19 package androidx.compose.runtime.reflect
20 
21 import androidx.compose.runtime.Composer
22 import androidx.compose.runtime.internal.SLOTS_PER_INT
23 import java.lang.reflect.Method
24 import java.lang.reflect.Modifier
25 import java.lang.reflect.Parameter
26 import kotlin.math.ceil
27 
28 private const val BITS_PER_INT = 31
29 
30 private fun changedParamCount(realValueParams: Int, thisParams: Int): Int {
31     if (realValueParams == 0) return 1
32     val totalParams = realValueParams + thisParams
33     return ceil(totalParams.toDouble() / SLOTS_PER_INT.toDouble()).toInt()
34 }
35 
defaultParamCountnull36 private fun defaultParamCount(realValueParams: Int): Int {
37     return ceil(realValueParams.toDouble() / BITS_PER_INT.toDouble()).toInt()
38 }
39 
40 /** Structure intended to be used exclusively by [getComposableInfo]. */
41 internal data class ComposableInfo(
42     val isComposable: Boolean,
43     val realParamsCount: Int,
44     val changedParams: Int,
45     val defaultParams: Int
46 )
47 
48 /**
49  * Checks whether the method is Composable function and returns result along with the real
50  * parameters count and changed parameter count (if composable) and packed in a structure.
51  */
getComposableInfonull52 private fun Method.getComposableInfo(): ComposableInfo {
53     val realParamsCount = parameterTypes.indexOfLast { it == Composer::class.java }
54     if (realParamsCount == -1) {
55         return ComposableInfo(false, parameterTypes.size, 0, 0)
56     }
57     val thisParams = if (Modifier.isStatic(this.modifiers)) 0 else 1
58     val changedParams = changedParamCount(realParamsCount, thisParams)
59     val totalParamsWithoutDefaults =
60         realParamsCount +
61             1 + // composer
62             changedParams
63     val totalParams = parameterTypes.size
64     val isDefault = totalParams != totalParamsWithoutDefaults
65     val defaultParams = if (isDefault) defaultParamCount(realParamsCount) else 0
66     return ComposableInfo(
67         totalParamsWithoutDefaults + defaultParams == totalParams,
68         realParamsCount,
69         changedParams,
70         defaultParams
71     )
72 }
73 
74 /**
75  * Returns the default value for the [Class] type. This will be 0 for numeric types, false for
76  * boolean and null for object references.
77  */
Classnull78 private fun Class<*>.getDefaultValue(): Any? =
79     when (name) {
80         "int" -> 0.toInt()
81         "short" -> 0.toShort()
82         "byte" -> 0.toByte()
83         "long" -> 0.toLong()
84         "double" -> 0.toDouble()
85         "float" -> 0.toFloat()
86         "boolean" -> false
87         "char" -> 0.toChar()
88         else -> null
89     }
90 
91 /** Represents the @Composable method. */
92 class ComposableMethod
93 internal constructor(private val method: Method, private val composableInfo: ComposableInfo) {
94     /** Returns the backing [Method]. */
asMethodnull95     fun asMethod() = method
96 
97     /** Returns the count of method parameters excluding the utility Compose-specific parameters. */
98     val parameterCount
99         get() = composableInfo.realParamsCount
100 
101     /** Returns method parameters excluding the utility Compose-specific parameters. */
102     val parameters: Array<Parameter>
103         @Suppress("NewApi") get() = method.parameters.copyOfRange(0, composableInfo.realParamsCount)
104 
105     /** Returns method parameters types excluding the utility Compose-specific parameters. */
106     val parameterTypes: Array<Class<*>>
107         get() = method.parameterTypes.copyOfRange(0, composableInfo.realParamsCount)
108 
109     /**
110      * Calls the Composable method on the given [instance]. If the method accepts default values,
111      * this function will call it with the correct options set.
112      */
113     @Suppress("BanUncheckedReflection", "ListIterator")
114     operator fun invoke(composer: Composer, instance: Any?, vararg args: Any?): Any? {
115         val (_, realParamsCount, changedParams, defaultParams) = composableInfo
116 
117         val totalParams = method.parameterTypes.size
118         val changedStartIndex = realParamsCount + 1
119         val defaultStartIndex = changedStartIndex + changedParams
120 
121         val defaultsMasks =
122             Array(defaultParams) { index ->
123                 val start = index * BITS_PER_INT
124                 val end = minOf(start + BITS_PER_INT, realParamsCount)
125                 val useDefault =
126                     (start until end).map { if (it >= args.size || args[it] == null) 1 else 0 }
127                 val mask = useDefault.foldIndexed(0) { i, mask, default -> mask or (default shl i) }
128                 mask
129             }
130 
131         val arguments =
132             Array(totalParams) { idx ->
133                 when (idx) {
134                     // pass in "empty" value for all real parameters since we will be using
135                     // defaults.
136                     in 0 until realParamsCount ->
137                         args.getOrElse(idx) { method.parameterTypes[idx].getDefaultValue() }
138                     // the composer is the first synthetic parameter
139                     realParamsCount -> composer
140                     // since this is the root we don't need to be anything unique. 0 should suffice.
141                     // changed parameters should be 0 to indicate "uncertain"
142                     changedStartIndex -> 0
143                     in changedStartIndex + 1 until defaultStartIndex -> 0
144                     // Default values mask, all parameters set to use defaults
145                     in defaultStartIndex until totalParams -> defaultsMasks[idx - defaultStartIndex]
146                     else -> error("Unexpected index")
147                 }
148             }
149         return method.invoke(instance, *arguments)
150     }
151 
equalsnull152     override fun equals(other: Any?) =
153         when (other) {
154             is ComposableMethod -> method == other.method
155             else -> false
156         }
157 
hashCodenull158     override fun hashCode() = method.hashCode()
159 }
160 
161 fun Method.asComposableMethod(): ComposableMethod? {
162     val composableInfo = getComposableInfo()
163     if (composableInfo.isComposable) {
164         return ComposableMethod(this, composableInfo)
165     }
166     return null
167 }
168 
dupnull169 private inline fun <reified T> T.dup(count: Int): Array<T> {
170     return (0 until count).map { this }.toTypedArray()
171 }
172 
173 /** Find the given @Composable method by name. */
174 @Throws(NoSuchMethodException::class)
getDeclaredComposableMethodnull175 fun Class<*>.getDeclaredComposableMethod(
176     methodName: String,
177     vararg args: Class<*>
178 ): ComposableMethod {
179     val changedParams = changedParamCount(args.size, 0)
180     val method =
181         try {
182             // without defaults
183             getDeclaredMethod(
184                 methodName,
185                 *args,
186                 Composer::class.java, // composer param
187                 *Int::class.java.dup(changedParams) // changed params
188             )
189         } catch (e: ReflectiveOperationException) {
190             val defaultParams = defaultParamCount(args.size)
191             try {
192                 getDeclaredMethod(
193                     methodName,
194                     *args,
195                     Composer::class.java, // composer param
196                     *Int::class.java.dup(changedParams), // changed param
197                     *Int::class.java.dup(defaultParams) // default param
198                 )
199             } catch (e2: ReflectiveOperationException) {
200                 null
201             }
202         } ?: throw NoSuchMethodException("$name.$methodName")
203 
204     return method.asComposableMethod()!!
205 }
206