From aa032a145fdd28b209227a2c310622b7e1e7ee9f Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sun, 13 Sep 2020 12:39:46 +0800 Subject: [PATCH] Code Review - Add comments. - Use when not if. - Add docs --- .../plugin/BuiltInJvmPluginLoaderImpl.kt | 5 +- .../internal/plugin/ExportManagerImpl.kt | 73 ++++++++----------- .../internal/plugin/JvmPluginClassLoader.kt | 34 +++++++-- .../mirai/console/plugin/jvm/ExportManager.kt | 61 +++++++++++++++- docs/Plugins.md | 52 +++++++++++++ 5 files changed, 170 insertions(+), 55 deletions(-) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/BuiltInJvmPluginLoaderImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/BuiltInJvmPluginLoaderImpl.kt index 31d591797..2fc247161 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/BuiltInJvmPluginLoaderImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/BuiltInJvmPluginLoaderImpl.kt @@ -42,7 +42,7 @@ internal object BuiltInJvmPluginLoaderImpl : override val dataStorage: PluginDataStorage get() = MiraiConsoleImplementationBridge.dataStorageForJvmPluginLoader - internal val classLoaders: ConcurrentLinkedQueue = ConcurrentLinkedQueue() + internal val classLoaders: MutableList = mutableListOf() @Suppress("EXTENSION_SHADOWED_BY_MEMBER") // doesn't matter override fun getPluginDescription(plugin: JvmPlugin): JvmPluginDescription = plugin.description @@ -53,7 +53,7 @@ internal object BuiltInJvmPluginLoaderImpl : ensureActive() fun Sequence>.findAllInstances(): Sequence> { - return map { (f, pluginClassLoader) -> + return onEach { (_, pluginClassLoader) -> val exportManagers = pluginClassLoader.findServices( ExportManager::class ).loadAllServices() @@ -67,6 +67,7 @@ internal object BuiltInJvmPluginLoaderImpl : } else { pluginClassLoader.declaredFilter = exportManagers[0] } + }.map { (f, pluginClassLoader) -> f to (pluginClassLoader.findServices( JvmPlugin::class, KotlinPlugin::class, diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/ExportManagerImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/ExportManagerImpl.kt index 262dcc670..37073cc88 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/ExportManagerImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/ExportManagerImpl.kt @@ -26,61 +26,46 @@ internal class ExportManagerImpl( } companion object { + @JvmStatic fun parse(lines: Iterator): ExportManagerImpl { val rules = ArrayList<(String) -> Boolean?>() - lines.forEach { line -> - val trimed = line.trim() - if (trimed.isEmpty()) return@forEach - if (trimed[0] == '#') return@forEach - val command: String - val argument: String - kotlin.run { - val splitter = trimed.indexOf(' ') - if (splitter == -1) { - command = trimed - argument = "" - } else { - command = trimed.substring(0, splitter) - argument = trimed.substring(splitter + 1) - } - } + lines.asSequence().map { it.trim() }.filter { it.isNotBlank() }.filterNot { + it[0] == '#' + }.forEach { line -> + val command = line.substringBefore(' ') + val argument = line.substringAfter(' ', missingDelimiterValue = "").trim() + when (command) { "export" -> { - if (argument.isBlank()) { - rules.add { true } - } else { - if (argument.endsWith(".")) { - rules.add { - if (it.startsWith(argument)) true else null - } - } else { - rules.add { - if (it == argument) true else null - } + when { + // export-all + argument.isBlank() -> rules.add { true } + // export package + argument.endsWith(".") -> rules.add { + if (it.startsWith(argument)) true else null + } + // export class + else -> rules.add { + if (it == argument) true else null } } } "deny", "internal", "hidden" -> { - if (argument.isBlank()) { - rules.add { false } - } else { - if (argument.endsWith(".")) { - rules.add { - if (it.startsWith(argument)) false else null - } - } else { - rules.add { - if (it == argument) false else null - } + when { + // deny-all + argument.isBlank() -> rules.add { false } + // deny package + argument.endsWith(".") -> rules.add { + if (it.startsWith(argument)) false else null + } + // deny class + else -> rules.add { + if (it == argument) false else null } } } - "export-all" -> { - rules.add { true } - } - "deny-all", "hidden-all" -> { - rules.add { false } - } + "export-all" -> rules.add { true } + "deny-all", "hidden-all" -> rules.add { false } } } return ExportManagerImpl(rules) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginClassLoader.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginClassLoader.kt index bde482fb2..9154ba714 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginClassLoader.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginClassLoader.kt @@ -50,6 +50,7 @@ internal class JvmPluginClassLoader( } internal fun findClass(name: String, disableGlobal: Boolean): Class<*>? { + // First. Try direct load in cache. val cachedClass = cache[name] if (cachedClass != null) { if (disableGlobal) { @@ -60,19 +61,30 @@ internal class JvmPluginClassLoader( } return cachedClass } - if (disableGlobal) + if (disableGlobal) { + // ==== Process Loading Request From JvmPluginClassLoader ==== + // + // If load from other classloader, + // means no other loaders are cached. + // direct load return kotlin.runCatching { super.findClass(name).also { cache[name] = it } }.getOrElse { if (it is ClassNotFoundException) null else throw it }?.also { + // This request is from other classloader, + // so we need to check the class is exported or not. val filter = declaredFilter if (filter != null && !filter.isExported(name)) { throw LoadingDeniedException(name) } } + } + // ==== Process Loading Request From JDK ClassLoading System ==== + + // First. scan other classLoaders's caches classLoaders.forEach { otherClassloader -> if (otherClassloader === this) return@forEach val filter = otherClassloader.declaredFilter @@ -83,20 +95,26 @@ internal class JvmPluginClassLoader( } } - return kotlin.runCatching { super.findClass(name).also { cache[name] = it } }.getOrElse { - if (it is ClassNotFoundException) { + // If no cache... + return kotlin.runCatching { + // Try load this class direct.... + super.findClass(name).also { cache[name] = it } + }.getOrElse { exception -> + if (exception is ClassNotFoundException) { + // Cannot load the class from this, try others. classLoaders.forEach { otherClassloader -> if (otherClassloader === this) return@forEach val other = kotlin.runCatching { otherClassloader.findClass(name, true) - }.getOrElse { err -> - if (err is LoadingDeniedException || err !is ClassNotFoundException) throw it - null - } + }.onFailure { err -> + if (err is LoadingDeniedException || err !is ClassNotFoundException) + throw err + }.getOrNull() if (other != null) return other } } - throw it + // Great, nobody known what is the class. + throw exception } } } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/ExportManager.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/ExportManager.kt index 1138747d6..c9c20544f 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/ExportManager.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/ExportManager.kt @@ -9,7 +9,61 @@ package net.mamoe.mirai.console.plugin.jvm -public fun interface ExportManager { +import net.mamoe.mirai.console.internal.plugin.ExportManagerImpl + +/** + * 插件的类导出管理器 + * + * 我们允许插件将一些内部实现hidden起来, 避免其他插件调用, 要启动这个特性, + * 只需要在你的 resources 文件夹创建名为 `export-rules.txt` 的规则文件,便可以控制插件的类的公开规则 + * + * Example: + * ```text + * + * # #开头的行我们都识别为注释, 你可以在规则文件里面写很多注释 + * + * # export 运行插件访问一个类, 或者一个包 + * + * # 导出了一个internal包的一个类 + * export org.example.miraiconsole.myplugin.internal.OpenInternal + * + * # 导出了整个 api 包, 导出包和导出类的区别就是末尾是否存在 . 号 + * export org.example.miraiconsole.myplugin.api. + * + * # deny, 不允许其他插件使用这个包, 要隐藏一个包的时候, 注意不要忘记最后的 . 号 + * # + * # 别名: hidden, internal + * deny org.example.miraiconsole.myplugin.internal. + * + * # 这条规则不会生效, 因为在这条规则前已经被上面的 deny 给隐藏了 + * export org.example.miraiconsole.myplugin.internal.NotOpenInternal + * + * + * # export-all, 导出全部内容, 当然在此规则之前的deny依然会生效 + * # 使用此规则会同时让此规则后的所有规则全部失效 + * # export-all + * + * # 拒绝其他插件使用任何类, 除了之前已经explort的 + * # 此规则会导致后面的所有规则全部失效 + * deny-all + * + * ``` + * + * 插件也可以通过 Service 来自定义导出控制 + * + * Example: + * ``` + * @AutoService(ExportManager::class) + * object MyExportManager: ExportManager { + * override fun isExported(className: String): Boolean { + * println(" <== $className") + * return true + * } + * } + * ``` + * + */ +public interface ExportManager { public fun isExported(className: String): Boolean } @@ -21,4 +75,9 @@ public object StandardExportManagers { public object AllDenied : ExportManager { override fun isExported(className: String): Boolean = false } + + @JvmStatic + public fun parse(lines: Iterator): ExportManager { + return ExportManagerImpl.parse(lines) + } } \ No newline at end of file diff --git a/docs/Plugins.md b/docs/Plugins.md index f04beb5f7..32f6de3f4 100644 --- a/docs/Plugins.md +++ b/docs/Plugins.md @@ -16,6 +16,8 @@ [`PluginConfig`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginConfig.kt [`PluginDataStorage`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataStorage.kt +[`ExportManager`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/ExportManager.kt + [`MiraiConsole`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt [`MiraiConsoleImplementation`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt @@ -160,6 +162,56 @@ public final class JExample extends JavaPlugin { 多个插件的加载是*顺序的*,意味着若一个插件的 `onLoad()` 等回调处理缓慢,后续插件的加载也会被延后,即使它们可能没有依赖关系。 因此请尽量让 `onLoad()`,`onEnable()`,`onDisable()`快速返回。 +### API 导出管理 + +我们允许插件将一些内部实现hidden起来, 避免其他插件调用, 要启动这个特性, +只需要在你的 resources 文件夹创建名为 `export-rules.txt` 的规则文件,便可以控制插件的类的公开规则 + +Example: +```text + +# #开头的行我们都识别为注释, 你可以在规则文件里面写很多注释 + +# export 运行插件访问一个类, 或者一个包 + +# 导出了一个internal包的一个类 +export org.example.miraiconsole.myplugin.internal.OpenInternal + +# 导出了整个 api 包, 导出包和导出类的区别就是末尾是否存在 . 号 +export org.example.miraiconsole.myplugin.api. + +# deny, 不允许其他插件使用这个包, 要隐藏一个包的时候, 注意不要忘记最后的 . 号 +# +# 别名: hidden, internal +deny org.example.miraiconsole.myplugin.internal. + +# 这条规则不会生效, 因为在这条规则前已经被上面的 deny 给隐藏了 +export org.example.miraiconsole.myplugin.internal.NotOpenInternal + + +# export-all, 导出全部内容, 当然在此规则之前的deny依然会生效 +# 使用此规则会同时让此规则后的所有规则全部失效 +# export-all + +# 拒绝其他插件使用任何类, 除了之前已经explort的 +# 此规则会导致后面的所有规则全部失效 +deny-all + +``` + +插件也可以通过 Service 来自定义导出控制 + +Example: +```kotlin +@AutoService(ExportManager::class) +object MyExportManager: ExportManager { + override fun isExported(className: String): Boolean { + println(" <== $className") + return true + } +} +``` + ### 插件生命周期 Mirai Console 不提供热加载和热卸载功能,所有插件只能在服务器启动前加载,在服务器结束时卸载。([为什么不支持热加载和卸载插件?])