1 /*
2  * 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.Companion.SUPPORTED_TYPES_STRING
20 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.Companion.isSupportedType
21 import com.google.devtools.ksp.getDeclaredProperties
22 import com.google.devtools.ksp.getVisibility
23 import com.google.devtools.ksp.symbol.KSClassDeclaration
24 import com.google.devtools.ksp.symbol.Visibility
25 
26 /** A helper to validate an AppFunctionSerializable declaration. */
27 class AppFunctionSerializableValidateHelper(
28     private val annotatedSerializable: AnnotatedAppFunctionSerializable
29 ) {
30 
31     /** Validates if the primary constructor is valid. */
validatePrimaryConstructornull32     fun validatePrimaryConstructor() {
33         val primaryConstructor = annotatedSerializable.primaryConstructor
34         if (primaryConstructor == null) {
35             throw ProcessingException(
36                 "A valid AppFunctionSerializable must have a primary constructor.",
37                 annotatedSerializable.attributeNode
38             )
39         }
40         val primaryConstructorDeclaration = checkNotNull(primaryConstructor)
41         if (primaryConstructorDeclaration.parameters.isEmpty()) {
42             throw ProcessingException(
43                 "A valid AppFunctionSerializable must have a non-empty primary constructor.",
44                 annotatedSerializable.attributeNode
45             )
46         }
47 
48         if (primaryConstructorDeclaration.getVisibility() != Visibility.PUBLIC) {
49             throw ProcessingException(
50                 "A valid AppFunctionSerializable must have  a public primary constructor.",
51                 annotatedSerializable.attributeNode
52             )
53         }
54 
55         for (parameter in primaryConstructorDeclaration.parameters) {
56             if (!parameter.isVal) {
57                 throw ProcessingException(
58                     "All parameters in @AppFunctionSerializable primary constructor must have getters",
59                     parameter
60                 )
61             }
62         }
63     }
64 
65     /** Validate if the parameters are valid. */
validateParametersnull66     fun validateParameters() {
67         val parametersToValidate =
68             annotatedSerializable
69                 .getProperties()
70                 .associateBy { checkNotNull(it.name) }
71                 .toMutableMap()
72 
73         val superTypesWithSerializableAnnotation =
74             annotatedSerializable.findSuperTypesWithSerializableAnnotation()
75         val superTypesWithCapabilityAnnotation =
76             annotatedSerializable.findSuperTypesWithCapabilityAnnotation()
77         validateSuperTypes(superTypesWithSerializableAnnotation, superTypesWithCapabilityAnnotation)
78 
79         for (superType in superTypesWithSerializableAnnotation) {
80             val superTypeAnnotatedSerializable =
81                 AnnotatedAppFunctionSerializable(superType).validate()
82             for (superTypeProperty in superTypeAnnotatedSerializable.getProperties()) {
83                 // Parameter has now been visited
84                 val parameterInSuperType = parametersToValidate.remove(superTypeProperty.name)
85                 if (parameterInSuperType == null) {
86                     throw ProcessingException(
87                         "All parameters in @AppFunctionSerializable " +
88                             "supertypes must be present in subtype",
89                         superTypeProperty.type
90                     )
91                 }
92                 validateSerializableParameter(parameterInSuperType)
93             }
94         }
95 
96         for (superType in superTypesWithCapabilityAnnotation) {
97             val capabilityProperties = superType.getDeclaredProperties()
98 
99             for (superTypeProperty in capabilityProperties) {
100                 // Parameter has now been visited
101                 val parameterInSuperType =
102                     parametersToValidate.remove(superTypeProperty.simpleName.asString())
103                 if (parameterInSuperType == null) {
104                     throw ProcessingException(
105                         "All Properties in @AppFunctionSchemaCapability " +
106                             "supertypes must be present in subtype",
107                         superTypeProperty
108                     )
109                 }
110                 validateSerializableParameter(parameterInSuperType)
111             }
112         }
113 
114         // Validate the remaining parameters
115         if (parametersToValidate.isNotEmpty()) {
116             for ((_, parameterToValidate) in parametersToValidate) {
117                 validateSerializableParameter(parameterToValidate)
118             }
119         }
120     }
121 
122     /**
123      * Validates that the super types of the serializable [annotatedSerializable] are valid.
124      *
125      * A super type of a serializable class must be annotated with either
126      * [androidx.appfunctions.AppFunctionSchemaCapability] or
127      * [androidx.appfunctions.AppFunctionSerializable]. A class cannot be annotated with both
128      * annotations.
129      *
130      * @param superTypesWithSerializableAnnotation a set of [KSClassDeclaration] for all super types
131      *   of the [annotatedSerializable] that are annotated with
132      *   [androidx.appfunctions.AppFunctionSerializable].
133      * @param superTypesWithCapabilityAnnotation a set of [KSClassDeclaration] for all super types
134      *   of the [annotatedSerializable] that are annotated with
135      *   [androidx.appfunctions.AppFunctionSchemaCapability].
136      */
validateSuperTypesnull137     private fun validateSuperTypes(
138         superTypesWithSerializableAnnotation: Set<KSClassDeclaration>,
139         superTypesWithCapabilityAnnotation: Set<KSClassDeclaration>
140     ) {
141         val classesWithMultipleAnnotations =
142             superTypesWithSerializableAnnotation.intersect(superTypesWithCapabilityAnnotation)
143         if (classesWithMultipleAnnotations.isNotEmpty()) {
144             throw ProcessingException(
145                 "A class cannot be annotated with both @AppFunctionSerializable and " +
146                     "@AppFunctionSchemaCapability.",
147                 classesWithMultipleAnnotations.first() // Choose the first one as a sample
148             )
149         }
150     }
151 
validateSerializableParameternull152     private fun validateSerializableParameter(propertyDeclaration: AppFunctionPropertyDeclaration) {
153         if (propertyDeclaration.isGenericType) {
154             // Don't validate a generic type. Whether a generic type is valid or not would be
155             // validated when it is parameterized.
156             return
157         }
158         if (!isSupportedType(propertyDeclaration.type)) {
159             throw ProcessingException(
160                 "AppFunctionSerializable properties must be one of the following types:\n" +
161                     SUPPORTED_TYPES_STRING +
162                     ", an @AppFunctionSerializable or a list of @AppFunctionSerializable\nbut found " +
163                     propertyDeclaration.type.toTypeName(),
164                 propertyDeclaration.type
165             )
166         }
167     }
168 }
169