From 835059c44c1a29f30cb522a8cc5d3b69276e24b4 Mon Sep 17 00:00:00 2001 From: Him188 <Him188@mamoe.net> Date: Thu, 17 Feb 2022 16:28:25 +0000 Subject: [PATCH] (ABI change) Rewrite `ComponentStorage`: order extensions by property `priority`. Remove `builtinImplementations` and contribute them at the first initialization phase instead. Close #1888, fix #1860. Add `ComponentStorageInternal` for frontend to provide components. Deprecate: - SingletonExtension - SingletonExtensionPoint - AbstractSingletonExtensionPoint - SingletonExtensionSelector - CommandCallInterceptorProviderImpl - CommandCallInterceptorProviderImplLazy - CommandCallParserProviderImpl - CommandCallParserProviderImplLazy - CommandCallResolverProviderImpl - CommandCallResolverProviderImplLazy ABI breaking change: - `PermissionServiceProvider`: supertype changed - `CommandCallResolverProvider.ExtensionPoint`: supertype changed - `PermissionServiceProvider.ExtensionPoint`: supertype changed --- .../backend/mirai-console/src/MiraiConsole.kt | 9 +- .../src/MiraiConsoleImplementation.kt | 6 +- .../src/command/BuiltInCommands.kt | 3 +- .../src/command/parse/CommandCallParser.kt | 8 +- .../parse/SpaceSeparatedCommandCallParser.kt | 12 +- .../resolve/BuiltInCommandCallResolver.kt | 8 +- .../command/resolve/CommandCallInterceptor.kt | 48 ++-- .../command/resolve/CommandCallResolver.kt | 10 +- .../src/extension/ComponentStorage.kt | 92 ++++++- .../mirai-console/src/extension/Extension.kt | 20 +- .../src/extension/ExtensionPoint.kt | 68 ++++-- .../src/extension/ExtensionRegistry.kt | 46 ++++ .../CommandCallInterceptorProvider.kt | 8 +- .../extensions/CommandCallParserProvider.kt | 15 +- .../extensions/CommandCallResolverProvider.kt | 21 +- .../extensions/PermissionServiceProvider.kt | 44 +--- .../src/extensions/PluginLoaderProvider.kt | 7 +- .../extensions/SingletonExtensionSelector.kt | 42 +++- .../MiraiConsoleImplementationBridge.kt | 47 ++-- .../extension/ComponentStorageInternal.kt | 226 ++++++++---------- .../SingletonExtensionSelectorImpl.kt | 9 +- .../permission/BuiltInPermissionServices.kt | 9 +- .../src/internal/plugin/PluginManagerImpl.kt | 12 +- .../src/permission/PermissionService.kt | 16 +- .../extension/GlobalComponentStorageTest.kt | 80 +++++++ 25 files changed, 554 insertions(+), 312 deletions(-) create mode 100644 mirai-console/backend/mirai-console/src/extension/ExtensionRegistry.kt create mode 100644 mirai-console/backend/mirai-console/test/extension/GlobalComponentStorageTest.kt diff --git a/mirai-console/backend/mirai-console/src/MiraiConsole.kt b/mirai-console/backend/mirai-console/src/MiraiConsole.kt index 5dae7a875..c20cf511f 100644 --- a/mirai-console/backend/mirai-console/src/MiraiConsole.kt +++ b/mirai-console/backend/mirai-console/src/MiraiConsole.kt @@ -162,7 +162,7 @@ public interface MiraiConsole : CoroutineScope { * * 调用 [Bot.login] 可登录. * - * @see Bot.botInstances 获取现有 [Bot] 实例列表 + * @see Bot.instances 获取现有 [Bot] 实例列表 * @see BotConfigurationAlterer ExtensionPoint */ // don't static @@ -225,11 +225,8 @@ public interface MiraiConsole : CoroutineScope { configuration() } - config = GlobalComponentStorage.run { - BotConfigurationAlterer.foldExtensions(config) { acc, extension -> - extension.alterConfiguration(id, acc) - - } + config = GlobalComponentStorage.foldExtensions(BotConfigurationAlterer, config) { acc, extension -> + extension.alterConfiguration(id, acc) } return when (password) { diff --git a/mirai-console/backend/mirai-console/src/MiraiConsoleImplementation.kt b/mirai-console/backend/mirai-console/src/MiraiConsoleImplementation.kt index 0d7ded158..a5911a72c 100644 --- a/mirai-console/backend/mirai-console/src/MiraiConsoleImplementation.kt +++ b/mirai-console/backend/mirai-console/src/MiraiConsoleImplementation.kt @@ -25,7 +25,6 @@ import net.mamoe.mirai.console.extension.ComponentStorage import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge import net.mamoe.mirai.console.internal.command.CommandManagerImpl import net.mamoe.mirai.console.internal.data.builtins.ConsoleDataScopeImpl -import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage import net.mamoe.mirai.console.internal.logging.LoggerControllerImpl import net.mamoe.mirai.console.internal.plugin.BuiltInJvmPluginLoaderImpl import net.mamoe.mirai.console.internal.pluginManagerImpl @@ -317,6 +316,9 @@ public interface MiraiConsoleImplementation : CoroutineScope { @ConsoleFrontEndImplementation public interface BackendAccess { // GlobalComponentStorage + /** + * 在 Mirai Console 第一个 phase 之后会包含内建 storages. + */ public val globalComponentStorage: ComponentStorage // PluginManagerImpl.resolvedPlugins @@ -365,7 +367,7 @@ public interface MiraiConsoleImplementation : CoroutineScope { @ConsoleFrontEndImplementation public companion object { private val backendAccessInstance = object : BackendAccess { - override val globalComponentStorage: ComponentStorage get() = GlobalComponentStorage + override val globalComponentStorage: ComponentStorage get() = getBridge().globalComponentStorage override val resolvedPlugins: MutableList<Plugin> get() = MiraiConsole.pluginManagerImpl.resolvedPlugins } diff --git a/mirai-console/backend/mirai-console/src/command/BuiltInCommands.kt b/mirai-console/backend/mirai-console/src/command/BuiltInCommands.kt index 74fe12dbf..4b24c829c 100644 --- a/mirai-console/backend/mirai-console/src/command/BuiltInCommands.kt +++ b/mirai-console/backend/mirai-console/src/command/BuiltInCommands.kt @@ -28,6 +28,7 @@ import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.* import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.PasswordKind.MD5 import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.PasswordKind.PLAIN +import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage import net.mamoe.mirai.console.internal.permission.BuiltInPermissionService import net.mamoe.mirai.console.internal.pluginManagerImpl import net.mamoe.mirai.console.internal.util.autoHexToBytes @@ -484,7 +485,7 @@ public object BuiltInCommands { lightYellow() "Built In Permission Service" } else { - val plugin = PermissionServiceProvider.providerPlugin + val plugin = GlobalComponentStorage.getPreferredExtension(PermissionServiceProvider).plugin if (plugin == null) { PermissionService.INSTANCE.toString() } else { diff --git a/mirai-console/backend/mirai-console/src/command/parse/CommandCallParser.kt b/mirai-console/backend/mirai-console/src/command/parse/CommandCallParser.kt index 8a8f76b25..b7862a233 100644 --- a/mirai-console/backend/mirai-console/src/command/parse/CommandCallParser.kt +++ b/mirai-console/backend/mirai-console/src/command/parse/CommandCallParser.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 Mamoe Technologies and contributors. + * Copyright 2019-2022 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. @@ -44,10 +44,8 @@ public interface CommandCallParser { */ @JvmStatic public fun MessageChain.parseCommandCall(sender: CommandSender): CommandCall? { - GlobalComponentStorage.run { - CommandCallParserProvider.useExtensions { provider -> - provider.instance.parse(sender, this@parseCommandCall)?.let { return it } - } + GlobalComponentStorage.useEachExtensions(CommandCallParserProvider) { provider -> + provider.instance.parse(sender, this@parseCommandCall)?.let { return it } } return null } diff --git a/mirai-console/backend/mirai-console/src/command/parse/SpaceSeparatedCommandCallParser.kt b/mirai-console/backend/mirai-console/src/command/parse/SpaceSeparatedCommandCallParser.kt index 68648b849..f2496d635 100644 --- a/mirai-console/backend/mirai-console/src/command/parse/SpaceSeparatedCommandCallParser.kt +++ b/mirai-console/backend/mirai-console/src/command/parse/SpaceSeparatedCommandCallParser.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 Mamoe Technologies and contributors. + * Copyright 2019-2022 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. @@ -12,7 +12,6 @@ package net.mamoe.mirai.console.command.parse import net.mamoe.mirai.console.command.CommandSender import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.extensions.CommandCallParserProvider -import net.mamoe.mirai.console.extensions.CommandCallParserProviderImpl import net.mamoe.mirai.console.internal.command.flattenCommandComponents import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.message.data.MessageChain @@ -22,6 +21,13 @@ import net.mamoe.mirai.message.data.content @ConsoleExperimentalApi @ExperimentalCommandDescriptors public object SpaceSeparatedCommandCallParser : CommandCallParser { + + @ConsoleExperimentalApi + @ExperimentalCommandDescriptors + public object Provider : CommandCallParserProvider { + override val instance: CommandCallParser get() = SpaceSeparatedCommandCallParser + } + override fun parse(caller: CommandSender, message: MessageChain): CommandCall? { val flatten = message.flattenCommandComponents().filterIsInstance<MessageContent>() if (flatten.isEmpty()) return null @@ -31,6 +37,4 @@ public object SpaceSeparatedCommandCallParser : CommandCallParser { valueArguments = flatten.drop(1).map(::DefaultCommandValueArgument) ) } - - public object Provider : CommandCallParserProvider by CommandCallParserProviderImpl(SpaceSeparatedCommandCallParser) } \ No newline at end of file diff --git a/mirai-console/backend/mirai-console/src/command/resolve/BuiltInCommandCallResolver.kt b/mirai-console/backend/mirai-console/src/command/resolve/BuiltInCommandCallResolver.kt index 12378024d..898888204 100644 --- a/mirai-console/backend/mirai-console/src/command/resolve/BuiltInCommandCallResolver.kt +++ b/mirai-console/backend/mirai-console/src/command/resolve/BuiltInCommandCallResolver.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 Mamoe Technologies and contributors. + * Copyright 2019-2022 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. @@ -15,6 +15,7 @@ import net.mamoe.mirai.console.command.descriptor.ArgumentAcceptance.Companion.i import net.mamoe.mirai.console.command.parse.CommandCall import net.mamoe.mirai.console.command.parse.CommandValueArgument import net.mamoe.mirai.console.command.parse.DefaultCommandValueArgument +import net.mamoe.mirai.console.extensions.CommandCallResolverProvider import net.mamoe.mirai.console.internal.data.classifierAsKClass import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.safeCast @@ -27,6 +28,11 @@ import net.mamoe.mirai.message.data.toMessageChain @ConsoleExperimentalApi @ExperimentalCommandDescriptors public object BuiltInCommandCallResolver : CommandCallResolver { + + internal object Provider : CommandCallResolverProvider { + override val instance: CommandCallResolver = BuiltInCommandCallResolver + } + override fun resolve(call: CommandCall): CommandResolveResult { val callee = CommandManager.matchCommand(call.calleeName) ?: return CommandResolveResult(CommandExecuteResult.UnresolvedCommand(call)) diff --git a/mirai-console/backend/mirai-console/src/command/resolve/CommandCallInterceptor.kt b/mirai-console/backend/mirai-console/src/command/resolve/CommandCallInterceptor.kt index bf7529bd3..845e3503d 100644 --- a/mirai-console/backend/mirai-console/src/command/resolve/CommandCallInterceptor.kt +++ b/mirai-console/backend/mirai-console/src/command/resolve/CommandCallInterceptor.kt @@ -66,15 +66,13 @@ public interface CommandCallInterceptor { */ @JvmStatic public fun Message.intercepted(caller: CommandSender): InterceptResult<Message> { - GlobalComponentStorage.run { - return CommandCallInterceptorProvider.foldExtensions(this@intercepted) { acc, ext -> - val intercepted = ext.instance.interceptBeforeCall(acc, caller) - intercepted?.fold( - onIntercepted = { return intercepted }, - otherwise = { it } - ) ?: acc - }.let { InterceptResult(it) } - } + return GlobalComponentStorage.foldExtensions(CommandCallInterceptorProvider, this@intercepted) { acc, ext -> + val intercepted = ext.instance.interceptBeforeCall(acc, caller) + intercepted?.fold( + onIntercepted = { return intercepted }, + otherwise = { it } + ) ?: acc + }.let { InterceptResult(it) } } /** @@ -83,15 +81,13 @@ public interface CommandCallInterceptor { */ @JvmStatic public fun CommandCall.intercepted(): InterceptResult<CommandCall> { - GlobalComponentStorage.run { - return CommandCallInterceptorProvider.foldExtensions(this@intercepted) { acc, ext -> - val intercepted = ext.instance.interceptCall(acc) - intercepted?.fold( - onIntercepted = { return intercepted }, - otherwise = { it } - ) ?: acc - }.let { InterceptResult(it) } - } + return GlobalComponentStorage.foldExtensions(CommandCallInterceptorProvider, this@intercepted) { acc, ext -> + val intercepted = ext.instance.interceptCall(acc) + intercepted?.fold( + onIntercepted = { return intercepted }, + otherwise = { it } + ) ?: acc + }.let { InterceptResult(it) } } /** @@ -100,15 +96,13 @@ public interface CommandCallInterceptor { */ @JvmStatic public fun ResolvedCommandCall.intercepted(): InterceptResult<ResolvedCommandCall> { - GlobalComponentStorage.run { - return CommandCallInterceptorProvider.foldExtensions(this@intercepted) { acc, ext -> - val intercepted = ext.instance.interceptResolvedCall(acc) - intercepted?.fold( - onIntercepted = { return intercepted }, - otherwise = { it } - ) ?: acc - }.let { InterceptResult(it) } - } + return GlobalComponentStorage.foldExtensions(CommandCallInterceptorProvider, this@intercepted) { acc, ext -> + val intercepted = ext.instance.interceptResolvedCall(acc) + intercepted?.fold( + onIntercepted = { return intercepted }, + otherwise = { it } + ) ?: acc + }.let { InterceptResult(it) } } } } diff --git a/mirai-console/backend/mirai-console/src/command/resolve/CommandCallResolver.kt b/mirai-console/backend/mirai-console/src/command/resolve/CommandCallResolver.kt index 6788e31fd..86465fc53 100644 --- a/mirai-console/backend/mirai-console/src/command/resolve/CommandCallResolver.kt +++ b/mirai-console/backend/mirai-console/src/command/resolve/CommandCallResolver.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 Mamoe Technologies and contributors. + * Copyright 2019-2022 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. @@ -76,11 +76,9 @@ public interface CommandCallResolver { @ConsoleExperimentalApi @ExperimentalCommandDescriptors public fun CommandCall.resolve(): CommandResolveResult { - GlobalComponentStorage.run { - val instance = - CommandCallResolverProvider.findSingletonInstance(CommandCallResolverProvider.builtinImplementation) - return instance.resolve(this@resolve) - } + return GlobalComponentStorage.getExtensions(CommandCallResolverProvider).first() + .extension.instance + .resolve(this@resolve) } } } \ No newline at end of file diff --git a/mirai-console/backend/mirai-console/src/extension/ComponentStorage.kt b/mirai-console/backend/mirai-console/src/extension/ComponentStorage.kt index 27a1abbda..40970eeae 100644 --- a/mirai-console/backend/mirai-console/src/extension/ComponentStorage.kt +++ b/mirai-console/backend/mirai-console/src/extension/ComponentStorage.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 Mamoe Technologies and contributors. + * Copyright 2019-2022 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. @@ -9,27 +9,99 @@ package net.mamoe.mirai.console.extension +import net.mamoe.mirai.console.ConsoleFrontEndImplementation +import net.mamoe.mirai.console.MiraiConsoleImplementation import net.mamoe.mirai.console.plugin.Plugin import net.mamoe.mirai.console.plugin.jvm.JvmPlugin import net.mamoe.mirai.console.plugin.jvm.JvmPlugin.Companion.onLoad +import net.mamoe.mirai.utils.MiraiExperimentalApi +import java.util.stream.Stream /** * 组件容器, 容纳 [Plugin] 注册的 [Extension]. * + * 插件可在 [JvmPlugin.onLoad] 时提供扩展. 前端可在 [MiraiConsoleImplementation.BackendAccess.globalComponentStorage] 获取全局组件容器. + * 目前未允许获取全局组件容器. 如有需求请 [提交 issues](https://github.com/mamoe/mirai/issues/new/choose). + * + * 实现细节: 线程安全. + * * @see Extension * @see JvmPlugin.onLoad */ public interface ComponentStorage { - public fun <T : Extension> contribute( - extensionPoint: ExtensionPoint<T>, - plugin: Plugin, - extensionInstance: T, - ) - public fun <T : Extension> contribute( - extensionPoint: ExtensionPoint<T>, + /** + * 注册一个扩展 + */ + public fun <E : Extension> contribute( + extensionPoint: ExtensionPoint<E>, plugin: Plugin, - lazyInstance: () -> T, - ) + extensionInstance: E, + ) // 2.11: E changed to T (only naming) + + /** + * 注册一个扩展. [lazyInstance] 将会在 [getExtensions] 时才会计算. + */ + public fun <E : Extension> contribute( + extensionPoint: ExtensionPoint<E>, + plugin: Plugin, + lazyInstance: () -> E, + ) // 2.11: E changed to T (only naming) + + /** + * 获取优先级最高的 [ExtensionPoint] 扩展实例. 在未找到任何注册的实例时抛出 [NoSuchElementException]. + * + * @since 2.11 + */ + @MiraiExperimentalApi + public fun <E : Extension> getPreferredExtension( + extensionPoint: ExtensionPoint<E>, + ): ExtensionRegistry<E> { + return getExtensions(extensionPoint).firstOrNull() + ?: throw NoSuchElementException("No extension registered for $extensionPoint") + } + + /** + * 获取注册的 [ExtensionPoint] 扩展实例列表. 返回的 [Sequence] 以 [Extension.priority] 倒序排序. + * + * @since 2.11 + */ + public fun <E : Extension> getExtensions( + extensionPoint: ExtensionPoint<E>, + ): Sequence<ExtensionRegistry<E>> + + /** + * 获取注册的 [ExtensionPoint] 扩展实例列表. 返回的 [Stream] 以 [Extension.priority] 倒序排序. + * + * @since 2.11 + */ + public fun <E : Extension> getExtensionsStream( + extensionPoint: ExtensionPoint<E>, + ): Stream<ExtensionRegistry<E>> } +/** + * 仅前端实现可用 + */ +@ConsoleFrontEndImplementation +public interface ComponentStorageInternal : ComponentStorage { + + /** + * 注册一个由 Mirai Console 实现的扩展 (因此没有相关 [Plugin]). [lazyInstance] 将会在 [getExtensions] 时才会计算. + */ + public fun <E : Extension> contributeConsole( + extensionPoint: ExtensionPoint<E>, + lazyInstance: () -> E, + ) + + /** + * 注册一个由 Mirai Console 实现的扩展 (因此没有相关 [Plugin]). + */ + public fun <E : Extension> contributeConsole( + extensionPoint: ExtensionPoint<E>, + instance: E, + ) { + @Suppress("USELESS_CAST") // bug + contributeConsole(extensionPoint, { instance } as () -> E) + } +} diff --git a/mirai-console/backend/mirai-console/src/extension/Extension.kt b/mirai-console/backend/mirai-console/src/extension/Extension.kt index 34a4d9d81..fc5721fa0 100644 --- a/mirai-console/backend/mirai-console/src/extension/Extension.kt +++ b/mirai-console/backend/mirai-console/src/extension/Extension.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 Mamoe Technologies and contributors. + * Copyright 2019-2022 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. @@ -9,12 +9,15 @@ package net.mamoe.mirai.console.extension +import net.mamoe.mirai.console.command.parse.SpaceSeparatedCommandCallParser import net.mamoe.mirai.console.extensions.PermissionServiceProvider import net.mamoe.mirai.console.extensions.PluginLoaderProvider import net.mamoe.mirai.console.extensions.SingletonExtensionSelector import net.mamoe.mirai.console.extensions.SingletonExtensionSelector.ExtensionPoint.selectSingleton import net.mamoe.mirai.console.plugin.jvm.JvmPlugin import net.mamoe.mirai.console.plugin.jvm.JvmPlugin.Companion.onLoad +import net.mamoe.mirai.console.util.ConsoleExperimentalApi +import net.mamoe.mirai.utils.DeprecatedSinceMirai /** * 表示一个扩展. @@ -37,7 +40,18 @@ import net.mamoe.mirai.console.plugin.jvm.JvmPlugin.Companion.onLoad * * @see ComponentStorage */ -public interface Extension +public interface Extension { + /** + * 优先级. 越高越先使用. 内嵌的 [SpaceSeparatedCommandCallParser] 拥有优先级 0. + * + * 若两个 [InstanceExtension] 有相同的优先级, 将会优先使用内嵌的实现, 再按 [ComponentStorage.contribute] 顺序依次使用. + * + * @since 2.11 + */ // https://github.com/mamoe/mirai/issues/1860 + @ConsoleExperimentalApi + public val priority: Int + get() = 0 +} /** * 增加一些函数 (方法)的扩展 @@ -51,6 +65,8 @@ public interface FunctionExtension : Extension * * @see PermissionServiceProvider */ +@Deprecated("Please use InstanceExtension instead.", replaceWith = ReplaceWith("InstanceExtension")) +@DeprecatedSinceMirai(warningSince = "2.11") public interface SingletonExtension<T> : Extension { public val instance: T } diff --git a/mirai-console/backend/mirai-console/src/extension/ExtensionPoint.kt b/mirai-console/backend/mirai-console/src/extension/ExtensionPoint.kt index f861b92ed..ddd28ac2a 100644 --- a/mirai-console/backend/mirai-console/src/extension/ExtensionPoint.kt +++ b/mirai-console/backend/mirai-console/src/extension/ExtensionPoint.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 Mamoe Technologies and contributors. + * Copyright 2019-2022 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. @@ -12,13 +12,15 @@ package net.mamoe.mirai.console.extension import net.mamoe.mirai.console.extensions.SingletonExtensionSelector -import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage import net.mamoe.mirai.console.util.ConsoleExperimentalApi +import net.mamoe.mirai.utils.DeprecatedSinceMirai import kotlin.reflect.KClass /** - * 由 [Extension] 的伴生对象实现. + * 表示一个扩展接入点(扩展类型). 在 Kotlin 由 [Extension] 的伴生对象实现, 在 Java 可通过静态字段提供. + * + * 在[注册扩展][ComponentStorage.contribute]时需要提供其 [ExtensionPoint], [ExtensionPoint] 也可以用于获取所有注册的扩展 ([ComponentStorage.getExtensions]). * * @see AbstractExtensionPoint */ @@ -37,6 +39,8 @@ public abstract class AbstractExtensionPoint<T : Extension>( /** * 表示一个 [SingletonExtension] 的 [ExtensionPoint] */ +@Deprecated("Please use InstanceExtensionPoint instead.", replaceWith = ReplaceWith("InstanceExtensionPoint")) +@DeprecatedSinceMirai(warningSince = "2.11") public interface SingletonExtensionPoint<T : SingletonExtension<*>> : ExtensionPoint<T> /** @@ -55,34 +59,56 @@ public abstract class AbstractInstanceExtensionPoint<E : InstanceExtension<T>, T * @since 2.10 */ @ConsoleExperimentalApi -constructor( - extensionType: KClass<E>, - /** - * 内建的实现列表. - */ - @ConsoleExperimentalApi - public vararg val builtinImplementations: () -> E, +public constructor( + extensionType: KClass<E> ) : AbstractExtensionPoint<E>(extensionType) { /** * @since 2.10 */ + @Deprecated( + "Default(builtin) implementations are not allowed any more. " + + "For plugin authors, provide them with lower priority when plugin being loaded(through the ComponentScope). " + + "For frontend implementers, provide them by `BackendAccess.globalComponentScope.contribute`. ", + replaceWith = ReplaceWith("AbstractInstanceExtensionPoint(extensionType)"), + level = DeprecationLevel.ERROR, + ) + @DeprecatedSinceMirai(errorSince = "2.11") // for removal @ConsoleExperimentalApi - public constructor(extensionType: KClass<E>) : this( - extensionType, - builtinImplementations = arrayOf<() -> E>() as Array<out () -> E> - ) // to avoid resolution ambiguity + public constructor( + extensionType: KClass<E>, + /** + * 内建的实现列表. + */ + builtinImplementations: () -> E, + ) : this(extensionType) /** * @since 2.0 */ - @ConsoleExperimentalApi + @Deprecated( + "Default(builtin) implementations are not allowed any more. " + + "For plugin authors, provide them with lower priority when plugin being loaded(through the ComponentScope). " + + "For frontend implementers, provide them by `BackendAccess.globalComponentScope.contribute`. ", + replaceWith = ReplaceWith("AbstractInstanceExtensionPoint(extensionType)"), + level = DeprecationLevel.ERROR + ) + @DeprecatedSinceMirai(errorSince = "2.11") // for removal + @ConsoleExperimentalApi // was experimental since 2.0 public constructor(extensionType: KClass<E>, vararg builtinImplementations: E) : this( extensionType, - builtinImplementations = builtinImplementations.map { { it } }.toTypedArray() ) } +@Deprecated( + "Please use AbstractInstanceExtensionPoint instead.", + replaceWith = ReplaceWith( + "AbstractInstanceExtension", + "net.mamoe.mirai.console.extension.AbstractInstanceExtensionPoint" + ) +) +@DeprecatedSinceMirai(warningSince = "2.11") +@Suppress("DEPRECATION") public abstract class AbstractSingletonExtensionPoint<E : SingletonExtension<T>, T> /** * @since 2.10 @@ -111,12 +137,6 @@ constructor( * 由 [SingletonExtensionSelector] 选择后的实例. */ @ConsoleExperimentalApi - public open val selectedInstance: T by lazy { - GlobalComponentStorage.run { - this@AbstractSingletonExtensionPoint.findSingletonInstance( - extensionType, - builtinImplementation - ) - } - } + public open val selectedInstance: T + get() = throw UnsupportedOperationException("SingletonExtension has been deprecated.") } \ No newline at end of file diff --git a/mirai-console/backend/mirai-console/src/extension/ExtensionRegistry.kt b/mirai-console/backend/mirai-console/src/extension/ExtensionRegistry.kt new file mode 100644 index 000000000..c950c292b --- /dev/null +++ b/mirai-console/backend/mirai-console/src/extension/ExtensionRegistry.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2019-2022 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/dev/LICENSE + */ + +package net.mamoe.mirai.console.extension + +import net.mamoe.mirai.console.plugin.Plugin +import net.mamoe.mirai.utils.NotStableForInheritance + +/** + * 一个已经注册的 [Extension]. 可通过 [ComponentStorage.getExtensions] 获得. + * + * @since 2.11 + */ +@NotStableForInheritance +public interface ExtensionRegistry<E : Extension> { + /** + * 提供该 [ExtensionRegistry] 的插件. 若为 `null` 则表示由 Mirai Console 内置或者由前端实现. + */ + public val plugin: Plugin? + + /** + * [Extension] 实例. + */ + public val extension: E +} + +internal inline val <T> ExtensionRegistry<out InstanceExtension<T>>.instance get() = extension.instance + +internal class ExtensionRegistryImpl<E : Extension> internal constructor( + /** + * 提供该 [ExtensionRegistry] 的插件. 若为 `null` 则表示由 Mirai Console 内置或者由前端实现. + */ + override val plugin: Plugin?, + /** + * [Extension] 实例. + */ + extensionInitializer: () -> E, +) : ExtensionRegistry<E> { + override val extension: E by lazy(extensionInitializer) +} diff --git a/mirai-console/backend/mirai-console/src/extensions/CommandCallInterceptorProvider.kt b/mirai-console/backend/mirai-console/src/extensions/CommandCallInterceptorProvider.kt index 0b5ffcc74..25dfbc9cd 100644 --- a/mirai-console/backend/mirai-console/src/extensions/CommandCallInterceptorProvider.kt +++ b/mirai-console/backend/mirai-console/src/extensions/CommandCallInterceptorProvider.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 Mamoe Technologies and contributors. + * Copyright 2019-2022 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. @@ -13,19 +13,25 @@ import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.resolve.CommandCallInterceptor import net.mamoe.mirai.console.extension.AbstractInstanceExtensionPoint import net.mamoe.mirai.console.extension.InstanceExtension +import net.mamoe.mirai.utils.DeprecatedSinceMirai @ExperimentalCommandDescriptors public interface CommandCallInterceptorProvider : InstanceExtension<CommandCallInterceptor> { + @ExperimentalCommandDescriptors public companion object ExtensionPoint : AbstractInstanceExtensionPoint<CommandCallInterceptorProvider, CommandCallInterceptor>( CommandCallInterceptorProvider::class ) } +@Deprecated("Deprecated for removal. Please implement your own CommandCallInterceptorProvider.") +@DeprecatedSinceMirai(warningSince = "2.11") // for removal. @ExperimentalCommandDescriptors public class CommandCallInterceptorProviderImpl(override val instance: CommandCallInterceptor) : CommandCallInterceptorProvider +@Deprecated("Deprecated for removal. Please implement your own CommandCallInterceptorProvider.") +@DeprecatedSinceMirai(warningSince = "2.11") // for removal. @ExperimentalCommandDescriptors public class CommandCallInterceptorProviderImplLazy(initializer: () -> CommandCallInterceptor) : CommandCallInterceptorProvider { diff --git a/mirai-console/backend/mirai-console/src/extensions/CommandCallParserProvider.kt b/mirai-console/backend/mirai-console/src/extensions/CommandCallParserProvider.kt index 8c8d4ca99..f20075007 100644 --- a/mirai-console/backend/mirai-console/src/extensions/CommandCallParserProvider.kt +++ b/mirai-console/backend/mirai-console/src/extensions/CommandCallParserProvider.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 Mamoe Technologies and contributors. + * Copyright 2019-2022 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. @@ -11,26 +11,27 @@ package net.mamoe.mirai.console.extensions import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.parse.CommandCallParser -import net.mamoe.mirai.console.command.parse.SpaceSeparatedCommandCallParser import net.mamoe.mirai.console.extension.AbstractInstanceExtensionPoint import net.mamoe.mirai.console.extension.InstanceExtension +import net.mamoe.mirai.utils.DeprecatedSinceMirai /** * The provider of [CommandCallParser] */ @ExperimentalCommandDescriptors public interface CommandCallParserProvider : InstanceExtension<CommandCallParser> { + @ExperimentalCommandDescriptors public companion object ExtensionPoint : - AbstractInstanceExtensionPoint<CommandCallParserProvider, CommandCallParser>( - CommandCallParserProvider::class, - SpaceSeparatedCommandCallParser.Provider - ) + AbstractInstanceExtensionPoint<CommandCallParserProvider, CommandCallParser>(CommandCallParserProvider::class) } - +@Deprecated("Deprecated for removal. Please implement your own CommandCallParserProvider.") +@DeprecatedSinceMirai(warningSince = "2.11") // for removal. @ExperimentalCommandDescriptors public class CommandCallParserProviderImpl(override val instance: CommandCallParser) : CommandCallParserProvider +@Deprecated("Deprecated for removal. Please implement your own CommandCallParserProvider.") +@DeprecatedSinceMirai(warningSince = "2.11") // for removal. @ExperimentalCommandDescriptors public class CommandCallParserProviderImplLazy(initializer: () -> CommandCallParser) : CommandCallParserProvider { override val instance: CommandCallParser by lazy(initializer) diff --git a/mirai-console/backend/mirai-console/src/extensions/CommandCallResolverProvider.kt b/mirai-console/backend/mirai-console/src/extensions/CommandCallResolverProvider.kt index 3181a9045..fdbdc9943 100644 --- a/mirai-console/backend/mirai-console/src/extensions/CommandCallResolverProvider.kt +++ b/mirai-console/backend/mirai-console/src/extensions/CommandCallResolverProvider.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 Mamoe Technologies and contributors. + * Copyright 2019-2022 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. @@ -10,23 +10,26 @@ package net.mamoe.mirai.console.extensions import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors -import net.mamoe.mirai.console.command.resolve.BuiltInCommandCallResolver import net.mamoe.mirai.console.command.resolve.CommandCallResolver -import net.mamoe.mirai.console.extension.AbstractSingletonExtensionPoint -import net.mamoe.mirai.console.extension.SingletonExtension +import net.mamoe.mirai.console.extension.AbstractInstanceExtensionPoint +import net.mamoe.mirai.console.extension.InstanceExtension +import net.mamoe.mirai.utils.DeprecatedSinceMirai @ExperimentalCommandDescriptors -public interface CommandCallResolverProvider : SingletonExtension<CommandCallResolver> { +public interface CommandCallResolverProvider : InstanceExtension<CommandCallResolver> { + + @ExperimentalCommandDescriptors public companion object ExtensionPoint : - AbstractSingletonExtensionPoint<CommandCallResolverProvider, CommandCallResolver>( - CommandCallResolverProvider::class, - { BuiltInCommandCallResolver } - ) + AbstractInstanceExtensionPoint<CommandCallResolverProvider, CommandCallResolver>(CommandCallResolverProvider::class) } +@Deprecated("Deprecated for removal. Please implement your own CommandCallResolverProvider.") +@DeprecatedSinceMirai(warningSince = "2.11") // for removal. @ExperimentalCommandDescriptors public class CommandCallResolverProviderImpl(override val instance: CommandCallResolver) : CommandCallResolverProvider +@Deprecated("Deprecated for removal. Please implement your own CommandCallResolverProvider.") +@DeprecatedSinceMirai(warningSince = "2.11") // for removal. @ExperimentalCommandDescriptors public class CommandCallResolverProviderImplLazy(initializer: () -> CommandCallResolver) : CommandCallResolverProvider { override val instance: CommandCallResolver by lazy(initializer) diff --git a/mirai-console/backend/mirai-console/src/extensions/PermissionServiceProvider.kt b/mirai-console/backend/mirai-console/src/extensions/PermissionServiceProvider.kt index ce2f1ec6e..c880ed105 100644 --- a/mirai-console/backend/mirai-console/src/extensions/PermissionServiceProvider.kt +++ b/mirai-console/backend/mirai-console/src/extensions/PermissionServiceProvider.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 Mamoe Technologies and contributors. + * Copyright 2019-2022 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. @@ -9,55 +9,35 @@ package net.mamoe.mirai.console.extensions -import net.mamoe.mirai.console.extension.AbstractSingletonExtensionPoint -import net.mamoe.mirai.console.extension.SingletonExtension -import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage -import net.mamoe.mirai.console.internal.permission.BuiltInPermissionService +import net.mamoe.mirai.console.extension.AbstractInstanceExtensionPoint +import net.mamoe.mirai.console.extension.InstanceExtension import net.mamoe.mirai.console.permission.PermissionService -import net.mamoe.mirai.console.plugin.Plugin -import net.mamoe.mirai.console.util.ConsoleExperimentalApi +import net.mamoe.mirai.utils.DeprecatedSinceMirai /** * [权限服务][PermissionService] 提供器. * * 当插件注册 [PermissionService] 后, 默认会使用插件的 [PermissionService]. */ -public interface PermissionServiceProvider : SingletonExtension<PermissionService<*>> { +public interface PermissionServiceProvider : InstanceExtension<PermissionService<*>> { + public companion object ExtensionPoint : - AbstractSingletonExtensionPoint<PermissionServiceProvider, PermissionService<*>>( - PermissionServiceProvider::class, - { BuiltInPermissionService() } - ) { - internal var permissionServiceOk = false - - @ConsoleExperimentalApi - public val providerPlugin: Plugin? by lazy { - GlobalComponentStorage.run { - val instance = PermissionService.INSTANCE - if (instance is BuiltInPermissionService) return@lazy null - PermissionServiceProvider.getExtensions().find { it.extension.instance === instance }?.plugin - } - } - - @ConsoleExperimentalApi - override val selectedInstance: PermissionService<*> - get() { - if (!permissionServiceOk) { - error("PermissionService not yet loaded") - } - return super.selectedInstance - } - } + AbstractInstanceExtensionPoint<PermissionServiceProvider, PermissionService<*>>(PermissionServiceProvider::class) + // ! BREAKING CHANGE MADE IN 2.11: supertype changed from AbstractSingletonExtensionPoint to AbstractInstanceExtensionPoint } /** * @see PermissionServiceProvider */ +@Deprecated("Please implement your own PermissionServiceProvider.") +@DeprecatedSinceMirai(warningSince = "2.11") // for hidden. public class PermissionServiceProviderImpl(override val instance: PermissionService<*>) : PermissionServiceProvider /** * @see PermissionServiceProvider */ +@Deprecated("Please implement your own PermissionServiceProvider.") +@DeprecatedSinceMirai(warningSince = "2.11") // for hidden. public class PermissionServiceProviderImplLazy(initializer: () -> PermissionService<*>) : PermissionServiceProvider { override val instance: PermissionService<*> by lazy(initializer) } diff --git a/mirai-console/backend/mirai-console/src/extensions/PluginLoaderProvider.kt b/mirai-console/backend/mirai-console/src/extensions/PluginLoaderProvider.kt index 0cc1938a4..53448610f 100644 --- a/mirai-console/backend/mirai-console/src/extensions/PluginLoaderProvider.kt +++ b/mirai-console/backend/mirai-console/src/extensions/PluginLoaderProvider.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 Mamoe Technologies and contributors. + * Copyright 2019-2022 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. @@ -14,6 +14,7 @@ import net.mamoe.mirai.console.extension.Extension import net.mamoe.mirai.console.extension.InstanceExtension import net.mamoe.mirai.console.extension.PluginComponentStorage import net.mamoe.mirai.console.plugin.loader.PluginLoader +import net.mamoe.mirai.utils.DeprecatedSinceMirai /** * 提供扩展 [PluginLoader] @@ -30,8 +31,12 @@ public interface PluginLoaderProvider : InstanceExtension<PluginLoader<*, *>> { public companion object ExtensionPoint : AbstractExtensionPoint<PluginLoaderProvider>(PluginLoaderProvider::class) } +@Deprecated("Please implement your own PluginLoaderProvider.") +@DeprecatedSinceMirai(warningSince = "2.11") // for hidden. public class PluginLoaderProviderImpl(override val instance: PluginLoader<*, *>) : PluginLoaderProvider +@Deprecated("Please implement your own PluginLoaderProvider.") +@DeprecatedSinceMirai(warningSince = "2.11") // for hidden. public class PluginLoaderProviderImplLazy(initializer: () -> PluginLoader<*, *>) : PluginLoaderProvider { override val instance: PluginLoader<*, *> by lazy(initializer) } \ No newline at end of file diff --git a/mirai-console/backend/mirai-console/src/extensions/SingletonExtensionSelector.kt b/mirai-console/backend/mirai-console/src/extensions/SingletonExtensionSelector.kt index 72f66b6c6..265e67722 100644 --- a/mirai-console/backend/mirai-console/src/extensions/SingletonExtensionSelector.kt +++ b/mirai-console/backend/mirai-console/src/extensions/SingletonExtensionSelector.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 Mamoe Technologies and contributors. + * Copyright 2019-2022 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. @@ -7,18 +7,17 @@ * https://github.com/mamoe/mirai/blob/dev/LICENSE */ +@file:Suppress("DEPRECATION") + package net.mamoe.mirai.console.extensions import net.mamoe.mirai.console.MiraiConsole -import net.mamoe.mirai.console.extension.AbstractExtensionPoint -import net.mamoe.mirai.console.extension.Extension -import net.mamoe.mirai.console.extension.FunctionExtension -import net.mamoe.mirai.console.extension.SingletonExtension -import net.mamoe.mirai.console.internal.extension.SingletonExtensionSelectorImpl -import net.mamoe.mirai.console.internal.extension.ExtensionRegistry +import net.mamoe.mirai.console.extension.* import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage +import net.mamoe.mirai.console.internal.extension.SingletonExtensionSelectorImpl import net.mamoe.mirai.console.plugin.Plugin import net.mamoe.mirai.console.plugin.name +import net.mamoe.mirai.utils.DeprecatedSinceMirai import net.mamoe.mirai.utils.info import kotlin.reflect.KClass @@ -27,10 +26,20 @@ import kotlin.reflect.KClass * * 如有多个 [SingletonExtensionSelector] 注册, 将会停止服务器. */ +@Deprecated( + "Order of extensions is now determined by its priority property since 2.11. SingletonExtensionSelector is not needed anymore. ", + level = DeprecationLevel.WARNING +) +@DeprecatedSinceMirai(warningSince = "2.11") public interface SingletonExtensionSelector : FunctionExtension { /** * 表示一个插件注册的 [Extension] */ + @Deprecated( + "Order of extensions is now determined by its priority property since 2.11. SingletonExtensionSelector is not needed anymore. ", + level = DeprecationLevel.WARNING + ) + @DeprecatedSinceMirai(warningSince = "2.11") public data class Registry<T : Extension>( val plugin: Plugin?, val extension: T, @@ -44,6 +53,11 @@ public interface SingletonExtensionSelector : FunctionExtension { candidates: Collection<Registry<T>>, ): T? + @Deprecated( + "Order of extensions is now determined by its priority property since 2.11. SingletonExtensionSelector is not needed anymore. ", + level = DeprecationLevel.WARNING + ) + @DeprecatedSinceMirai(warningSince = "2.11") public companion object ExtensionPoint : AbstractExtensionPoint<SingletonExtensionSelector>(SingletonExtensionSelector::class) { @@ -53,16 +67,22 @@ public interface SingletonExtensionSelector : FunctionExtension { internal fun init() { check(instanceField == null) { "Internal error: reinitialize SingletonExtensionSelector" } - val instances = GlobalComponentStorage.run { SingletonExtensionSelector.getExtensions() } + val instances = GlobalComponentStorage.getExtensions(ExtensionPoint).toList() instanceField = when { instances.isEmpty() -> SingletonExtensionSelectorImpl instances.size == 1 -> { - instances.single().also { (plugin, ext) -> - MiraiConsole.mainLogger.info { "Loaded SingletonExtensionSelector: $ext from ${plugin?.name ?: "<builtin>"}" } + instances.single().also { registry -> + MiraiConsole.mainLogger.info { "Loaded SingletonExtensionSelector: ${registry.extension} from ${registry.plugin?.name ?: "<builtin>"}" } }.extension } else -> { - error("Found too many SingletonExtensionSelectors: ${instances.joinToString { (p, i) -> "'$i' from '${p?.name ?: "<builtin>"}'" }}. Check your plugins and ensure there is only one external SingletonExtensionSelectors") + val hint = instances.joinToString { reg -> + "'${reg.extension}' from '${reg.plugin?.name ?: "<builtin>"}'" + } + error( + "Found too many SingletonExtensionSelectors: $hint. " + + "Check your plugins and ensure there is only one external SingletonExtensionSelectors" + ) } } } diff --git a/mirai-console/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt b/mirai-console/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt index b8448aedc..4434fc256 100644 --- a/mirai-console/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt +++ b/mirai-console/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt @@ -22,9 +22,13 @@ import net.mamoe.mirai.console.MiraiConsoleImplementation import net.mamoe.mirai.console.command.BuiltInCommands import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.command.ConsoleCommandSender +import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors +import net.mamoe.mirai.console.command.parse.SpaceSeparatedCommandCallParser +import net.mamoe.mirai.console.command.resolve.BuiltInCommandCallResolver +import net.mamoe.mirai.console.extensions.CommandCallParserProvider +import net.mamoe.mirai.console.extensions.CommandCallResolverProvider import net.mamoe.mirai.console.extensions.PermissionServiceProvider import net.mamoe.mirai.console.extensions.PostStartupExtension -import net.mamoe.mirai.console.extensions.SingletonExtensionSelector import net.mamoe.mirai.console.internal.command.CommandConfig import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.ConfigurationKey @@ -32,7 +36,7 @@ import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.Pa import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.PasswordKind.PLAIN import net.mamoe.mirai.console.internal.data.builtins.LoggerConfig import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage -import net.mamoe.mirai.console.internal.extension.SingletonExtensionSelectorImpl +import net.mamoe.mirai.console.internal.extension.GlobalComponentStorageImpl import net.mamoe.mirai.console.internal.logging.LoggerControllerImpl import net.mamoe.mirai.console.internal.logging.MiraiConsoleLogger import net.mamoe.mirai.console.internal.permission.BuiltInPermissionService @@ -77,6 +81,9 @@ internal class MiraiConsoleImplementationBridge( override val version: SemVersion by MiraiConsoleBuildConstants::version override val pluginManager: PluginManagerImpl by lazy { PluginManagerImpl(coroutineContext) } + // used internally + val globalComponentStorage: GlobalComponentStorageImpl by lazy { GlobalComponentStorageImpl() } + override val mainLogger: MiraiLogger by lazy { createLogger("main") } init { @@ -99,6 +106,15 @@ internal class MiraiConsoleImplementationBridge( internal fun doStart() { externalImplementation.preStart() + @OptIn(ExperimentalCommandDescriptors::class) + phase("register builtin componenets") { + GlobalComponentStorage.run { + contributeConsole(CommandCallParserProvider, SpaceSeparatedCommandCallParser.Provider) + contributeConsole(CommandCallResolverProvider, BuiltInCommandCallResolver.Provider) + contributeConsole(PermissionServiceProvider, BuiltInPermissionService.Provider()) + } + } + phase("setup logger controller") { if (loggerController === LoggerControllerImpl) { // Reload LoggerConfig. @@ -159,25 +175,30 @@ internal class MiraiConsoleImplementationBridge( mainLogger.verbose { "${PluginManager.plugins.size} plugin(s) loaded." } } - phase("load SingletonExtensionSelector") { - SingletonExtensionSelector.init() - val instance = SingletonExtensionSelector.instance - if (instance is SingletonExtensionSelectorImpl) { - consoleDataScope.addAndReloadConfig(instance.config) - } - } +// phase("load SingletonExtensionSelector") { +// SingletonExtensionSelector.init() +// val instance = SingletonExtensionSelector.instance +// if (instance is SingletonExtensionSelectorImpl) { +// consoleDataScope.addAndReloadConfig(instance.config) +// } +// } phase("load PermissionService") { mainLogger.verbose { "Loading PermissionService..." } - PermissionServiceProvider.permissionServiceOk = true PermissionService.INSTANCE.let { ps -> if (ps is BuiltInPermissionService) { consoleDataScope.addAndReloadConfig(ps.config) mainLogger.verbose { "Reloaded PermissionService settings." } } else { - mainLogger.info { "Loaded PermissionService from plugin ${PermissionServiceProvider.providerPlugin?.name}" } + mainLogger.info { + "Loaded PermissionService from plugin ${ + GlobalComponentStorage.getPreferredExtension( + PermissionServiceProvider + ).plugin?.name + }" + } } } @@ -258,9 +279,7 @@ internal class MiraiConsoleImplementationBridge( } phase("finally post") { - GlobalComponentStorage.run { - PostStartupExtension.useExtensions { it() } // exceptions thrown will be caught by caller of `doStart`. - } + globalComponentStorage.useEachExtensions(PostStartupExtension) { it.invoke() } } externalImplementation.postStart() diff --git a/mirai-console/backend/mirai-console/src/internal/extension/ComponentStorageInternal.kt b/mirai-console/backend/mirai-console/src/internal/extension/ComponentStorageInternal.kt index 5e8d4a755..618f7e849 100644 --- a/mirai-console/backend/mirai-console/src/internal/extension/ComponentStorageInternal.kt +++ b/mirai-console/backend/mirai-console/src/internal/extension/ComponentStorageInternal.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 Mamoe Technologies and contributors. + * Copyright 2019-2022 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. @@ -10,140 +10,116 @@ package net.mamoe.mirai.console.internal.extension import net.mamoe.mirai.console.extension.* -import net.mamoe.mirai.console.extensions.SingletonExtensionSelector -import net.mamoe.mirai.console.extensions.SingletonExtensionSelector.ExtensionPoint.selectSingleton +import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge import net.mamoe.mirai.console.internal.data.kClassQualifiedNameOrTip import net.mamoe.mirai.console.plugin.Plugin import net.mamoe.mirai.console.plugin.name +import java.util.* import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.CopyOnWriteArraySet +import java.util.stream.Stream import kotlin.contracts.contract -import kotlin.reflect.KClass + +internal class GlobalComponentStorageImpl : AbstractConcurrentComponentStorage() + +// source compatibility for <2.11 +internal val GlobalComponentStorage get() = MiraiConsoleImplementationBridge.globalComponentStorage /** - * The [ComponentStorage] containing all components provided by Mirai Console internals and installed plugins. + * thread-safe. */ -internal object GlobalComponentStorage : AbstractConcurrentComponentStorage() -internal interface ExtensionRegistry<out E : Extension> { - val plugin: Plugin? - val extension: E +internal abstract class AbstractConcurrentComponentStorage : ComponentStorage, ComponentStorageInternal { + /////////////////////////////////////////////////////////////////////////// + // registry implementation + /////////////////////////////////////////////////////////////////////////// - operator fun component1(): Plugin? { - return this.plugin - } + /** + * For each [ExtensionPoint]. thread-safe. + */ + internal class Registries<T : Extension> { + @Volatile + private var data: MutableCollection<ExtensionRegistry<T>> = ArrayList() + private val lock = Any() - operator fun component2(): E { - return this.extension - } -} - -internal class LazyExtensionRegistry<out E : Extension>( - override val plugin: Plugin?, - initializer: () -> E, -) : ExtensionRegistry<E> { - override val extension: E by lazy { initializer() } -} - -internal data class DataExtensionRegistry<out E : Extension>( - override val plugin: Plugin?, - override val extension: E, -) : ExtensionRegistry<E> - -internal abstract class AbstractConcurrentComponentStorage : ComponentStorage { - private val instances: MutableMap<ExtensionPoint<*>, MutableSet<ExtensionRegistry<*>>> = ConcurrentHashMap() - - @Suppress("UNCHECKED_CAST") - internal fun <T : Extension> ExtensionPoint<out T>.getExtensions(): Set<ExtensionRegistry<T>> { - val userDefined = instances.getOrPut(this, ::CopyOnWriteArraySet) as Set<ExtensionRegistry<T>> - - val builtins = if (this is AbstractInstanceExtensionPoint<*, *>) { - this.builtinImplementations.mapTo(HashSet()) { instance -> - DataExtensionRegistry( - null, - instance() + fun register(registry: ExtensionRegistry<T>) { + synchronized(lock) { + val list = PriorityQueue( + data.size + 1, + Comparator.comparing<ExtensionRegistry<T>, Int> { it.extension.priority }.reversed() ) - } as Set<ExtensionRegistry<T>> - } else null - - return builtins?.plus(userDefined) ?: userDefined - } - - // unused for now - internal fun removeExtensionsRegisteredByPlugin(plugin: Plugin) { - instances.forEach { (_, u) -> - u.removeAll { it.plugin == plugin } + list.addAll(data) + list.add(registry) + data = list + } } + + /** + * @return thread-safe Sequence + */ + fun asSequence(): Sequence<ExtensionRegistry<T>> = data.asSequence() + + /** + * @return thread-safe Sequence + */ + fun asStream(): Stream<ExtensionRegistry<T>> = data.stream() } + private val registries: MutableMap<ExtensionPoint<*>, Registries<*>> = ConcurrentHashMap() + + private fun <T : Extension> getRegistries(ep: ExtensionPoint<T>): Registries<T> { + @Suppress("UNCHECKED_CAST") + return registries.getOrPut(ep) { Registries<T>() } as Registries<T> + } + + private fun <T : Extension> registerExtension(ep: ExtensionPoint<T>, registry: ExtensionRegistry<T>) { + getRegistries(ep).register(registry) + } + + + /////////////////////////////////////////////////////////////////////////// + // public API implementation + /////////////////////////////////////////////////////////////////////////// + + internal fun mergeWith(another: AbstractConcurrentComponentStorage) { - for ((ep, list) in another.instances) { - for (extensionRegistry in list) { + another.registries.forEach { (ep, registries) -> + for (extensionRegistry in registries.asSequence()) { @Suppress("UNCHECKED_CAST") ep as ExtensionPoint<Extension> - this.contribute(ep, extensionRegistry.plugin, lazyInstance = { extensionRegistry.extension }) + registerExtension(ep, ExtensionRegistryImpl(extensionRegistry.plugin) { extensionRegistry.extension }) } } } - internal inline fun <T : Extension> ExtensionPoint<out T>.withExtensions(block: T.() -> Unit) { - return withExtensions { _ -> block() } - } - - @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") - @kotlin.internal.LowPriorityInOverloadResolution - internal inline fun <T : Extension> ExtensionPoint<out T>.withExtensions(block: T.(plugin: Plugin?) -> Unit) { - contract { - callsInPlace(block) - } - for ((plugin, extension) in this.getExtensions()) { + internal inline fun <T : Extension> useEachExtensions( + extensionPoint: ExtensionPoint<T>, + block: ExtensionRegistry<T>.(instance: T) -> Unit + ) { + contract { callsInPlace(block) } + getExtensions(extensionPoint).forEach { registry -> + val plugin = registry.plugin + val extension = registry.extension kotlin.runCatching { - block.invoke(extension, plugin) + block.invoke(registry, registry.extension) }.getOrElse { throwable -> - throwExtensionException(extension, plugin, throwable) + extensionPoint.throwExtensionException(extension, plugin, throwable) } } } - internal inline fun <reified E : SingletonExtension<*>> ExtensionPoint<out E>.findSingleton(builtin: E): E = - findSingleton(E::class, builtin) - - internal fun <E : SingletonExtension<*>> ExtensionPoint<out E>.findSingleton(type: KClass<E>, builtin: E): E { - val candidates = this.getExtensions() - return when (candidates.size) { - 0 -> builtin - 1 -> candidates.single().extension - else -> SingletonExtensionSelector.instance.selectSingleton(type, candidates) ?: builtin - } - } - - internal inline fun <reified E : SingletonExtension<T>, T> ExtensionPoint<out E>.findSingletonInstance(noinline default: () -> T): T = - findSingletonInstance(E::class, default) - - internal fun <E : SingletonExtension<T>, T> ExtensionPoint<out E>.findSingletonInstance( - type: KClass<E>, - default: () -> T, - ): T { - val candidates = this.getExtensions() - return when (candidates.size) { - 0 -> default() - 1 -> candidates.single().extension.instance - else -> SingletonExtensionSelector.instance.selectSingleton(type, candidates)?.instance ?: default() - } - } - - internal inline fun <T : Extension, E> ExtensionPoint<out T>.foldExtensions( + internal inline fun <T : Extension, E> foldExtensions( + extensionPoint: ExtensionPoint<T>, initial: E, block: (acc: E, extension: T) -> E, ): E { - contract { - callsInPlace(block) - } + contract { callsInPlace(block) } var e: E = initial - for ((plugin, extension) in this.getExtensions()) { + getExtensions(extensionPoint).forEach { registry -> + val plugin = registry.plugin + val extension = registry.extension kotlin.runCatching { e = block.invoke(e, extension) }.getOrElse { throwable -> - throwExtensionException(extension, plugin, throwable) + extensionPoint.throwExtensionException(extension, plugin, throwable) } } return e @@ -153,52 +129,38 @@ internal abstract class AbstractConcurrentComponentStorage : ComponentStorage { extension: T, plugin: Plugin?, throwable: Throwable, - ) { + ): Nothing { throw ExtensionException( "Exception while executing extension '${extension.kClassQualifiedNameOrTip}' provided by plugin '${plugin?.name ?: "<builtin>"}', registered for '${this.extensionType.qualifiedName}'", throwable ) } - internal inline fun <T : Extension> ExtensionPoint<T>.useExtensions(block: (extension: T) -> Unit): Unit = - withExtensions(block) - - @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") - @kotlin.internal.LowPriorityInOverloadResolution - internal inline fun <T : Extension> ExtensionPoint<T>.useExtensions(block: (extension: T, plugin: Plugin?) -> Unit): Unit = - withExtensions(block) - - override fun <T : Extension> contribute( - extensionPoint: ExtensionPoint<T>, + override fun <E : Extension> contribute( + extensionPoint: ExtensionPoint<E>, plugin: Plugin, - extensionInstance: T, + extensionInstance: E, ) { - instances.getOrPut(extensionPoint, ::CopyOnWriteArraySet).add(DataExtensionRegistry(plugin, extensionInstance)) + registerExtension(extensionPoint, ExtensionRegistryImpl(plugin) { extensionInstance }) } - @JvmName("contribute1") - fun <T : Extension> contribute( - extensionPoint: ExtensionPoint<T>, - plugin: Plugin?, - extensionInstance: T, - ) { - instances.getOrPut(extensionPoint, ::CopyOnWriteArraySet).add(DataExtensionRegistry(plugin, extensionInstance)) + override fun <E : Extension> contributeConsole(extensionPoint: ExtensionPoint<E>, lazyInstance: () -> E) { + registerExtension(extensionPoint, ExtensionRegistryImpl(null, lazyInstance)) } - override fun <T : Extension> contribute( - extensionPoint: ExtensionPoint<T>, + override fun <E : Extension> contribute( + extensionPoint: ExtensionPoint<E>, plugin: Plugin, - lazyInstance: () -> T, + lazyInstance: () -> E, ) { - instances.getOrPut(extensionPoint, ::CopyOnWriteArraySet).add(LazyExtensionRegistry(plugin, lazyInstance)) + registerExtension(extensionPoint, ExtensionRegistryImpl(plugin, lazyInstance)) } - @JvmName("contribute1") - fun <T : Extension> contribute( - extensionPoint: ExtensionPoint<T>, - plugin: Plugin?, - lazyInstance: () -> T, - ) { - instances.getOrPut(extensionPoint, ::CopyOnWriteArraySet).add(LazyExtensionRegistry(plugin, lazyInstance)) + override fun <E : Extension> getExtensions(extensionPoint: ExtensionPoint<E>): Sequence<ExtensionRegistry<E>> { + return getRegistries(extensionPoint).asSequence() + } + + override fun <E : Extension> getExtensionsStream(extensionPoint: ExtensionPoint<E>): Stream<ExtensionRegistry<E>> { + return getRegistries(extensionPoint).asStream() } } \ No newline at end of file diff --git a/mirai-console/backend/mirai-console/src/internal/extension/SingletonExtensionSelectorImpl.kt b/mirai-console/backend/mirai-console/src/internal/extension/SingletonExtensionSelectorImpl.kt index 0714840a7..a630c6a9d 100644 --- a/mirai-console/backend/mirai-console/src/internal/extension/SingletonExtensionSelectorImpl.kt +++ b/mirai-console/backend/mirai-console/src/internal/extension/SingletonExtensionSelectorImpl.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 Mamoe Technologies and contributors. + * Copyright 2019-2022 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. @@ -18,9 +18,16 @@ import net.mamoe.mirai.console.extensions.SingletonExtensionSelector import net.mamoe.mirai.console.internal.data.kClassQualifiedName import net.mamoe.mirai.console.plugin.name import net.mamoe.mirai.console.util.ConsoleInput +import net.mamoe.mirai.utils.DeprecatedSinceMirai import net.mamoe.mirai.utils.info import kotlin.reflect.KClass +@Suppress("DEPRECATION") +@Deprecated( + "Order of extensions is not determined by its priority property since 2.11. SingletonExtensionSelector is not needed anymore. ", + level = DeprecationLevel.WARNING +) +@DeprecatedSinceMirai(warningSince = "2.11") internal object SingletonExtensionSelectorImpl : SingletonExtensionSelector { internal val config: SaveData = SaveData() diff --git a/mirai-console/backend/mirai-console/src/internal/permission/BuiltInPermissionServices.kt b/mirai-console/backend/mirai-console/src/internal/permission/BuiltInPermissionServices.kt index 2b5653783..3c320c514 100644 --- a/mirai-console/backend/mirai-console/src/internal/permission/BuiltInPermissionServices.kt +++ b/mirai-console/backend/mirai-console/src/internal/permission/BuiltInPermissionServices.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 Mamoe Technologies and contributors. + * Copyright 2019-2022 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. @@ -14,6 +14,7 @@ import net.mamoe.mirai.console.data.AutoSavePluginConfig import net.mamoe.mirai.console.data.PluginDataExtensions import net.mamoe.mirai.console.data.PluginDataExtensions.withDefault import net.mamoe.mirai.console.data.value +import net.mamoe.mirai.console.extensions.PermissionServiceProvider import net.mamoe.mirai.console.permission.* import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.CopyOnWriteArraySet @@ -100,6 +101,12 @@ internal class AllDenyPermissionService : PermissionService<PermissionImpl> { internal class BuiltInPermissionService : AbstractConcurrentPermissionService<PermissionImpl>(), PermissionService<PermissionImpl> { + class Provider : PermissionServiceProvider { + override val instance: PermissionService<*> by lazy { + BuiltInPermissionService() + } + } + override val permissionType: KClass<PermissionImpl> get() = PermissionImpl::class override val permissions: ConcurrentHashMap<PermissionId, PermissionImpl> = ConcurrentHashMap() diff --git a/mirai-console/backend/mirai-console/src/internal/plugin/PluginManagerImpl.kt b/mirai-console/backend/mirai-console/src/internal/plugin/PluginManagerImpl.kt index c2d744f30..ecbe86aed 100644 --- a/mirai-console/backend/mirai-console/src/internal/plugin/PluginManagerImpl.kt +++ b/mirai-console/backend/mirai-console/src/internal/plugin/PluginManagerImpl.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019-2021 Mamoe Technologies and contributors. + * Copyright 2019-2022 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. @@ -160,12 +160,10 @@ internal class PluginManagerImpl( internal fun initExternalPluginLoaders(): Int { var count = 0 - GlobalComponentStorage.run { - PluginLoaderProvider.useExtensions { ext, plugin -> - logger.info { "Loaded PluginLoader ${ext.instance} from ${plugin?.name ?: "<builtin>"}" } - _pluginLoaders.add(ext.instance) - count++ - } + GlobalComponentStorage.useEachExtensions(PluginLoaderProvider) { + logger.info { "Loaded PluginLoader ${extension.instance} from ${plugin?.name ?: "<builtin>"}" } + _pluginLoaders.add(extension.instance) + count++ } return count } diff --git a/mirai-console/backend/mirai-console/src/permission/PermissionService.kt b/mirai-console/backend/mirai-console/src/permission/PermissionService.kt index 9542ed089..e91af577a 100644 --- a/mirai-console/backend/mirai-console/src/permission/PermissionService.kt +++ b/mirai-console/backend/mirai-console/src/permission/PermissionService.kt @@ -1,10 +1,10 @@ /* - * Copyright 2019-2021 Mamoe Technologies and contributors. + * Copyright 2019-2022 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. + * 此源代码的使用受 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 + * https://github.com/mamoe/mirai/blob/dev/LICENSE */ @file:Suppress("NOTHING_TO_INLINE", "unused", "MemberVisibilityCanBePrivate") @@ -13,7 +13,9 @@ package net.mamoe.mirai.console.permission import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME +import net.mamoe.mirai.console.extension.instance import net.mamoe.mirai.console.extensions.PermissionServiceProvider +import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage import net.mamoe.mirai.console.internal.permission.checkType import net.mamoe.mirai.console.permission.Permission.Companion.parentsWithSelf import net.mamoe.mirai.console.plugin.Plugin @@ -140,14 +142,12 @@ public interface PermissionService<P : Permission> { public companion object { /** - * [PermissionService] 实例 - * - * @see PermissionServiceProvider.selectedInstance + * 选用的 [PermissionService] 实例. */ @get:JvmName("getInstance") @JvmStatic public val INSTANCE: PermissionService<out Permission> - get() = PermissionServiceProvider.selectedInstance + get() = GlobalComponentStorage.getPreferredExtension(PermissionServiceProvider).instance /** * 获取一个权限, 失败时抛出 [NoSuchElementException] diff --git a/mirai-console/backend/mirai-console/test/extension/GlobalComponentStorageTest.kt b/mirai-console/backend/mirai-console/test/extension/GlobalComponentStorageTest.kt new file mode 100644 index 000000000..450385faf --- /dev/null +++ b/mirai-console/backend/mirai-console/test/extension/GlobalComponentStorageTest.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2019-2022 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/dev/LICENSE + */ + +package net.mamoe.mirai.console.extension + +import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage +import net.mamoe.mirai.console.internal.extension.GlobalComponentStorageImpl +import net.mamoe.mirai.console.testFramework.AbstractConsoleInstanceTest +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +internal class GlobalComponentStorageTest : AbstractConsoleInstanceTest() { + class MyInstance + + class MyExtension(override val instance: MyInstance, override val priority: Int) : InstanceExtension<MyInstance> { + companion object EP : AbstractInstanceExtensionPoint<MyExtension, MyInstance>(MyExtension::class) + } + + @Test + fun `can register`() { + GlobalComponentStorage.contributeConsole(MyExtension, MyExtension(MyInstance(), 1)) + GlobalComponentStorage.contributeConsole(MyExtension, MyExtension(MyInstance(), 2)) + GlobalComponentStorage.getExtensionsList(MyExtension).run { + assertEquals(2, size) + } + } + + @Test + fun `can contribute`() { + GlobalComponentStorage.contribute(MyExtension, mockPlugin, MyExtension(MyInstance(), 1)) + GlobalComponentStorage.contribute(MyExtension, mockPlugin, MyExtension(MyInstance(), 2)) + GlobalComponentStorage.getExtensionsList(MyExtension).run { + assertEquals(2, size) + } + } + + @Test + fun `can sort by priority`() { + GlobalComponentStorage.contributeConsole(MyExtension, MyExtension(MyInstance(), 1)) + GlobalComponentStorage.contributeConsole(MyExtension, MyExtension(MyInstance(), 2)) + GlobalComponentStorage.getExtensionsList(MyExtension).run { + assertEquals(2, size) + assertEquals(2, first().extension.priority) + assertEquals(1, get(1).extension.priority) + } + } + + @Test + fun `can sort by priority 2`() { + GlobalComponentStorage.contributeConsole(MyExtension, MyExtension(MyInstance(), 2)) + GlobalComponentStorage.contributeConsole(MyExtension, MyExtension(MyInstance(), 1)) + GlobalComponentStorage.getExtensionsList(MyExtension).run { + assertEquals(2, size) + assertEquals(2, first().extension.priority) + assertEquals(1, get(1).extension.priority) + } + } + + @Test + fun `can fold`() { + GlobalComponentStorage.contributeConsole(MyExtension, MyExtension(MyInstance(), 2)) + GlobalComponentStorage.contributeConsole(MyExtension, MyExtension(MyInstance(), 1)) + val list = GlobalComponentStorage.foldExtensions(MyExtension, listOf<MyExtension>()) { acc, extension -> + acc + extension + } + assertEquals(2, list.size) + assertEquals(2, list.first().priority) + assertEquals(1, list[1].priority) + } +} + +private fun <T : Extension> GlobalComponentStorageImpl.getExtensionsList(ep: ExtensionPoint<T>): List<ExtensionRegistry<T>> { + return getExtensions(ep).toList() +} \ No newline at end of file