From 7ecffb239dfef7cd4af01c8905b25b8bf804e69d Mon Sep 17 00:00:00 2001
From: Him188 <Him188@mamoe.net>
Date: Sun, 23 Aug 2020 17:18:09 +0800
Subject: [PATCH] Move Plugin.enable and Plugin.disable into PluginManager for
 a better java API.

---
 .../internal/plugin/JarPluginLoaderImpl.kt    | 11 +++--
 .../internal/plugin/JvmPluginInternal.kt      |  2 +-
 .../internal/plugin/PluginManagerImpl.kt      |  2 +-
 .../net/mamoe/mirai/console/plugin/Plugin.kt  | 37 ++++-------------
 .../mirai/console/plugin/PluginLoader.kt      | 41 ++++++++++++++++++-
 .../mirai/console/plugin/PluginManager.kt     | 25 +++++++++++
 6 files changed, 81 insertions(+), 37 deletions(-)

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 10de0f8a6..2c91bc820 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
@@ -13,6 +13,7 @@ import kotlinx.coroutines.*
 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.data.createInstanceOrNull
 import net.mamoe.mirai.console.plugin.AbstractFilePluginLoader
 import net.mamoe.mirai.console.plugin.PluginLoadException
 import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader
@@ -25,7 +26,6 @@ import net.mamoe.yamlkt.Yaml
 import java.io.File
 import java.net.URI
 import kotlin.coroutines.CoroutineContext
-import kotlin.reflect.full.createInstance
 
 internal object JarPluginLoaderImpl :
     AbstractFilePluginLoader<JvmPlugin, JvmPluginDescription>(".jar"),
@@ -90,14 +90,14 @@ internal object JarPluginLoaderImpl :
                 jarFile = file
             ).kotlin.run {
                 objectInstance
-                    ?: kotlin.runCatching { createInstance() }.getOrNull()
+                    ?: createInstanceOrNull()
                     ?: (java.constructors + java.declaredConstructors)
                         .firstOrNull { it.parameterCount == 0 }
                         ?.apply { kotlin.runCatching { isAccessible = true } }
                         ?.newInstance()
-            } ?: error("No Kotlin object or public no-arg constructor found")
+            } ?: error("No Kotlin object or public no-arg constructor found for $mainClassName")
 
-            check(main is JvmPlugin) { "The main class of Jar plugin must extend JvmPlugin, recommending JavaPlugin or KotlinPlugin" }
+            check(main is JvmPlugin) { "The main class of Jar plugin must extend JvmPlugin, recommended JavaPlugin or KotlinPlugin" }
 
             if (main is JvmPluginInternal) {
                 main._description = description
@@ -110,6 +110,7 @@ internal object JarPluginLoaderImpl :
     }
 
     override fun enable(plugin: JvmPlugin) {
+        if (plugin.isEnabled) throw IllegalStateException("Plugin is already enabled")
         ensureActive()
         if (plugin is JvmPluginInternal) {
             plugin.internalOnEnable()
@@ -117,6 +118,8 @@ internal object JarPluginLoaderImpl :
     }
 
     override fun disable(plugin: JvmPlugin) {
+        if (!plugin.isEnabled) throw IllegalStateException("Plugin is already disabled")
+
         if (plugin is JvmPluginInternal) {
             plugin.internalOnDisable()
         } else plugin.onDisable()
diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt
index e05cb3b9d..0e8ab5709 100644
--- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt
+++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt
@@ -16,10 +16,10 @@ import net.mamoe.mirai.console.MiraiConsole
 import net.mamoe.mirai.console.internal.data.mkdir
 import net.mamoe.mirai.console.plugin.Plugin
 import net.mamoe.mirai.console.plugin.PluginManager
+import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.safeLoader
 import net.mamoe.mirai.console.plugin.ResourceContainer.Companion.asResourceContainer
 import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
 import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
-import net.mamoe.mirai.console.plugin.safeLoader
 import net.mamoe.mirai.utils.MiraiLogger
 import java.io.InputStream
 import java.nio.file.Path
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 d39deb81f..b9c73a879 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
@@ -61,7 +61,7 @@ internal object PluginManagerImpl : PluginManager {
 
     init {
         MiraiConsole.coroutineContext[Job]!!.invokeOnCompletion {
-            plugins.forEach(Plugin::disable)
+            plugins.forEach { it.disable() }
         }
     }
 
diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt
index 5280d5e0b..a8b6cb198 100644
--- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt
+++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt
@@ -12,6 +12,8 @@
 package net.mamoe.mirai.console.plugin
 
 import net.mamoe.mirai.console.command.CommandOwner
+import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.disable
+import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.enable
 import net.mamoe.mirai.console.plugin.dsecription.PluginDescription
 import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
 import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
@@ -21,6 +23,10 @@ import java.nio.file.Path
 /**
  * 表示一个 mirai-console 插件.
  *
+ * @see PluginManager.enable 启用一个插件
+ * @see PluginManager.disable 禁用一个插件
+ * @see PluginManager.description 获取一个插件的 [描述][PluginDescription]
+ *
  * @see PluginDescription 插件描述, 需由 [PluginLoader] 帮助提供([PluginLoader.description])
  * @see JvmPlugin Java, Kotlin 或其他 JVM 平台插件
  * @see PluginFileExtensions 支持文件系统存储的扩展
@@ -31,8 +37,8 @@ public interface Plugin : CommandOwner {
     /**
      * 判断此插件是否已启用
      *
-     * @see disable 关闭这个插件
-     * @see enable 启用这个插件
+     * @see PluginManager.enable 启用一个插件
+     * @see PluginManager.disable 禁用一个插件
      */
     public val isEnabled: Boolean
 
@@ -42,33 +48,6 @@ public interface Plugin : CommandOwner {
     public val loader: PluginLoader<*, *>
 }
 
-/**
- * 获取插件描述
- */
-public val Plugin.description: PluginDescription get() = safeLoader.getDescription(this)
-
-/**
- * 禁用这个插件
- *
- * @see PluginLoader.disable
- */
-public fun Plugin.disable(): Unit = safeLoader.disable(this)
-
-/**
- * 启用这个插件
- *
- * @see PluginLoader.enable
- */
-public fun Plugin.enable(): Unit = safeLoader.enable(this)
-
-/**
- * 经过泛型类型转换的 [PluginLoader]
- */
-@get:JvmSynthetic
-@Suppress("UNCHECKED_CAST")
-public inline val <P : Plugin> P.safeLoader: PluginLoader<P, PluginDescription>
-    get() = this.loader as PluginLoader<P, PluginDescription>
-
 /**
  * 支持文件系统存储的扩展.
  *
diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt
index d4d0f60fe..05bd7cc8d 100644
--- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt
+++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt
@@ -38,7 +38,7 @@ public interface PluginLoader<P : Plugin, D : PluginDescription> {
      *
      * 在 console 启动时, [PluginManager] 会获取所有 [PluginDescription], 分析依赖关系, 确认插件加载顺序.
      *
-     * **实现细节:** 此函数只*应该*在 console 启动时被调用一次. 但取决于前端实现不同, 或可能由于被一些插件需要, 此函数也可能会被多次调用.
+     * **实现细节:** 此函数*只应该*在 console 启动时被调用一次. 但取决于前端实现不同, 或由于被一些插件需要, 此函数也可能会被多次调用.
      */
     public fun listPlugins(): List<D>
 
@@ -52,6 +52,7 @@ public interface PluginLoader<P : Plugin, D : PluginDescription> {
      * @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如无法读取插件信息等).
      *
      * @see PluginDescription 插件描述
+     * @see getDescription 无 receiver, 接受参数的版本.
      */
     @get:JvmName("getPluginDescription")
     @get:Throws(PluginLoadException::class)
@@ -71,17 +72,53 @@ public interface PluginLoader<P : Plugin, D : PluginDescription> {
     /**
      * 启用这个插件.
      *
-     * **实现约定**: 若插件已经启用, 抛出
+     * **实现细节**: 此函数可抛出 [PluginLoadException] 作为正常失败原因, 其他任意异常都属于意外错误.
+     * 当异常发生时, 插件将会直接被放弃加载, 并影响依赖它的其他插件.
+     *
+     * @throws IllegalStateException 当插件已经启用时抛出
+     * @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如找不到主类等).
      */
+    @Throws(IllegalStateException::class, PluginLoadException::class)
     public fun enable(plugin: P)
+
+    /**
+     * 禁用这个插件.
+     *
+     * **实现细节**: 此函数可抛出 [PluginLoadException] 作为正常失败原因, 其他任意异常都属于意外错误.
+     * 当异常发生时, 插件将会直接被放弃加载, 并影响依赖它的其他插件.
+     *
+     * @throws IllegalStateException 当插件已经禁用时抛出
+     * @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如找不到主类等).
+     */
+    @Throws(IllegalStateException::class, PluginLoadException::class)
     public fun disable(plugin: P)
 }
 
+/**
+ * 获取此插件的描述.
+ *
+ * **实现细节**: 此函数只允许抛出 [PluginLoadException] 作为正常失败原因, 其他任意异常都属于意外错误.
+ *
+ * 若在 console 启动并加载所有插件的过程中, 本函数抛出异常, 则会放弃此插件的加载, 并影响依赖它的其他插件.
+ *
+ * @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如无法读取插件信息等).
+ *
+ * @see PluginDescription 插件描述
+ * @see PluginLoader.description
+ */
 @Suppress("UNCHECKED_CAST")
 @JvmSynthetic
 public inline fun <D : PluginDescription, P : Plugin> PluginLoader<in P, out D>.getDescription(plugin: P): D =
     plugin.description
 
+/**
+ * 在加载插件过程中遇到的意料之中的问题.
+ *
+ * @see PluginLoader.load
+ * @see PluginLoader.enable
+ * @see PluginLoader.disable
+ * @see PluginLoader.description
+ */
 public open class PluginLoadException : RuntimeException {
     public constructor() : super()
     public constructor(message: String?) : super(message)
diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt
index 2921bf7a8..5f7796a18 100644
--- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt
+++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt
@@ -89,11 +89,36 @@ public interface PluginManager {
      */
     public val Plugin.description: PluginDescription
 
+    /**
+     * 禁用这个插件
+     *
+     * @see PluginLoader.disable
+     */
+    public fun Plugin.disable(): Unit = safeLoader.disable(this)
+
+    /**
+     * 启用这个插件
+     *
+     * @see PluginLoader.enable
+     */
+    public fun Plugin.enable(): Unit = safeLoader.enable(this)
+
+    /**
+     * 经过泛型类型转换的 [PluginLoader]
+     */
+    @get:JvmSynthetic
+    @Suppress("UNCHECKED_CAST")
+    public val <P : Plugin> P.safeLoader: PluginLoader<P, PluginDescription>
+        get() = this.loader as PluginLoader<P, PluginDescription>
+
     public companion object INSTANCE : PluginManager by PluginManagerImpl {
         // due to Kotlin's bug
         public override val Plugin.description: PluginDescription get() = PluginManagerImpl.run { description }
         public override fun PluginLoader<*, *>.register(): Boolean = PluginManagerImpl.run { register() }
         public override fun PluginLoader<*, *>.unregister(): Boolean = PluginManagerImpl.run { unregister() }
+        public override fun Plugin.disable(): Unit = PluginManagerImpl.run { disable() }
+        public override fun Plugin.enable(): Unit = PluginManagerImpl.run { enable() }
+        public override val <P : Plugin> P.safeLoader: PluginLoader<P, PluginDescription> get() = PluginManagerImpl.run { safeLoader }
     }
 }