1 /* <lambda>null2 * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. 3 */ 4 5 package kotlinx.coroutines.internal 6 7 import java.io.* 8 import java.net.* 9 import java.util.* 10 import java.util.jar.* 11 import java.util.zip.* 12 import kotlin.collections.ArrayList 13 14 /** 15 * Don't use JvmField here to enable R8 optimizations via "assumenosideeffects" 16 */ 17 internal val ANDROID_DETECTED = runCatching { Class.forName("android.os.Build") }.isSuccess 18 19 /** 20 * A simplified version of [ServiceLoader]. 21 * FastServiceLoader locates and instantiates all service providers named in configuration 22 * files placed in the resource directory <tt>META-INF/services</tt>. 23 * 24 * The main difference between this class and classic service loader is in skipping 25 * verification JARs. A verification requires reading the whole JAR (and it causes problems and ANRs on Android devices) 26 * and prevents only trivial checksum issues. See #878. 27 * 28 * If any error occurs during loading, it fallbacks to [ServiceLoader], mostly to prevent R8 issues. 29 */ 30 internal object FastServiceLoader { 31 private const val PREFIX: String = "META-INF/services/" 32 33 /** 34 * This method attempts to load [MainDispatcherFactory] in Android-friendly way. 35 * 36 * If we are not on Android, this method fallbacks to a regular service loading, 37 * else we attempt to do `Class.forName` lookup for 38 * `AndroidDispatcherFactory` and `TestMainDispatcherFactory`. 39 * If lookups are successful, we return resultinAg instances because we know that 40 * `MainDispatcherFactory` API is internal and this is the only possible classes of `MainDispatcherFactory` Service on Android. 41 * 42 * Such intricate dance is required to avoid calls to `ServiceLoader.load` for multiple reasons: 43 * 1) It eliminates disk lookup on potentially slow devices on the Main thread. 44 * 2) Various Android toolchain versions by various vendors don't tend to handle ServiceLoader calls properly. 45 * Sometimes META-INF is removed from the resulting APK, sometimes class names are mangled, etc. 46 * While it is not the problem of `kotlinx.coroutines`, it significantly worsens user experience, thus we are workarounding it. 47 * Examples of such issues are #932, #1072, #1557, #1567 48 * 49 * We also use SL for [CoroutineExceptionHandler], but we do not experience the same problems and CEH is a public API 50 * that may already be injected vis SL, so we are not using the same technique for it. 51 */ loadMainDispatcherFactorynull52 internal fun loadMainDispatcherFactory(): List<MainDispatcherFactory> { 53 val clz = MainDispatcherFactory::class.java 54 if (!ANDROID_DETECTED) { 55 return load(clz, clz.classLoader) 56 } 57 58 return try { 59 val result = ArrayList<MainDispatcherFactory>(2) 60 createInstanceOf(clz, "kotlinx.coroutines.android.AndroidDispatcherFactory")?.apply { result.add(this) } 61 createInstanceOf(clz, "kotlinx.coroutines.test.internal.TestMainDispatcherFactory")?.apply { result.add(this) } 62 result 63 } catch (e: Throwable) { 64 // Fallback to the regular SL in case of any unexpected exception 65 load(clz, clz.classLoader) 66 } 67 } 68 69 /* 70 * This method is inline to have a direct Class.forName("string literal") in the byte code to avoid weird interactions with ProGuard/R8. 71 */ 72 @Suppress("NOTHING_TO_INLINE") createInstanceOfnull73 private inline fun createInstanceOf( 74 baseClass: Class<MainDispatcherFactory>, 75 serviceClass: String 76 ): MainDispatcherFactory? { 77 return try { 78 val clz = Class.forName(serviceClass, true, baseClass.classLoader) 79 baseClass.cast(clz.getDeclaredConstructor().newInstance()) 80 } catch (e: ClassNotFoundException) { // Do not fail if TestMainDispatcherFactory is not found 81 null 82 } 83 } 84 loadnull85 private fun <S> load(service: Class<S>, loader: ClassLoader): List<S> { 86 return try { 87 loadProviders(service, loader) 88 } catch (e: Throwable) { 89 // Fallback to default service loader 90 ServiceLoader.load(service, loader).toList() 91 } 92 } 93 94 // Visible for tests loadProvidersnull95 internal fun <S> loadProviders(service: Class<S>, loader: ClassLoader): List<S> { 96 val fullServiceName = PREFIX + service.name 97 // Filter out situations when both JAR and regular files are in the classpath (e.g. IDEA) 98 val urls = loader.getResources(fullServiceName) 99 val providers = urls.toList().flatMap { parse(it) }.toSet() 100 require(providers.isNotEmpty()) { "No providers were loaded with FastServiceLoader" } 101 return providers.map { getProviderInstance(it, loader, service) } 102 } 103 getProviderInstancenull104 private fun <S> getProviderInstance(name: String, loader: ClassLoader, service: Class<S>): S { 105 val clazz = Class.forName(name, false, loader) 106 require(service.isAssignableFrom(clazz)) { "Expected service of class $service, but found $clazz" } 107 return service.cast(clazz.getDeclaredConstructor().newInstance()) 108 } 109 parsenull110 private fun parse(url: URL): List<String> { 111 val path = url.toString() 112 // Fast-path for JARs 113 if (path.startsWith("jar")) { 114 val pathToJar = path.substringAfter("jar:file:").substringBefore('!') 115 val entry = path.substringAfter("!/") 116 // mind the verify = false flag! 117 (JarFile(pathToJar, false)).use { file -> 118 BufferedReader(InputStreamReader(file.getInputStream(ZipEntry(entry)), "UTF-8")).use { r -> 119 return parseFile(r) 120 } 121 } 122 } 123 // Regular path for everything else 124 return BufferedReader(InputStreamReader(url.openStream())).use { reader -> 125 parseFile(reader) 126 } 127 } 128 129 // JarFile does no implement Closesable on Java 1.6 usenull130 private inline fun <R> JarFile.use(block: (JarFile) -> R): R { 131 var cause: Throwable? = null 132 try { 133 return block(this) 134 } catch (e: Throwable) { 135 cause = e 136 throw e 137 } finally { 138 try { 139 close() 140 } catch (closeException: Throwable) { 141 if (cause === null) throw closeException 142 cause.addSuppressed(closeException) 143 throw cause 144 } 145 } 146 } 147 parseFilenull148 private fun parseFile(r: BufferedReader): List<String> { 149 val names = mutableSetOf<String>() 150 while (true) { 151 val line = r.readLine() ?: break 152 val serviceName = line.substringBefore("#").trim() 153 require(serviceName.all { it == '.' || Character.isJavaIdentifierPart(it) }) { "Illegal service provider class name: $serviceName" } 154 if (serviceName.isNotEmpty()) { 155 names.add(serviceName) 156 } 157 } 158 return names.toList() 159 } 160 } 161