Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add fallback solution for Services #2511

Merged
merged 15 commits into from
Mar 11, 2023
67 changes: 67 additions & 0 deletions mirai-core-utils/src/commonMain/kotlin/Services.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,76 @@

package net.mamoe.mirai.utils

import kotlinx.atomicfu.locks.reentrantLock
import kotlinx.atomicfu.locks.withLock
import kotlin.jvm.JvmName
import kotlin.reflect.KClass

public object Services {
private val lock = reentrantLock()
public fun <T : Any> qualifiedNameOrFail(clazz: KClass<out T>): String =
clazz.qualifiedName ?: error("Could not find qualifiedName for $clazz")

internal class Implementation(
val implementationClass: String,
val instance: Lazy<Any>
)

private val registered: MutableMap<String, MutableList<Implementation>> = mutableMapOf()
private val overrided: MutableMap<String, Implementation> = mutableMapOf()

@Suppress("UNCHECKED_CAST")
public fun <T : Any> getOverrideOrNull(clazz: KClass<out T>): T? {
lock.withLock {
return overrided[qualifiedNameOrFail(clazz)]?.instance?.value as T?
}
}

internal fun registerAsOverride(baseClass: String, implementationClass: String, implementation: () -> Any) {
lock.withLock {
overrided[baseClass] = Implementation(implementationClass, lazy(implementation))
}
}

public fun register(baseClass: String, implementationClass: String, implementation: () -> Any) {
lock.withLock {
registered.getOrPut(baseClass, ::mutableListOf)
.add(Implementation(implementationClass, lazy(implementation)))
}
}

public fun firstImplementationOrNull(baseClass: String): Any? {
lock.withLock {
overrided[baseClass]?.let { return it.instance.value }
return registered[baseClass]?.firstOrNull()?.instance?.value
}
}

public fun implementations(baseClass: String): Sequence<Lazy<Any>>? {
lock.withLock {
val implementations = registered[baseClass]
val forced = overrided[baseClass]
if (forced == null && implementations == null) return null

val implementationsSnapshot = implementations?.toList().orEmpty()

return sequence {
if (forced != null) yield(forced.instance)

implementationsSnapshot.forEach { yield(it.instance) }
}
}
}

internal fun implementations1(baseClass: String) = lock.withLock { registered[baseClass]?.toList().orEmpty() }

public fun print(): String {
lock.withLock {
return registered.entries.joinToString { "${it.key}:${it.value}" }
}
}
}

public expect fun <T : Any> loadServiceOrNull(clazz: KClass<out T>, fallbackImplementation: String? = null): T?
public expect fun <T : Any> loadService(clazz: KClass<out T>, fallbackImplementation: String? = null): T
public expect fun <T : Any> loadServices(clazz: KClass<out T>): Sequence<T>
Expand Down
89 changes: 77 additions & 12 deletions mirai-core-utils/src/jvmBaseMain/kotlin/Services.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,35 +13,100 @@ import java.util.*
import kotlin.reflect.KClass
import kotlin.reflect.full.createInstance

private enum class LoaderType {
JDK,
BOTH,
FALLBACK,
}

private val loaderType = when (systemProp("mirai.service.loader", "both")) {
"jdk" -> LoaderType.JDK
"both" -> LoaderType.BOTH
"fallback" -> LoaderType.FALLBACK
else -> throw IllegalStateException("cannot find a service loader, mirai.service.loader must be both, jdk or fallback (default by both)")
}

@Suppress("UNCHECKED_CAST")
public actual fun <T : Any> loadService(clazz: KClass<out T>, fallbackImplementation: String?): T {
val fallbackService by lazy {
Services.firstImplementationOrNull(Services.qualifiedNameOrFail(clazz)) as T?
}

val jdkService by lazy {
ServiceLoader.load(clazz.java).firstOrNull()?.let { return@lazy it }

ServiceLoader.load(clazz.java, clazz.java.classLoader).firstOrNull()
}

var suppressed: Throwable? = null
return ServiceLoader.load(clazz.java).firstOrNull()
?: ServiceLoader.load(clazz.java, clazz.java.classLoader).firstOrNull()
?: (if (fallbackImplementation == null) null
else runCatching { findCreateInstance<T>(fallbackImplementation) }.onFailure { suppressed = it }.getOrNull())
?: throw NoSuchElementException("Could not find an implementation for service class ${clazz.qualifiedName}").apply {
if (suppressed != null) addSuppressed(suppressed)
}

val services by lazy {
when (loaderType) {
LoaderType.JDK -> jdkService
LoaderType.BOTH -> jdkService ?: fallbackService
LoaderType.FALLBACK -> fallbackService
}?.let { return@lazy it }

if (fallbackImplementation != null) {
runCatching {
findCreateInstance<T>(fallbackImplementation)
}.onFailure { suppressed = it }.getOrNull()
} else null
}

return Services.getOverrideOrNull(clazz) ?: services
?: throw NoSuchElementException("Could not find an implementation for service class ${clazz.qualifiedName}").apply {
if (suppressed != null) addSuppressed(suppressed)
}
}

private fun <T : Any> findCreateInstance(fallbackImplementation: String): T {
return Class.forName(fallbackImplementation).cast<Class<out T>>().kotlin.run { objectInstance ?: createInstance() }
}

public actual fun <T : Any> loadServiceOrNull(clazz: KClass<out T>, fallbackImplementation: String?): T? {
return ServiceLoader.load(clazz.java).firstOrNull()
?: ServiceLoader.load(clazz.java, clazz.java.classLoader).firstOrNull()
?: if (fallbackImplementation == null) return null
else runCatching { findCreateInstance<T>(fallbackImplementation) }.getOrNull()
return runCatching { loadService(clazz, fallbackImplementation) }.getOrNull()
}

@Suppress("UNCHECKED_CAST")
public actual fun <T : Any> loadServices(clazz: KClass<out T>): Sequence<T> {
return sequence {
fun fallBackServicesSeq(): Sequence<T> {
return Services.implementations(Services.qualifiedNameOrFail(clazz)).orEmpty()
.map { it.value as T }
}

fun jdkServices(): Sequence<T> = sequence {
val current = ServiceLoader.load(clazz.java).iterator()
if (current.hasNext()) {
yieldAll(current)
} else {
yieldAll(ServiceLoader.load(clazz.java, clazz.java.classLoader))
}
}

fun bothServices(): Sequence<T> = sequence {
Services.getOverrideOrNull(clazz)?.let { yield(it) }

var jdkServices = ServiceLoader.load(clazz.java).toList()
if (jdkServices.isEmpty()) {
jdkServices = ServiceLoader.load(clazz.java, clazz.java.classLoader).toList()
}
yieldAll(jdkServices)

Services.implementations1(Services.qualifiedNameOrFail(clazz)).asSequence()
.filter { impl ->
// Drop duplicated
jdkServices.none { it.javaClass.name == impl.implementationClass }
}
.forEach { yield(it.instance.value as T) }
}




return when (loaderType) {
LoaderType.JDK -> jdkServices()
LoaderType.BOTH -> bothServices()
LoaderType.FALLBACK -> fallBackServicesSeq()
}
}
45 changes: 2 additions & 43 deletions mirai-core-utils/src/nativeMain/kotlin/Service.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,47 +11,9 @@

package net.mamoe.mirai.utils

import kotlinx.atomicfu.locks.reentrantLock
import kotlinx.atomicfu.locks.withLock
import net.mamoe.mirai.utils.Services.qualifiedNameOrFail
import kotlin.reflect.KClass

public object Services {
private val lock = reentrantLock()

private class Implementation(
val implementationClass: String,
val instance: Lazy<Any>
)

private val registered: MutableMap<String, MutableList<Implementation>> = mutableMapOf()

public fun register(baseClass: String, implementationClass: String, implementation: () -> Any) {
lock.withLock {
registered.getOrPut(baseClass, ::mutableListOf)
.add(Implementation(implementationClass, lazy(implementation)))
}
}

public fun firstImplementationOrNull(baseClass: String): Any? {
lock.withLock {
return registered[baseClass]?.firstOrNull()?.instance?.value
}
}

public fun implementations(baseClass: String): List<Lazy<Any>>? {
lock.withLock {
return registered[baseClass]?.map { it.instance }
}

}

public fun print(): String {
lock.withLock {
return registered.entries.joinToString { "${it.key}:${it.value}" }
}
}
}

@Suppress("UNCHECKED_CAST")
public actual fun <T : Any> loadServiceOrNull(
clazz: KClass<out T>,
Expand All @@ -66,7 +28,4 @@ public actual fun <T : Any> loadService(
?: error("Could not load service '${clazz.qualifiedName ?: clazz}'. Current services: ${Services.print()}")

public actual fun <T : Any> loadServices(clazz: KClass<out T>): Sequence<T> =
Services.implementations(qualifiedNameOrFail(clazz))?.asSequence()?.map { it.value }.orEmpty().castUp()

private fun <T : Any> qualifiedNameOrFail(clazz: KClass<out T>) =
clazz.qualifiedName ?: error("Could not find qualifiedName for $clazz")
Services.implementations(qualifiedNameOrFail(clazz)).orEmpty().map { it.value }.castUp()
2 changes: 2 additions & 0 deletions mirai-core/src/jvmBaseMain/kotlin/MiraiImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ import io.ktor.client.engine.okhttp.*
import io.ktor.client.plugins.*
import kotlinx.atomicfu.atomic
import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade
import net.mamoe.mirai.internal.utils.MiraiCoreServices

private val initialized = atomic(false)

@Suppress("FunctionName")
internal actual fun _MiraiImpl_static_init() {
if (!initialized.compareAndSet(expect = false, update = true)) return
MiraiCoreServices.registerAll()
MessageProtocolFacade.INSTANCE // register serializers
}

Expand Down