• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 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 package com.android.bedstead.harrier
17 
18 import android.util.Log
19 import com.google.errorprone.annotations.CanIgnoreReturnValue
20 import com.android.bedstead.nene.utils.FailureDumper
21 import kotlin.reflect.KClass
22 import kotlin.reflect.KProperty
23 
24 /**
25  * Registrar of dependencies for use by Bedstead modules.
26  *
27  * Use of this service locator allows for the single [DeviceState] entry point to
28  * bedstead while allowing modularisation and loose coupling.
29  */
30 class BedsteadServiceLocator : DeviceStateComponent {
31 
32     private val dependenciesMap = mutableMapOf<KClass<*>, Any>()
33 
34     /**
35      * Obtains the instance of the given [clazz]
36      * if you have circular dependencies use [getValue]
37      */
38     @Suppress("UNCHECKED_CAST")
getnull39     fun <T : Any> get(clazz: KClass<T>): T {
40         val existingInstance = dependenciesMap[clazz]
41         return if (existingInstance != null) {
42             existingInstance as T
43         } else {
44             createDependencyByReflection(clazz.java).also {
45                 dependenciesMap[clazz] = it
46                 if (it is DeviceStateComponent) {
47                     Log.v(LOG_TAG, "prepareTestState (after creation): " + it.javaClass)
48                     it.prepareTestState()
49                 }
50             }
51         }
52     }
53 
54     /**
55      * See [BedsteadServiceLocator.get]
56      */
getnull57     inline fun <reified T : Any> get(): T = get(T::class)
58 
59     /**
60      * Obtains the instance of the given type when needed by delegated properties
61      * example: val instance: Type by locator
62      */
63     inline operator fun <reified T : Any> getValue(thisRef: Any, property: KProperty<*>): T {
64         return get<T>()
65     }
66 
67     /**
68      * See [BedsteadServiceLocator.get]
69      */
getnull70     fun <T : Any> get(clazz: Class<T>): T = get(clazz.kotlin)
71 
72     /**
73      * Obtains the instance of the given [className]
74      * @param className – the fully qualified name of the desired class.
75      */
76     @Suppress("UNCHECKED_CAST")
77     @CanIgnoreReturnValue
78     fun <T : Any> get(className: String): T {
79         try {
80             return (get(Class.forName(className))) as T
81         } catch (e: ClassNotFoundException) {
82             throw IllegalStateException(
83                 "Could not find dependency: $className. " +
84                         "Make sure it is on the classpath and the appropriate module is loaded"
85             )
86         }
87     }
88 
89     /**
90      * Obtains the instance of the given [className] or null if it's not available
91      * @param className – the fully qualified name of the desired class.
92      */
93     @Suppress("UNCHECKED_CAST")
getOrNullnull94     fun <T : Any> getOrNull(className: String): T? {
95         return try {
96             (get(Class.forName(className))) as T
97         } catch (e: ClassNotFoundException) {
98             null
99         }
100     }
101 
createDependencyByReflectionnull102     private fun <T : Any> createDependencyByReflection(clazz: Class<T>): T {
103         return try {
104             clazz.getDeclaredConstructor().newInstance()
105         } catch (ignored: NoSuchMethodException) {
106             try {
107                 clazz
108                     .getDeclaredConstructor(BedsteadServiceLocator::class.java)
109                     .newInstance(this)
110             } catch (ignored: NoSuchMethodException) {
111                 throw IllegalStateException(
112                     "$clazz doesn't have a constructor taking BedsteadServiceLocator as the only " +
113                             "parameter or an empty constructor. " +
114                             "Kotlin classes with init blocks can't be created by reflection. " +
115                             "Provide the right constructor."
116                 )
117             }
118         }
119     }
120 
121     /**
122      * Get all loaded dependencies
123      */
getAllDependenciesnull124     fun getAllDependencies(): Collection<Any> {
125         return dependenciesMap.values
126     }
127 
128     /**
129      * Get all loaded dependencies of type T
130      */
getAllDependenciesOfTypenull131     private inline fun <reified T : Any> getAllDependenciesOfType(): List<T> {
132         return getAllDependencies().filterIsInstance<T>()
133     }
134 
135     /**
136      * Get all loaded FailureDumpers
137      */
getAllFailureDumpersnull138     fun getAllFailureDumpers(): List<FailureDumper> {
139         return getAllDependenciesOfType<FailureDumper>()
140     }
141 
142     /**
143      * Get all loaded TestRuleExecutors
144      */
getAllTestRuleExecutorsnull145     fun getAllTestRuleExecutors(): List<TestRuleExecutor> {
146         return getAllDependenciesOfType<TestRuleExecutor>()
147     }
148 
teardownShareableStatenull149     override fun teardownShareableState() {
150         getAllDependenciesOfType<DeviceStateComponent>().forEach {
151             Log.v(LOG_TAG, "teardownShareableState: " + it.javaClass)
152             try {
153                 it.teardownShareableState()
154             } catch (exception: Exception) {
155                 Log.e(
156                     LOG_TAG,
157                     "an exception occurred while executing " +
158                             "teardownShareableState for ${it.javaClass}",
159                     exception
160                 )
161             }
162         }
163     }
164 
teardownNonShareableStatenull165     override fun teardownNonShareableState() {
166         getAllDependenciesOfType<DeviceStateComponent>().forEach {
167             Log.v(LOG_TAG, "teardownNonShareableState: " + it.javaClass)
168             try {
169                 it.teardownNonShareableState()
170             } catch (exception: Exception) {
171                 Log.e(
172                     LOG_TAG,
173                     "an exception occurred while executing " +
174                             "teardownNonShareableState for ${it.javaClass}",
175                     exception
176                 )
177             }
178         }
179     }
180 
prepareTestStatenull181     override fun prepareTestState() {
182         getAllDependenciesOfType<DeviceStateComponent>().forEach {
183             Log.v(LOG_TAG, "prepareTestState: " + it.javaClass)
184             it.prepareTestState()
185         }
186     }
187 
188     /**
189      * remove all dependencies in order to free some memory
190      */
clearDependenciesnull191     fun clearDependencies() {
192         dependenciesMap.clear()
193     }
194 
195     companion object {
196         private const val LOG_TAG = "BedsteadServiceLocator"
197     }
198 }
199