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.internal
18 
19 import android.content.Context
20 import android.util.Log
21 import androidx.annotation.RestrictTo
22 import androidx.appfunctions.AppFunctionConfiguration
23 import androidx.appfunctions.internal.Constants.APP_FUNCTIONS_TAG
24 import java.lang.reflect.InvocationTargetException
25 
26 /**
27  * A factory that will incorporate [AppFunctionConfiguration] from [context] to create AppFunction
28  * enclosing classes.
29  *
30  * If the application context from [context] overrides [AppFunctionConfiguration.Provider], the
31  * customized factory method will be used to instantiate the enclosing class. Otherwise, it will use
32  * reflection to create the instance assuming the enclosing class has a no argument constructor.
33  *
34  * [createEnclosingClass] will throw [AppFunctionInstantiationException] if unable to instantiate
35  * the enclosing class.
36  */
37 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
38 public class ConfigurableAppFunctionFactory<T : Any>(
39     private val context: Context,
40 ) {
41     public fun createEnclosingClass(enclosingClass: Class<T>): T {
42         val configurationProvider = context.applicationContext as? AppFunctionConfiguration.Provider
43         val customFactory =
44             configurationProvider
45                 ?.appFunctionConfiguration
46                 ?.enclosingClassFactories
47                 ?.get(enclosingClass)
48         if (customFactory == null) {
49             Log.d(APP_FUNCTIONS_TAG, "Unable to find custom factory for [$enclosingClass]")
50             return getNoArgumentAppFunctionFactory<T>().invoke(enclosingClass)
51         }
52 
53         val instance = customFactory.invoke()
54         @Suppress("UNCHECKED_CAST") return instance as T
55     }
56 
57     /** Thrown when unable to instantiate the AppFunction enclosing class. */
58     public class AppFunctionInstantiationException(errorMessage: String) :
59         RuntimeException(errorMessage)
60 
61     private fun <T : Any> getNoArgumentAppFunctionFactory(): (Class<T>) -> T {
62         return { enclosingClass: Class<T> ->
63             try {
64                 enclosingClass.getDeclaredConstructor().newInstance()
65             } catch (_: IllegalAccessException) {
66                 throw AppFunctionInstantiationException(
67                     "Cannot access the constructor of $enclosingClass"
68                 )
69             } catch (_: NoSuchMethodException) {
70                 throw AppFunctionInstantiationException(
71                     "$enclosingClass requires additional parameter to create. " +
72                         "Please either remove the additional parameters or implement the " +
73                         "factory and provide it in " +
74                         "${AppFunctionConfiguration::class.qualifiedName}",
75                 )
76             } catch (_: InstantiationException) {
77                 throw AppFunctionInstantiationException(
78                     "$enclosingClass should have a public no-argument constructor"
79                 )
80             } catch (_: InvocationTargetException) {
81                 throw AppFunctionInstantiationException(
82                     "Something went wrong when creating $enclosingClass"
83                 )
84             } catch (_: ExceptionInInitializerError) {
85                 throw AppFunctionInstantiationException(
86                     "Something went wrong when creating $enclosingClass"
87                 )
88             }
89         }
90     }
91 }
92