1 /*
<lambda>null2  * Copyright 2025 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.appfunctions.compiler.core
18 
19 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.PRIMITIVE_ARRAY
20 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.PRIMITIVE_LIST
21 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.PRIMITIVE_SINGULAR
22 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.SERIALIZABLE_LIST
23 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.SERIALIZABLE_PROXY_LIST
24 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.SERIALIZABLE_PROXY_SINGULAR
25 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.SERIALIZABLE_SINGULAR
26 import androidx.appfunctions.metadata.AppFunctionPrimitiveTypeMetadata
27 import com.google.devtools.ksp.symbol.KSTypeReference
28 import com.squareup.kotlinpoet.LIST
29 import com.squareup.kotlinpoet.TypeName
30 import java.time.LocalDateTime
31 
32 /** Represents a type that is supported by AppFunction and AppFunctionSerializable. */
33 class AppFunctionTypeReference(val selfTypeReference: KSTypeReference) {
34 
35     /**
36      * The category of this reference type.
37      *
38      * The category of a type is determined by its underlying type. For example, a type reference to
39      * a list of strings will have a category of PRIMITIVE_LIST.
40      */
41     val typeCategory: AppFunctionSupportedTypeCategory by lazy {
42         when {
43             selfTypeReference.asStringWithoutNullQualifier() in SUPPORTED_SINGLE_PRIMITIVE_TYPES ->
44                 PRIMITIVE_SINGULAR
45             selfTypeReference.asStringWithoutNullQualifier() in SUPPORTED_ARRAY_PRIMITIVE_TYPES ->
46                 PRIMITIVE_ARRAY
47             isAppFunctionSerializableProxyType(selfTypeReference) -> SERIALIZABLE_PROXY_SINGULAR
48             isSupportedPrimitiveListType(selfTypeReference) -> PRIMITIVE_LIST
49             isAppFunctionSerializableProxyListType(selfTypeReference) -> SERIALIZABLE_PROXY_LIST
50             isAppFunctionSerializableListType(selfTypeReference) -> SERIALIZABLE_LIST
51             isAppFunctionSerializableType(selfTypeReference) -> SERIALIZABLE_SINGULAR
52             else ->
53                 throw ProcessingException(
54                     "Unsupported type reference ${selfTypeReference.ensureQualifiedTypeName().asString()}",
55                     selfTypeReference,
56                 )
57         }
58     }
59 
60     /**
61      * If this type is nullable.
62      *
63      * @return true if the type is nullable, false otherwise.
64      */
65     val isNullable: Boolean by lazy { selfTypeReference.toTypeName().isNullable }
66 
67     /**
68      * The type reference of the list element if the type reference is a list.
69      *
70      * @return the type reference of the list element if the type reference is a list.
71      * @throws IllegalArgumentException if used for a non-list type.
72      */
73     val itemTypeReference: KSTypeReference by lazy { ->
74         require(selfTypeReference.isOfType(LIST)) { "Type reference is not a list" }
75         selfTypeReference.resolveListParameterizedType()
76     }
77 
78     /**
79      * The type reference itself or the type reference of the list element if the type reference is
80      * a list. For example, if the type reference is List<String>, then the selfOrItemTypeReference
81      * will be String.
82      */
83     val selfOrItemTypeReference: KSTypeReference by lazy {
84         if (selfTypeReference.isOfType(LIST)) {
85             itemTypeReference
86         } else {
87             selfTypeReference
88         }
89     }
90 
91     /**
92      * Checks if the type reference is of the given category.
93      *
94      * @param category The category to check.
95      * @return true if the type reference is of the given category, false otherwise.
96      */
97     fun isOfTypeCategory(category: AppFunctionSupportedTypeCategory): Boolean {
98         return this.typeCategory == category
99     }
100 
101     /**
102      * The category of types that are supported by app functions.
103      *
104      * The category of a type is determined by its underlying type. For example, a type reference to
105      * a list of strings will have a category of PRIMITIVE_LIST.
106      */
107     enum class AppFunctionSupportedTypeCategory {
108         PRIMITIVE_SINGULAR,
109         PRIMITIVE_ARRAY,
110         PRIMITIVE_LIST,
111         SERIALIZABLE_SINGULAR,
112         SERIALIZABLE_LIST,
113         SERIALIZABLE_PROXY_SINGULAR,
114         SERIALIZABLE_PROXY_LIST
115     }
116 
117     companion object {
118         /**
119          * Checks if the type reference is a supported type.
120          *
121          * A supported type is a primitive type, a type annotated as @AppFunctionSerializable, or a
122          * list of a supported type.
123          */
124         fun isSupportedType(typeReferenceArgument: KSTypeReference): Boolean {
125             return typeReferenceArgument.asStringWithoutNullQualifier() in SUPPORTED_TYPES ||
126                 isSupportedPrimitiveListType(typeReferenceArgument) ||
127                 isAppFunctionSerializableType(typeReferenceArgument) ||
128                 isAppFunctionSerializableListType(typeReferenceArgument) ||
129                 isAppFunctionSerializableProxyListType(typeReferenceArgument)
130         }
131 
132         /**
133          * Converts a type reference to an AppFunction data type.
134          *
135          * @return The AppFunction data type.
136          * @throws ProcessingException If the type reference is not a supported type.
137          */
138         fun KSTypeReference.toAppFunctionDatatype(): Int {
139             return when (this.toTypeName().ignoreNullable().toString()) {
140                 String::class.ensureQualifiedName() -> AppFunctionPrimitiveTypeMetadata.TYPE_STRING
141                 Int::class.ensureQualifiedName() -> AppFunctionPrimitiveTypeMetadata.TYPE_INT
142                 Long::class.ensureQualifiedName() -> AppFunctionPrimitiveTypeMetadata.TYPE_LONG
143                 Float::class.ensureQualifiedName() -> AppFunctionPrimitiveTypeMetadata.TYPE_FLOAT
144                 Double::class.ensureQualifiedName() -> AppFunctionPrimitiveTypeMetadata.TYPE_DOUBLE
145                 Boolean::class.ensureQualifiedName() ->
146                     AppFunctionPrimitiveTypeMetadata.TYPE_BOOLEAN
147                 Unit::class.ensureQualifiedName() -> AppFunctionPrimitiveTypeMetadata.TYPE_UNIT
148                 Byte::class.ensureQualifiedName() -> AppFunctionPrimitiveTypeMetadata.TYPE_BYTES
149                 IntArray::class.ensureQualifiedName() -> AppFunctionPrimitiveTypeMetadata.TYPE_INT
150                 LongArray::class.ensureQualifiedName() -> AppFunctionPrimitiveTypeMetadata.TYPE_LONG
151                 FloatArray::class.ensureQualifiedName() ->
152                     AppFunctionPrimitiveTypeMetadata.TYPE_FLOAT
153                 DoubleArray::class.ensureQualifiedName() ->
154                     AppFunctionPrimitiveTypeMetadata.TYPE_DOUBLE
155                 BooleanArray::class.ensureQualifiedName() ->
156                     AppFunctionPrimitiveTypeMetadata.TYPE_BOOLEAN
157                 ByteArray::class.ensureQualifiedName() ->
158                     AppFunctionPrimitiveTypeMetadata.TYPE_BYTES
159                 ANDROID_PENDING_INTENT -> AppFunctionPrimitiveTypeMetadata.TYPE_PENDING_INTENT
160                 else ->
161                     throw ProcessingException(
162                         "Unsupported type reference " + this.ensureQualifiedTypeName().asString(),
163                         this,
164                     )
165             }
166         }
167 
168         private fun isSupportedPrimitiveListType(typeReferenceArgument: KSTypeReference) =
169             typeReferenceArgument.isOfType(LIST) &&
170                 typeReferenceArgument
171                     .resolveListParameterizedType()
172                     .asStringWithoutNullQualifier() in SUPPORTED_PRIMITIVE_TYPES_IN_LIST
173 
174         private fun isAppFunctionSerializableListType(
175             typeReferenceArgument: KSTypeReference
176         ): Boolean {
177             return typeReferenceArgument.isOfType(LIST) &&
178                 isAppFunctionSerializableType(typeReferenceArgument.resolveListParameterizedType())
179         }
180 
181         private fun isAppFunctionSerializableType(typeReferenceArgument: KSTypeReference): Boolean {
182             return typeReferenceArgument
183                 .resolve()
184                 .declaration
185                 .annotations
186                 .findAnnotation(IntrospectionHelper.AppFunctionSerializableAnnotation.CLASS_NAME) !=
187                 null
188         }
189 
190         private fun isAppFunctionSerializableProxyListType(
191             typeReferenceArgument: KSTypeReference
192         ): Boolean {
193             return typeReferenceArgument.isOfType(LIST) &&
194                 isAppFunctionSerializableProxyType(
195                     typeReferenceArgument.resolveListParameterizedType()
196                 )
197         }
198 
199         private fun isAppFunctionSerializableProxyType(
200             typeReferenceArgument: KSTypeReference
201         ): Boolean {
202             return typeReferenceArgument.asStringWithoutNullQualifier() in
203                 SUPPORTED_SINGLE_SERIALIZABLE_PROXY_TYPES ||
204                 typeReferenceArgument
205                     .resolve()
206                     .declaration
207                     .annotations
208                     .findAnnotation(
209                         IntrospectionHelper.AppFunctionSerializableProxyAnnotation.CLASS_NAME
210                     ) != null
211         }
212 
213         private fun TypeName.ignoreNullable(): TypeName {
214             return copy(nullable = false)
215         }
216 
217         private fun KSTypeReference.asStringWithoutNullQualifier(): String =
218             toTypeName().ignoreNullable().toString()
219 
220         // Android Only primitives
221         private const val ANDROID_PENDING_INTENT = "android.app.PendingIntent"
222         private const val ANDROID_URI = "android.net.Uri"
223 
224         private val SUPPORTED_ARRAY_PRIMITIVE_TYPES =
225             setOf(
226                 IntArray::class.ensureQualifiedName(),
227                 LongArray::class.ensureQualifiedName(),
228                 FloatArray::class.ensureQualifiedName(),
229                 DoubleArray::class.ensureQualifiedName(),
230                 BooleanArray::class.ensureQualifiedName(),
231                 ByteArray::class.ensureQualifiedName(),
232             )
233 
234         private val SUPPORTED_SINGLE_PRIMITIVE_TYPES =
235             setOf(
236                 Int::class.ensureQualifiedName(),
237                 Long::class.ensureQualifiedName(),
238                 Float::class.ensureQualifiedName(),
239                 Double::class.ensureQualifiedName(),
240                 Boolean::class.ensureQualifiedName(),
241                 String::class.ensureQualifiedName(),
242                 Unit::class.ensureQualifiedName(),
243                 ANDROID_PENDING_INTENT
244             )
245 
246         private val SUPPORTED_SINGLE_SERIALIZABLE_PROXY_TYPES =
247             setOf(LocalDateTime::class.ensureQualifiedName(), ANDROID_URI)
248 
249         private val SUPPORTED_PRIMITIVE_TYPES_IN_LIST = setOf(String::class.ensureQualifiedName())
250 
251         private val SUPPORTED_TYPES =
252             SUPPORTED_SINGLE_PRIMITIVE_TYPES +
253                 SUPPORTED_ARRAY_PRIMITIVE_TYPES +
254                 SUPPORTED_SINGLE_SERIALIZABLE_PROXY_TYPES
255 
256         val SUPPORTED_TYPES_STRING: String =
257             SUPPORTED_TYPES.joinToString(",\n") +
258                 "\nLists of ${SUPPORTED_PRIMITIVE_TYPES_IN_LIST.joinToString(", ")}"
259     }
260 }
261