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