1 /*
<lambda>null2  * Copyright 2024 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.metadata
18 
19 import android.annotation.SuppressLint
20 import androidx.annotation.IntDef
21 import androidx.annotation.RestrictTo
22 import androidx.appsearch.annotation.Document
23 
24 @IntDef(
25     AppFunctionDataTypeMetadata.TYPE_UNIT,
26     AppFunctionDataTypeMetadata.TYPE_BOOLEAN,
27     AppFunctionDataTypeMetadata.TYPE_BYTES,
28     AppFunctionDataTypeMetadata.TYPE_OBJECT,
29     AppFunctionDataTypeMetadata.TYPE_DOUBLE,
30     AppFunctionDataTypeMetadata.TYPE_FLOAT,
31     AppFunctionDataTypeMetadata.TYPE_LONG,
32     AppFunctionDataTypeMetadata.TYPE_INT,
33     AppFunctionDataTypeMetadata.TYPE_STRING,
34     AppFunctionDataTypeMetadata.TYPE_ARRAY,
35     AppFunctionDataTypeMetadata.TYPE_REFERENCE,
36     AppFunctionDataTypeMetadata.TYPE_ALL_OF,
37     AppFunctionDataTypeMetadata.TYPE_PENDING_INTENT,
38 )
39 @Retention(AnnotationRetention.SOURCE)
40 internal annotation class AppFunctionDataType
41 
42 @IntDef(
43     AppFunctionDataTypeMetadata.TYPE_UNIT,
44     AppFunctionDataTypeMetadata.TYPE_BOOLEAN,
45     AppFunctionDataTypeMetadata.TYPE_BYTES,
46     AppFunctionDataTypeMetadata.TYPE_DOUBLE,
47     AppFunctionDataTypeMetadata.TYPE_FLOAT,
48     AppFunctionDataTypeMetadata.TYPE_LONG,
49     AppFunctionDataTypeMetadata.TYPE_INT,
50     AppFunctionDataTypeMetadata.TYPE_STRING,
51     AppFunctionDataTypeMetadata.TYPE_PENDING_INTENT,
52 )
53 @Retention(AnnotationRetention.SOURCE)
54 internal annotation class AppFunctionPrimitiveType
55 
56 /** Base class for defining the schema of an input or output type. */
57 public abstract class AppFunctionDataTypeMetadata
58 internal constructor(
59     /** Whether the data type is nullable. */
60     public val isNullable: Boolean,
61 ) {
62     /** Converts this [AppFunctionDataTypeMetadata] to an [AppFunctionDataTypeMetadataDocument]. */
63     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
64     public abstract fun toAppFunctionDataTypeMetadataDocument(): AppFunctionDataTypeMetadataDocument
65 
66     public companion object {
67         /** Void type. */
68         internal const val TYPE_UNIT: Int = 0
69         /** Boolean type. */
70         internal const val TYPE_BOOLEAN: Int = 1
71         /** Byte array type. */
72         internal const val TYPE_BYTES: Int = 2
73         /**
74          * Object type. The schema of the object is defined in a [AppFunctionObjectTypeMetadata].
75          */
76         internal const val TYPE_OBJECT: Int = 3
77         /** Double type. */
78         internal const val TYPE_DOUBLE: Int = 4
79         /** Float type. */
80         internal const val TYPE_FLOAT: Int = 5
81         /** Long type. */
82         internal const val TYPE_LONG: Int = 6
83         /** Integer type. */
84         internal const val TYPE_INT: Int = 7
85         /** String type. */
86         internal const val TYPE_STRING: Int = 8
87         /** Array type. The schema of the array is defined in a [AppFunctionArrayTypeMetadata] */
88         internal const val TYPE_ARRAY: Int = 10
89         /**
90          * Reference type. The schema of the reference is defined in a
91          * [AppFunctionReferenceTypeMetadata]
92          */
93         internal const val TYPE_REFERENCE: Int = 11
94         /**
95          * All of type. The schema of the all of type is defined in a [AppFunctionAllOfTypeMetadata]
96          */
97         internal const val TYPE_ALL_OF: Int = 12
98         /** Pending Intent type. */
99         internal const val TYPE_PENDING_INTENT: Int = 13
100 
101         /** All primitive types used in [AppFunctionPrimitiveType] @IntDef annotation. */
102         internal val PRIMITIVE_TYPES =
103             setOf(
104                 TYPE_UNIT,
105                 TYPE_BOOLEAN,
106                 TYPE_BYTES,
107                 TYPE_DOUBLE,
108                 TYPE_FLOAT,
109                 TYPE_LONG,
110                 TYPE_INT,
111                 TYPE_STRING,
112                 TYPE_PENDING_INTENT
113             )
114     }
115 
116     override fun equals(other: Any?): Boolean {
117         if (this === other) return true
118         if (javaClass != other?.javaClass) return false
119 
120         other as AppFunctionDataTypeMetadata
121 
122         return isNullable == other.isNullable
123     }
124 
125     override fun hashCode(): Int {
126         return isNullable.hashCode()
127     }
128 
129     override fun toString(): String {
130         return "AppFunctionDataTypeMetadata(isNullable=$isNullable)"
131     }
132 }
133 
134 /** Defines the schema of an array data type. */
135 public class AppFunctionArrayTypeMetadata(
136     /** The type of items in the array. */
137     public val itemType: AppFunctionDataTypeMetadata,
138     /** Whether this data type is nullable. */
139     isNullable: Boolean,
140 ) : AppFunctionDataTypeMetadata(isNullable = isNullable) {
equalsnull141     override fun equals(other: Any?): Boolean {
142         if (!super.equals(other)) return false
143         if (other !is AppFunctionArrayTypeMetadata) return false
144         return itemType == other.itemType
145     }
146 
hashCodenull147     override fun hashCode(): Int {
148         var result = super.hashCode()
149         result = 31 * result + itemType.hashCode()
150         return result
151     }
152 
toStringnull153     override fun toString(): String {
154         return "AppFunctionArrayTypeMetadataDocument(" +
155             "itemType=$itemType, " +
156             "isNullable=$isNullable" +
157             ")"
158     }
159 
160     /** Converts this [AppFunctionArrayTypeMetadata] to an [AppFunctionDataTypeMetadataDocument]. */
161     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
toAppFunctionDataTypeMetadataDocumentnull162     override fun toAppFunctionDataTypeMetadataDocument(): AppFunctionDataTypeMetadataDocument {
163         return AppFunctionDataTypeMetadataDocument(
164             itemType = itemType.toAppFunctionDataTypeMetadataDocument(),
165             type = TYPE,
166             isNullable = isNullable,
167         )
168     }
169 
170     public companion object {
171         /** Array type. The schema of the array is defined in a [AppFunctionArrayTypeMetadata] */
172         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public const val TYPE: Int = TYPE_ARRAY
173     }
174 }
175 
176 /**
177  * Defines the schema of a single object data type that is a composition of all of the other types
178  * in the [matchAll] list.
179  *
180  * An object of this type must match all of the [AppFunctionDataTypeMetadata] in the [matchAll]
181  * list. [matchAll] takes an array of object definitions that are composed together to a single
182  * object to form this type. Note that while this composition offers object type extensibility, it
183  * does not imply a hierarchy between the objects matched in [matchAll] i.e. the resulting single
184  * object is a flattened representation of all the other matched objects.
185  *
186  * For example, consider the following objects:
187  * ```
188  * open class Address (
189  *     open val street: String,
190  *     open val city: String,
191  *     open val state: String,
192  *     open val zipCode: String,
193  * )
194  *
195  * class PersonWithAddress (
196  *     override val street: String,
197  *     override val city: String,
198  *     override val state: String,
199  *     override val zipCode: String,
200  *     val name: String,
201  *     val age: Int,
202  * ) : Address(street, city, state, zipCode)
203  * ```
204  *
205  * The following [AppFunctionAllOfTypeMetadata] can be used to define a data type that matches
206  * PersonWithAddress.
207  *
208  * ```
209  * val personWithAddressType = AppFunctionAllOfTypeMetadata(
210  *     qualifiedName = "androidx.appfunctions.metadata.PersonWithAddress",
211  *     matchAll = listOf(
212  *         AppFunctionObjectTypeMetadata(
213  *             properties = mapOf(
214  *                 "street" to AppFunctionPrimitiveTypeMetadata(...),
215  *                 "city" to AppFunctionPrimitiveTypeMetadata(...),
216  *                 "state" to AppFunctionPrimitiveTypeMetadata(...),
217  *                 "zipCode" to AppFunctionPrimitiveTypeMetadata(...),
218  *             ),
219  *             required = listOf("street", "city", "state", "zipCode"),
220  *             qualifiedName = "androidx.appfunctions.metadata.Address",
221  *             isNullable = false,
222  *         ),
223  *         AppFunctionObjectTypeMetadata(
224  *             properties = mapOf(
225  *                 "name" to AppFunctionPrimitiveTypeMetadata(...),
226  *                 "age" to AppFunctionPrimitiveTypeMetadata(...),
227  *             ),
228  *             required = listOf("name", "age"),
229  *             qualifiedName = "androidx.appfunctions.metadata.PersonWithAddress",
230  *             isNullable = false,
231  *         ),
232  *     ),
233  *     isNullable = false,
234  * )
235  * ```
236  *
237  * This data type can be used to define the schema of an input or output type.
238  */
239 public class AppFunctionAllOfTypeMetadata(
240     /** The list of data types that are composed. */
241     public val matchAll: List<AppFunctionDataTypeMetadata>,
242     /**
243      * The composed object's qualified name if available. For example,
244      * "androidx.appfunctions.metadata.PersonWithAddress".
245      *
246      * Use this value to set [androidx.appfunctions.AppFunctionData.qualifiedName] when trying to
247      * build the parameters for [androidx.appfunctions.ExecuteAppFunctionRequest].
248      */
249     public val qualifiedName: String?,
250 
251     /** Whether this data type is nullable. */
252     isNullable: Boolean,
253 ) : AppFunctionDataTypeMetadata(isNullable = isNullable) {
254 
equalsnull255     override fun equals(other: Any?): Boolean {
256         if (!super.equals(other)) return false
257         if (other !is AppFunctionAllOfTypeMetadata) return false
258         if (qualifiedName != other.qualifiedName) return false
259         return matchAll == other.matchAll
260     }
261 
hashCodenull262     override fun hashCode(): Int {
263         var result = super.hashCode()
264         result = 31 * result + matchAll.hashCode()
265         if (qualifiedName != null) {
266             result = 31 * result + qualifiedName.hashCode()
267         }
268         return result
269     }
270 
toStringnull271     override fun toString(): String {
272         return "AppFunctionAllOfTypeMetadata(matchAll=$matchAll, isNullable=$isNullable)"
273     }
274 
275     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
toAppFunctionDataTypeMetadataDocumentnull276     override fun toAppFunctionDataTypeMetadataDocument(): AppFunctionDataTypeMetadataDocument {
277         val allOfDocuments = matchAll.map { it.toAppFunctionDataTypeMetadataDocument() }
278         return AppFunctionDataTypeMetadataDocument(
279             type = TYPE,
280             allOf = allOfDocuments,
281             isNullable = isNullable,
282             objectQualifiedName = qualifiedName
283         )
284     }
285 
286     public companion object {
287         /**
288          * All Of type.
289          *
290          * The [AppFunctionAllOfTypeMetadata] is used to define a component only data type object
291          * that is a composition of all of the types in the list.
292          *
293          * The [AppFunctionAllOfTypeMetadata] can contain either:
294          * * Top level [AppFunctionObjectTypeMetadata]
295          * * An [AppFunctionReferenceTypeMetadata] to an outer object metadata.
296          */
297         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public const val TYPE: Int = TYPE_ALL_OF
298     }
299 }
300 
301 /** Defines the schema of a object type. */
302 public class AppFunctionObjectTypeMetadata(
303     /** The schema of the properties of the object. */
304     public val properties: Map<String, AppFunctionDataTypeMetadata>,
305     /** A list of required properties' names. */
306     public val required: List<String>,
307     /**
308      * The object's qualified name if available.
309      *
310      * Use this value to set [androidx.appfunctions.AppFunctionData.qualifiedName] when trying to
311      * build the parameters for [androidx.appfunctions.ExecuteAppFunctionRequest].
312      */
313     public val qualifiedName: String?,
314     /** Whether this data type is nullable. */
315     isNullable: Boolean,
316 ) : AppFunctionDataTypeMetadata(isNullable = isNullable) {
equalsnull317     override fun equals(other: Any?): Boolean {
318         if (!super.equals(other)) return false
319         if (other !is AppFunctionObjectTypeMetadata) return false
320 
321         if (properties != other.properties) return false
322         if (required != other.required) return false
323         if (qualifiedName != other.qualifiedName) return false
324 
325         return true
326     }
327 
hashCodenull328     override fun hashCode(): Int {
329         var result = super.hashCode()
330         result = 31 * result + properties.hashCode()
331         result = 31 * result + required.hashCode()
332         if (qualifiedName != null) {
333             result = 31 * result + qualifiedName.hashCode()
334         }
335         return result
336     }
337 
toStringnull338     override fun toString(): String {
339         return "AppFunctionObjectTypeMetadata(" +
340             "properties=$properties, " +
341             "required=$required, " +
342             "qualifiedName=$qualifiedName, " +
343             "isNullable=$isNullable" +
344             ")"
345     }
346 
347     /**
348      * Converts this [AppFunctionObjectTypeMetadata] to an [AppFunctionDataTypeMetadataDocument].
349      */
350     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
toAppFunctionDataTypeMetadataDocumentnull351     override fun toAppFunctionDataTypeMetadataDocument(): AppFunctionDataTypeMetadataDocument {
352         val properties =
353             properties.map { (name, dataType) ->
354                 AppFunctionNamedDataTypeMetadataDocument(
355                     name = checkNotNull(name),
356                     dataTypeMetadata = dataType.toAppFunctionDataTypeMetadataDocument()
357                 )
358             }
359         return AppFunctionDataTypeMetadataDocument(
360             type = TYPE,
361             properties = properties,
362             required = required,
363             objectQualifiedName = qualifiedName,
364             isNullable = isNullable,
365         )
366     }
367 
368     public companion object {
369         /**
370          * Object type. The schema of the object is defined in a [AppFunctionObjectTypeMetadata].
371          */
372         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public const val TYPE: Int = TYPE_OBJECT
373     }
374 }
375 
376 /**
377  * Represents a type that reference a data type that is defined in [AppFunctionComponentsMetadata].
378  */
379 public class AppFunctionReferenceTypeMetadata(
380     /** The string referencing a data type defined in [AppFunctionComponentsMetadata]. */
381     public val referenceDataType: String,
382     /** Whether the data type is nullable. */
383     isNullable: Boolean
384 ) : AppFunctionDataTypeMetadata(isNullable = isNullable) {
equalsnull385     override fun equals(other: Any?): Boolean {
386         if (!super.equals(other)) return false
387         if (other !is AppFunctionReferenceTypeMetadata) return false
388         return referenceDataType == other.referenceDataType
389     }
390 
hashCodenull391     override fun hashCode(): Int {
392         var result = super.hashCode()
393         result = 31 * result + referenceDataType.hashCode()
394         return result
395     }
396 
toStringnull397     override fun toString(): String {
398         return "AppFunctionReferenceTypeMetadata(" +
399             "referenceDataType=$referenceDataType, " +
400             "isNullable=$isNullable" +
401             ")"
402     }
403 
404     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
toAppFunctionDataTypeMetadataDocumentnull405     override fun toAppFunctionDataTypeMetadataDocument(): AppFunctionDataTypeMetadataDocument {
406         return AppFunctionDataTypeMetadataDocument(
407             type = TYPE,
408             dataTypeReference = referenceDataType,
409             isNullable = isNullable,
410         )
411     }
412 
413     public companion object {
414         /**
415          * Object type. The schema of the object is defined in a [AppFunctionObjectTypeMetadata].
416          */
417         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public const val TYPE: Int = TYPE_REFERENCE
418     }
419 }
420 
421 /** Defines the schema of a primitive data type. */
422 public class AppFunctionPrimitiveTypeMetadata(
423     @AppFunctionPrimitiveType public val type: Int,
424     isNullable: Boolean
425 ) : AppFunctionDataTypeMetadata(isNullable) {
equalsnull426     override fun equals(other: Any?): Boolean {
427         if (!super.equals(other)) return false
428         if (other !is AppFunctionPrimitiveTypeMetadata) return false
429 
430         if (type != other.type) return false
431 
432         return true
433     }
434 
hashCodenull435     override fun hashCode(): Int {
436         var result = super.hashCode()
437         result = 31 * result * type.hashCode()
438         return result
439     }
440 
toStringnull441     override fun toString(): String {
442         return "AppFunctionPrimitiveTypeMetadata(type=$type, isNullable=$isNullable)"
443     }
444 
445     /**
446      * Converts this [AppFunctionPrimitiveTypeMetadata] to an [AppFunctionDataTypeMetadataDocument].
447      */
448     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
toAppFunctionDataTypeMetadataDocumentnull449     override fun toAppFunctionDataTypeMetadataDocument(): AppFunctionDataTypeMetadataDocument {
450         return AppFunctionDataTypeMetadataDocument(type = type, isNullable = isNullable)
451     }
452 
453     public companion object {
454         /** Void type. */
455         public const val TYPE_UNIT: Int = AppFunctionDataTypeMetadata.TYPE_UNIT
456         /** Boolean type. */
457         public const val TYPE_BOOLEAN: Int = AppFunctionDataTypeMetadata.TYPE_BOOLEAN
458         /** Byte array type. */
459         public const val TYPE_BYTES: Int = AppFunctionDataTypeMetadata.TYPE_BYTES
460         /** Double type. */
461         public const val TYPE_DOUBLE: Int = AppFunctionDataTypeMetadata.TYPE_DOUBLE
462         /** Float type. */
463         public const val TYPE_FLOAT: Int = AppFunctionDataTypeMetadata.TYPE_FLOAT
464         /** Long type. */
465         public const val TYPE_LONG: Int = AppFunctionDataTypeMetadata.TYPE_LONG
466         /** Integer type. */
467         public const val TYPE_INT: Int = AppFunctionDataTypeMetadata.TYPE_INT
468         /** String type. */
469         public const val TYPE_STRING: Int = AppFunctionDataTypeMetadata.TYPE_STRING
470         /** Pending Intent type. */
471         public const val TYPE_PENDING_INTENT: Int = AppFunctionDataTypeMetadata.TYPE_PENDING_INTENT
472     }
473 }
474 
475 /** Represents the persistent storage format of the schema of a data type and its name. */
476 @Document
477 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
478 public data class AppFunctionNamedDataTypeMetadataDocument(
479     @Document.Namespace public val namespace: String = APP_FUNCTION_NAMESPACE,
480     /** The id of the data type. */
481     @Document.Id public val id: String = APP_FUNCTION_ID_EMPTY,
482     /** The name of the data type. */
483     @Document.StringProperty public val name: String,
484     /** The data type metadata. */
485     @Document.DocumentProperty public val dataTypeMetadata: AppFunctionDataTypeMetadataDocument,
486 )
487 
488 /** Represents the persistent storage format of [AppFunctionDataTypeMetadata]. */
489 @Document
490 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
491 public data class AppFunctionDataTypeMetadataDocument(
492     @Document.Namespace public val namespace: String = APP_FUNCTION_NAMESPACE,
493     /** The id of the data type. */
494     @Document.Id public val id: String = APP_FUNCTION_ID_EMPTY,
495     /** The data type. */
496     @Document.LongProperty @AppFunctionDataType public val type: Int,
497 
498     /**
499      * If the [type] is [AppFunctionDataTypeMetadata.TYPE_ARRAY], this specifies the array content
500      * data type.
501      */
502     @Document.DocumentProperty public val itemType: AppFunctionDataTypeMetadataDocument? = null,
503     /**
504      * If the [type] is [AppFunctionDataTypeMetadata.TYPE_OBJECT], this specified the object's
505      * properties.
506      */
507     @Document.DocumentProperty
508     public val properties: List<AppFunctionNamedDataTypeMetadataDocument> = emptyList(),
509 
510     /**
511      * If the [type] is [AppFunctionDataTypeMetadata.TYPE_ALL_OF], this specified the object's
512      * properties.
513      */
514     @Document.DocumentProperty
515     public val allOf: List<AppFunctionDataTypeMetadataDocument> = emptyList(),
516 
517     /**
518      * If the [type] is [AppFunctionDataTypeMetadata.TYPE_OBJECT], this specified the object's
519      * required properties' names.
520      */
521     @Document.StringProperty public val required: List<String> = emptyList(),
522     /**
523      * If the [type] is [AppFunctionDataTypeMetadata.TYPE_REFERENCE], this specified the reference.
524      */
525     @Document.StringProperty public val dataTypeReference: String? = null,
526     /** Whether the type is nullable. */
527     @Document.BooleanProperty public val isNullable: Boolean = false,
528     /**
529      * If the [type] is [AppFunctionDataTypeMetadata.TYPE_OBJECT], this specified the object's
530      * qualified name if available.
531      */
532     @Document.StringProperty public val objectQualifiedName: String? = null,
533 ) {
534     @SuppressLint(
535         // When doesn't handle @IntDef correctly.
536         "WrongConstant"
537     )
toAppFunctionDataTypeMetadatanull538     public fun toAppFunctionDataTypeMetadata(): AppFunctionDataTypeMetadata =
539         when (type) {
540             AppFunctionDataTypeMetadata.TYPE_ARRAY -> {
541                 val itemType = checkNotNull(itemType) { "Item type must be present for array type" }
542                 AppFunctionArrayTypeMetadata(
543                     itemType = itemType.toAppFunctionDataTypeMetadata(),
544                     isNullable = isNullable
545                 )
546             }
547             AppFunctionDataTypeMetadata.TYPE_OBJECT -> {
548                 check(properties.isNotEmpty()) {
549                     "Properties must be present for object type can't be empty"
550                 }
551                 val propertiesMap =
552                     properties.associate {
553                         it.name to it.dataTypeMetadata.toAppFunctionDataTypeMetadata()
554                     }
555                 AppFunctionObjectTypeMetadata(
556                     properties = propertiesMap,
557                     required = required,
558                     qualifiedName = objectQualifiedName,
559                     isNullable = isNullable
560                 )
561             }
562             AppFunctionDataTypeMetadata.TYPE_REFERENCE ->
563                 AppFunctionReferenceTypeMetadata(
564                     referenceDataType =
565                         checkNotNull(dataTypeReference) {
566                             "Data type reference must be present for reference type"
567                         },
568                     isNullable = isNullable
569                 )
570             AppFunctionDataTypeMetadata.TYPE_ALL_OF ->
571                 AppFunctionAllOfTypeMetadata(
572                     matchAll = allOf.map { it.toAppFunctionDataTypeMetadata() },
573                     qualifiedName = objectQualifiedName,
574                     isNullable = isNullable
575                 )
576             in AppFunctionDataTypeMetadata.PRIMITIVE_TYPES ->
577                 AppFunctionPrimitiveTypeMetadata(type = type, isNullable = isNullable)
578             else -> throw IllegalArgumentException("Unknown type: $type")
579         }
580 }
581