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