From bbf36e164d64142583d7bf3c6a91bd136ed75da5 Mon Sep 17 00:00:00 2001 From: Him188 Date: Wed, 2 Sep 2020 22:09:25 +0800 Subject: [PATCH] Introduce ExtensionPoint, Add extension points PluginLoaderProvider and BotConfigurationAlterer --- .../net/mamoe/mirai/console/MiraiConsole.kt | 11 +- .../mirai/console/command/CommandSender.kt | 4 +- .../extensions/AbstractExtensionPoint.kt | 118 ++++++++++++++++++ .../extensions/BotConfigurationAlterer.kt | 28 +++++ .../extensions/MiraiConsoleServices.kt | 22 ++++ .../extensions/PluginLoaderProvider.kt | 12 ++ .../console/internal/data/reflectionUtils.kt | 3 + .../internal/plugin/JarPluginLoaderImpl.kt | 6 +- .../internal/plugin/PluginManagerImpl.kt | 20 ++- ...erviceHelper.kt => PluginServiceHelper.kt} | 9 +- 10 files changed, 213 insertions(+), 20 deletions(-) create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/AbstractExtensionPoint.kt create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/BotConfigurationAlterer.kt create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/MiraiConsoleServices.kt create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/PluginLoaderProvider.kt rename backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/{ServiceHelper.kt => PluginServiceHelper.kt} (87%) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt index 960ca39d3..1158e95f1 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt @@ -18,6 +18,8 @@ import kotlinx.coroutines.Job import net.mamoe.mirai.Bot import net.mamoe.mirai.console.MiraiConsole.INSTANCE import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start +import net.mamoe.mirai.console.extensions.BotConfigurationAlterer +import net.mamoe.mirai.console.extensions.foldExtensions import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge import net.mamoe.mirai.console.plugin.PluginLoader import net.mamoe.mirai.console.plugin.PluginManager @@ -101,6 +103,8 @@ public interface MiraiConsole : CoroutineScope { * 调用 [Bot.login] 可登录. * * @see Bot.botInstances 获取现有 [Bot] 实例列表 + * + * @see BotConfigurationAl */ // don't static @ConsoleExperimentalAPI("This is a low-level API and might be removed in the future.") @@ -120,7 +124,7 @@ public interface MiraiConsole : CoroutineScope { @Suppress("UNREACHABLE_CODE") private fun addBotImpl(id: Long, password: Any, configuration: BotConfiguration.() -> Unit = {}): Bot { - val config: BotConfiguration.() -> Unit = { + var config = BotConfiguration().apply { fileBasedDeviceInfo() redirectNetworkLogToDirectory() parentCoroutineContext = MiraiConsole.childScopeContext("Bot $id") @@ -128,6 +132,11 @@ public interface MiraiConsole : CoroutineScope { this.loginSolver = MiraiConsoleImplementationBridge.createLoginSolver(id, this) configuration() } + + config = BotConfigurationAlterer.foldExtensions(config) { acc, extension -> + extension.alterConfiguration(id, acc) + } + return when (password) { is ByteArray -> Bot(id, password, config) is String -> Bot(id, password, config) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt index 73b857aa9..1dbe2a424 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt @@ -33,9 +33,9 @@ import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip import net.mamoe.mirai.console.internal.data.castOrNull import net.mamoe.mirai.console.internal.plugin.rootCauseOrSelf import net.mamoe.mirai.console.util.ConsoleExperimentalAPI +import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope +import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScopeContext import net.mamoe.mirai.console.util.MessageScope -import net.mamoe.mirai.console.util.childScope -import net.mamoe.mirai.console.util.childScopeContext import net.mamoe.mirai.contact.* import net.mamoe.mirai.message.* import net.mamoe.mirai.message.data.Message diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/AbstractExtensionPoint.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/AbstractExtensionPoint.kt new file mode 100644 index 000000000..5a5f41cf9 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/AbstractExtensionPoint.kt @@ -0,0 +1,118 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +@file:Suppress("unused", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") + +package net.mamoe.mirai.console.extensions + +import net.mamoe.mirai.console.internal.data.kClassQualifiedNameOrTip +import net.mamoe.mirai.console.plugin.Plugin +import net.mamoe.mirai.console.plugin.PluginLoader +import net.mamoe.mirai.console.plugin.name +import net.mamoe.mirai.console.util.ConsoleExperimentalAPI +import java.util.concurrent.CopyOnWriteArraySet +import kotlin.contracts.contract +import kotlin.internal.LowPriorityInOverloadResolution +import kotlin.reflect.KClass + +@ConsoleExperimentalAPI +public open class AbstractExtensionPoint( + @ConsoleExperimentalAPI + public val type: KClass +) { + + @ConsoleExperimentalAPI + public data class ExtensionRegistry( + public val plugin: Plugin, + public val extension: T + ) + + private val instances: MutableSet> = CopyOnWriteArraySet() + + @Synchronized + @ConsoleExperimentalAPI + public fun registerExtension(plugin: Plugin, extension: T) { + require(plugin.isEnabled) { "Plugin $plugin must be enabled before registering an extension." } + instances.add(ExtensionRegistry(plugin, extension)) + } + + @Synchronized + internal fun getExtensions(): Set> = instances +} + + +/** + * 在调用一个 extension 时遇到的异常. + * + * @see PluginLoader.load + * @see PluginLoader.enable + * @see PluginLoader.disable + * @see PluginLoader.description + */ +@ConsoleExperimentalAPI +public open class ExtensionException : RuntimeException { + public constructor() : super() + public constructor(message: String?) : super(message) + public constructor(message: String?, cause: Throwable?) : super(message, cause) + public constructor(cause: Throwable?) : super(cause) +} + +internal inline fun AbstractExtensionPoint.withExtensions(block: T.() -> Unit) { + return withExtensions { _ -> block() } +} + +@LowPriorityInOverloadResolution +internal inline fun AbstractExtensionPoint.withExtensions(block: T.(plugin: Plugin) -> Unit) { + contract { + callsInPlace(block) + } + for ((plugin, extension) in this.getExtensions()) { + kotlin.runCatching { + block.invoke(extension, plugin) + }.getOrElse { throwable -> + throwExtensionException(extension, plugin, throwable) + } + } +} + +internal inline fun AbstractExtensionPoint.foldExtensions( + initial: E, + block: (acc: E, extension: T) -> E +): E { + contract { + callsInPlace(block) + } + var e: E = initial + for ((plugin, extension) in this.getExtensions()) { + kotlin.runCatching { + e = block.invoke(e, extension) + }.getOrElse { throwable -> + throwExtensionException(extension, plugin, throwable) + } + } + return e +} + +internal fun AbstractExtensionPoint.throwExtensionException( + extension: T, + plugin: Plugin, + throwable: Throwable +) { + throw ExtensionException( + "Exception while executing extension ${extension.kClassQualifiedNameOrTip} from ${plugin.name}, registered for ${this.type.qualifiedName}", + throwable + ) +} + +internal inline fun AbstractExtensionPoint.useExtensions(block: (extension: T) -> Unit): Unit = + withExtensions(block) + +@LowPriorityInOverloadResolution +internal inline fun AbstractExtensionPoint.useExtensions(block: (extension: T, plugin: Plugin) -> Unit): Unit = + withExtensions(block) \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/BotConfigurationAlterer.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/BotConfigurationAlterer.kt new file mode 100644 index 000000000..d48f6d9bf --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/BotConfigurationAlterer.kt @@ -0,0 +1,28 @@ +@file:Suppress("unused") + +package net.mamoe.mirai.console.extensions + +import net.mamoe.mirai.console.MiraiConsole +import net.mamoe.mirai.console.util.ConsoleExperimentalAPI +import net.mamoe.mirai.utils.BotConfiguration + +/** + * [MiraiConsole.addBot] 时的 [BotConfiguration] 修改扩展 + * + * @see MiraiConsole.addBot + */ +@ConsoleExperimentalAPI +public interface BotConfigurationAlterer { + + /** + * 修改 [configuration], 返回修改完成的 [BotConfiguration] + */ + @JvmDefault + public fun alterConfiguration( + botId: Long, + configuration: BotConfiguration + ): BotConfiguration = configuration + + public companion object ExtensionPoint : + AbstractExtensionPoint(BotConfigurationAlterer::class) +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/MiraiConsoleServices.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/MiraiConsoleServices.kt new file mode 100644 index 000000000..af3f79bf5 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/MiraiConsoleServices.kt @@ -0,0 +1,22 @@ +package net.mamoe.mirai.console.extensions + +import net.mamoe.mirai.console.internal.util.PluginServiceHelper +import kotlin.reflect.KClass + +// not public now +internal object ServiceContainer { + private val instances: MutableMap, List<*>> = mutableMapOf() + + @Suppress("UNCHECKED_CAST") + @JvmStatic + @Synchronized + fun getService(clazz: KClass): List { + instances[clazz]?.let { return it as List } + PluginServiceHelper.loadAllServicesFromMemoryAndPluginClassLoaders(clazz).let { + instances[clazz] = it + return it + } + } + + inline fun getService(): List = getService(T::class) +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/PluginLoaderProvider.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/PluginLoaderProvider.kt new file mode 100644 index 000000000..e0410f9ac --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/extensions/PluginLoaderProvider.kt @@ -0,0 +1,12 @@ +package net.mamoe.mirai.console.extensions + +import net.mamoe.mirai.console.plugin.PluginLoader + +/** + * 提供扩展 [PluginLoader] + */ +public interface PluginLoaderProvider { + public val instance: PluginLoader<*, *> + + public companion object ExtensionPoint : AbstractExtensionPoint(PluginLoaderProvider::class) +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/reflectionUtils.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/reflectionUtils.kt index 72a0c5a36..e9952dd86 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/reflectionUtils.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/reflectionUtils.kt @@ -72,3 +72,6 @@ internal fun KClass<*>.findValueName(): String = internal fun Int.isOdd() = this and 0b1 != 0 internal val KProperty<*>.valueName: String get() = this.findAnnotation()?.value ?: this.name + +internal inline val Any.kClassQualifiedName: String? get() = this::class.qualifiedName +internal inline val Any.kClassQualifiedNameOrTip: String get() = this::class.qualifiedNameOrTip \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JarPluginLoaderImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JarPluginLoaderImpl.kt index 69b383732..5683356c0 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JarPluginLoaderImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JarPluginLoaderImpl.kt @@ -15,12 +15,12 @@ import kotlinx.coroutines.ensureActive import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.data.PluginDataStorage import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge -import net.mamoe.mirai.console.internal.util.ServiceHelper.findServices -import net.mamoe.mirai.console.internal.util.ServiceHelper.loadAllServices +import net.mamoe.mirai.console.internal.util.PluginServiceHelper.findServices +import net.mamoe.mirai.console.internal.util.PluginServiceHelper.loadAllServices import net.mamoe.mirai.console.plugin.AbstractFilePluginLoader import net.mamoe.mirai.console.plugin.PluginLoadException import net.mamoe.mirai.console.plugin.jvm.* -import net.mamoe.mirai.console.util.childScope +import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.info import java.io.File diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt index 56ccd2116..03035b668 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt @@ -15,16 +15,16 @@ import kotlinx.atomicfu.locks.withLock import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import net.mamoe.mirai.console.MiraiConsole +import net.mamoe.mirai.console.extensions.PluginLoaderProvider +import net.mamoe.mirai.console.extensions.useExtensions import net.mamoe.mirai.console.internal.data.cast import net.mamoe.mirai.console.internal.data.mkdir -import net.mamoe.mirai.console.internal.util.ServiceHelper.findServices -import net.mamoe.mirai.console.internal.util.ServiceHelper.loadAllServices import net.mamoe.mirai.console.plugin.* import net.mamoe.mirai.console.plugin.description.PluginDependency import net.mamoe.mirai.console.plugin.description.PluginDescription import net.mamoe.mirai.console.plugin.description.PluginKind import net.mamoe.mirai.console.plugin.jvm.JvmPlugin -import net.mamoe.mirai.console.util.childScope +import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope import net.mamoe.mirai.utils.info import java.io.File import java.nio.file.Path @@ -144,16 +144,10 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol private fun loadPluginLoaderProvidedByPlugins() { loadersLock.withLock { - JarPluginLoaderImpl.classLoaders.asSequence() - .flatMap { pluginClassLoader -> - pluginClassLoader.findServices>().loadAllServices() - } - .onEach { loaded -> - logger.info { "Successfully loaded PluginLoader ${loaded}." } - } - .forEach { - _pluginLoaders.add(it) - } + PluginLoaderProvider.useExtensions { + logger.info { "Loaded PluginLoader ${it.instance} from $" } + _pluginLoaders.add(it.instance) + } } } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/ServiceHelper.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/PluginServiceHelper.kt similarity index 87% rename from backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/ServiceHelper.kt rename to backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/PluginServiceHelper.kt index 04e8db113..0ba666f38 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/ServiceHelper.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/PluginServiceHelper.kt @@ -11,8 +11,10 @@ package net.mamoe.mirai.console.internal.util import net.mamoe.mirai.console.internal.data.cast import net.mamoe.mirai.console.internal.data.createInstanceOrNull +import net.mamoe.mirai.console.internal.plugin.JarPluginLoaderImpl import java.io.InputStream import java.lang.reflect.Modifier +import java.util.* import kotlin.reflect.KClass import java.lang.reflect.Member as JReflectionMember @@ -22,7 +24,7 @@ internal class ServiceList( internal val delegate: List ) -internal object ServiceHelper { +internal object PluginServiceHelper { inline fun ClassLoader.findServices(): ServiceList = findServices(T::class) fun ClassLoader.findServices(vararg serviceTypes: KClass): ServiceList = @@ -60,6 +62,11 @@ internal object ServiceHelper { )*/ } as T? } + + fun loadAllServicesFromMemoryAndPluginClassLoaders(service: KClass): List { + val list = ServiceLoader.load(service.java, this::class.java.classLoader).toList() + return list + JarPluginLoaderImpl.classLoaders.flatMap { it.findServices(service).loadAllServices() } + } } internal fun JReflectionMember.isStatic(): Boolean = this.modifiers.and(Modifier.STATIC) != 0