diff --git a/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValueSettingCodegen.kt b/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValueSettingCodegen.kt index 6b2db60ea..fd383b795 100644 --- a/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValueSettingCodegen.kt +++ b/backend/codegen/src/main/kotlin/net/mamoe/mirai/console/codegen/ValueSettingCodegen.kt @@ -156,6 +156,26 @@ internal fun PluginData.${ktType.lowerCaseName}ValueImpl(): SerializerAwareValue } + object JPluginData_value_primitivesCodegen : RegionCodegen("JPluginData.kt"), DefaultInvoke { + @JvmStatic + fun main(args: Array) = super.startIndependently() + override val defaultInvokeArgs: List = KtPrimitives + KtString + + override fun StringBuilder.apply(ktType: KtType) { + @Suppress("unused") + appendKCode( + """ + /** + * 创建一个 [${ktType.standardName}] 类型的 [Value], 并设置初始值为 [default] + */ + public fun value(default: ${ktType.standardName}): SerializerAwareValue<${ktType.standardName}> = delegate.valueImpl(default) + """ + ) + appendLine() + } + + } + /** * 运行本 object 中所有嵌套 object Codegen */ diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AbstractPluginData.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AbstractPluginData.kt index 636f1b1ea..6d66ede26 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AbstractPluginData.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/AbstractPluginData.kt @@ -14,10 +14,8 @@ package net.mamoe.mirai.console.data import kotlinx.serialization.KSerializer import net.mamoe.mirai.console.data.PluginData.ValueNode import net.mamoe.mirai.console.internal.data.PluginDataImpl -import net.mamoe.mirai.console.internal.data.serialName import net.mamoe.mirai.console.util.ConsoleExperimentalAPI -import kotlin.annotation.AnnotationTarget.* -import kotlin.reflect.KProperty +import net.mamoe.mirai.console.util.ConsoleInternalAPI /** * [PluginData] 的默认实现. 支持使用 `by value()` 等委托方法创建 [Value] 并跟踪其改动. @@ -28,24 +26,23 @@ public abstract class AbstractPluginData : PluginData, PluginDataImpl() { /** * 添加了追踪的 [ValueNode] 列表, 即通过 `by value` 初始化的属性列表. * - * 他们的修改会被跟踪, 并触发 [onValueChanged]. + * 它们的修改会被跟踪, 并触发 [onValueChanged]. * * @see provideDelegate */ + @ConsoleExperimentalAPI public override val valueNodes: MutableList> = mutableListOf() /** - * 使用 `by` 时自动调用此方法, 添加对 [Value] 的值修改的跟踪. - * - * 将会创建一个 [ValueNode] 并添加到 [valueNodes] + * 供手动实现时值跟踪使用 (如 Java 用户). 一般 Kotlin 用户需使用 [provideDelegate] */ - public final override operator fun SerializerAwareValue.provideDelegate( - thisRef: Any?, - property: KProperty<*> - ): SerializerAwareValue = apply { valueNodes.add(ValueNode(property.serialName, this, this.serializer)) } + public override fun > T.track(valueName: String): T = + apply { valueNodes.add(ValueNode(valueName, this, this.serializer)) } /** - * 值更新序列化器. 仅供内部使用. + * 所有 [valueNodes] 更新和保存序列化器. 仅供内部使用 + * + * @suppress 注意, 这是实验性 API. */ @ConsoleExperimentalAPI public final override val updaterSerializer: KSerializer @@ -54,30 +51,6 @@ public abstract class AbstractPluginData : PluginData, PluginDataImpl() { /** * 当所属于这个 [PluginData] 的 [Value] 的 [值][Value.value] 被修改时被调用. */ + @ConsoleInternalAPI public abstract override fun onValueChanged(value: Value<*>) -} - -/** - * [PluginConfig] 的默认实现. - * - * 支持所有 [PluginData] 支持的功能, 支持通过 UI - * - * @see PluginConfig - */ -@ExperimentalPluginConfig -public abstract class AbstractPluginConfig : AbstractPluginData(), PluginConfig - - -/** - * 标记实验性的 [PluginConfig] API. - * - * @see ConsoleExperimentalAPI - */ -@ConsoleExperimentalAPI -@Retention(AnnotationRetention.BINARY) -@RequiresOptIn(level = RequiresOptIn.Level.WARNING) -@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR) -@MustBeDocumented -public annotation class ExperimentalPluginConfig( - val message: String = "" -) \ No newline at end of file +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/JPluginData.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/JPluginData.kt new file mode 100644 index 000000000..7c7739e43 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/JPluginData.kt @@ -0,0 +1,168 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +@file:Suppress("unused") + +package net.mamoe.mirai.console.data + +import net.mamoe.mirai.console.internal.data.cast +import net.mamoe.mirai.console.internal.data.setValueBySerializer +import net.mamoe.mirai.console.internal.data.valueImpl +import net.mamoe.mirai.console.plugin.jvm.JvmPlugin +import kotlin.reflect.KClass +import kotlin.reflect.KType +import kotlin.reflect.KTypeProjection +import kotlin.reflect.full.createType + +/** + * 供 Java 用户使用的 [PluginData]. 参考 [PluginData] 以获取更多信息. + * + * 在 [JvmPlugin] 的典型实现方式: + * ``` + * // PluginMain.java + * public final class PluginMain extends JavaPlugin { + * public static PluginMain INSTANCE = null; + * public PluginMain() { + * INSTANCE = this; + * } + * } + * + * // MyPluginData.java + * public class AccountPluginData extends JPluginData { + * public static AccountPluginData INSTANCE; + * + * public AccountPluginData() { + * super(PluginMain.INSTANCE.loadPluginData(AccountPluginData.class)); + * INSTANCE = this; + * } + * + * public final Value string = value("test"); // 默认值 "test" + * + * public final Value> list = typedValue(createKType(List.class, createKType(String.class))); // 无默认值, 自动创建空 List + * + * public final Value> custom = typedValue( + * createKType(Map.class, createKType(Long.class), createKType(Object.class)), + * new HashMap() {{ // 带默认值 + * put(123L, "ok"); + * }} + * ); + * } + * ``` + * + * 使用时, 需要使用 `.get()`, 如: + * ``` + * Value> theList = MyPluginData.INSTANCE.list; // 获取 Value 实例. Value 代表一个追踪自动保存的值. + * + * List actualList = theList.get(); + * + * theList.set(); + * ``` + * + * @see PluginData + */ +public open class JPluginData( + private val delegate: PluginData +) : PluginData by delegate { + //// region JPluginData_value_primitives CODEGEN //// + + /** + * 创建一个 [Byte] 类型的 [Value], 并设置初始值为 [default] + */ + public fun value(default: Byte): SerializerAwareValue = delegate.valueImpl(default) + + /** + * 创建一个 [Short] 类型的 [Value], 并设置初始值为 [default] + */ + public fun value(default: Short): SerializerAwareValue = delegate.valueImpl(default) + + /** + * 创建一个 [Int] 类型的 [Value], 并设置初始值为 [default] + */ + public fun value(default: Int): SerializerAwareValue = delegate.valueImpl(default) + + /** + * 创建一个 [Long] 类型的 [Value], 并设置初始值为 [default] + */ + public fun value(default: Long): SerializerAwareValue = delegate.valueImpl(default) + + /** + * 创建一个 [Float] 类型的 [Value], 并设置初始值为 [default] + */ + public fun value(default: Float): SerializerAwareValue = delegate.valueImpl(default) + + /** + * 创建一个 [Double] 类型的 [Value], 并设置初始值为 [default] + */ + public fun value(default: Double): SerializerAwareValue = delegate.valueImpl(default) + + /** + * 创建一个 [Char] 类型的 [Value], 并设置初始值为 [default] + */ + public fun value(default: Char): SerializerAwareValue = delegate.valueImpl(default) + + /** + * 创建一个 [Boolean] 类型的 [Value], 并设置初始值为 [default] + */ + public fun value(default: Boolean): SerializerAwareValue = delegate.valueImpl(default) + + /** + * 创建一个 [String] 类型的 [Value], 并设置初始值为 [default] + */ + public fun value(default: String): SerializerAwareValue = delegate.valueImpl(default) + + //// endregion JPluginData_value_primitives CODEGEN //// + + /** + * 构造一个支持泛型的 [Value]. + * + * 对于 [Map], [Set], [List] 等标准库类型, 这个函数会尝试构造 [LinkedHashMap], [LinkedHashSet], [ArrayList] 等相关类型. + * 而对于自定义数据类型, 本函数只会反射获取 [objectInstance][KClass.objectInstance] 或使用*无参构造器*构造实例. + * + * @param type Kotlin 类型. 可通过 [createKType] 获得 + * + * @param T 类型 T. 仅支持: + * - 基础数据类型, [String] + * - 标准库集合类型 ([List], [Map], [Set]) + * - 标准库数据类型 ([Map.Entry], [Pair], [Triple]) + * - 使用 [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization) 的 [Serializable] 标记的类 + */ + @JvmOverloads + public fun typedValue(type: KType, default: T? = null): SerializerAwareValue { + val value = delegate.valueImpl(type, type.classifier!!.cast()) + if (default != null) value.setValueBySerializer(default) + return value + } + + public companion object { + /** + * 根据 [Class] 及泛型参数获得一个类型 + * + * 如要获得一个 `Map` 的类型, + * ```java + * KType type = JPluginDataHelper.createKType(Map.java, createKType(String.java), createKType(Long.java)) + * ``` + * + * @param genericArguments 带有顺序的泛型参数 + */ + @JvmStatic + public fun createKType(clazz: Class, nullable: Boolean, vararg genericArguments: KType): KType { + return clazz.kotlin.createType(genericArguments.map { KTypeProjection(null, it) }, nullable) + } + + /** + * 根据 [Class] 及泛型参数获得一个不可为 `null` 的类型 + * + * @see createKType + */ + @JvmStatic + public fun createKType(clazz: Class, vararg genericArguments: KType): KType { + return createKType(clazz, false, *genericArguments) + } + } +} \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginConfig.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginConfig.kt index cc7df366f..8ba7ba9cc 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginConfig.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginConfig.kt @@ -9,6 +9,9 @@ package net.mamoe.mirai.console.data +import net.mamoe.mirai.console.util.ConsoleExperimentalAPI +import kotlin.annotation.AnnotationTarget.* + /** * 一个插件的配置数据, 用于和用户交互. * @@ -18,4 +21,18 @@ package net.mamoe.mirai.console.data * 插件内部的数据应用 [PluginData] 存储, 而不能使用 [PluginConfig]. */ @ExperimentalPluginConfig -public interface PluginConfig : PluginData \ No newline at end of file +public interface PluginConfig : PluginData + +/** + * 标记实验性的 [PluginConfig] API. + * + * @see ConsoleExperimentalAPI + */ +@ConsoleExperimentalAPI +@Retention(AnnotationRetention.BINARY) +@RequiresOptIn(level = RequiresOptIn.Level.WARNING) +@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR) +@MustBeDocumented +public annotation class ExperimentalPluginConfig( + val message: String = "" +) \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt index 0ed42e12c..86dbfe8c5 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt @@ -7,19 +7,22 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_SUPER_CLASS", "NOTHING_TO_INLINE") +@file:Suppress( + "INVISIBLE_REFERENCE", + "INVISIBLE_MEMBER", + "EXPOSED_SUPER_CLASS", + "NOTHING_TO_INLINE", "unused" +) @file:JvmName("PluginDataKt") package net.mamoe.mirai.console.data import kotlinx.serialization.KSerializer -import net.mamoe.mirai.console.internal.data.createInstanceSmart -import net.mamoe.mirai.console.internal.data.typeOf0 -import net.mamoe.mirai.console.internal.data.valueFromKTypeImpl -import net.mamoe.mirai.console.internal.data.valueImpl +import net.mamoe.mirai.console.internal.data.* import net.mamoe.mirai.console.plugin.jvm.JvmPlugin import net.mamoe.mirai.console.plugin.jvm.loadPluginData import net.mamoe.mirai.console.util.ConsoleExperimentalAPI +import net.mamoe.mirai.console.util.ConsoleInternalAPI import kotlin.internal.LowPriorityInOverloadResolution import kotlin.reflect.KClass import kotlin.reflect.KProperty @@ -30,17 +33,52 @@ import kotlin.reflect.KType * * [PluginData] 不涉及有关数据的存储, 而是只维护数据结构: [属性节点列表][valueNodes]. * - * 有关存储方案, 请查看 [PluginDataStorage] + * 有关存储方案, 请查看 [PluginDataStorage]. + * + * **注意**: [PluginData] 总应该是单例的. + * + * ### [JvmPlugin] 的实现方案 + * + * 要修改保存时的名称, 请参考 [ValueName] + * + * ### 使用 Kotlin * * 在 [JvmPlugin] 的典型实现方式: * ``` * object PluginMain : KotlinPlugin() * * object AccountPluginData : PluginData by PluginMain.loadPluginData() { - * val map: Map by value("a" to "b") + * val list: MutableList by value(mutableListOf("a", "b")) // mutableListOf("a", "b") 是初始值, 可以省略 + * val custom: Map by value() // 使用 kotlinx-serialization 序列化的类型. (目前还不支持) + * var custom2: CustomData by value() // 允许 var + * } + * + * @Serializable + * data class CustomData( + * // ... + * ) + * ``` + * + * 使用时, 可以方便地直接调用, 如: + * ``` + * val theList = AccountPluginData.list + * ``` + * + * 但也注意, 不要存储 `AccountPluginData.list`. 它可能受不到值跟踪. + * 错误的示例: + * ``` + * + * class { + * * } * ``` * + * ### 使用 Java + * + * 参考 [JPluginData] + * + * **注意**: 由于实现特殊, 请不要在初始化 Value 时就使用 `.get()`. 这可能会导致自动保存追踪失效. 必须在使用时才调用 `.get()` 获取真实数据对象. + * * @see JvmPlugin.loadPluginData 通过 [JvmPlugin] 获取指定 [PluginData] 实例. * @see PluginDataStorage [PluginData] 存储仓库 */ @@ -51,7 +89,9 @@ public interface PluginData { * 他们的修改会被跟踪, 并触发 [onValueChanged]. * * @see provideDelegate + * @see track */ + @ConsoleExperimentalAPI public val valueNodes: MutableList> /** @@ -61,14 +101,14 @@ public interface PluginData { /** * 节点名称. * - * 如果属性带有 [SerialName], 则使用 [kotlinx.serialization.SerialName.value], + * 如果属性带有 [ValueName], 则使用 [ValueName.value], * 否则使用 [属性名称][KProperty.name] */ - val serialName: String, + val valueName: String, /** * 属性值代理 */ - val value: Value, + val value: Value, /** * 属性值更新器 * @@ -81,13 +121,28 @@ public interface PluginData { /** * 使用 `by value()` 时自动调用此方法, 添加对 [Value] 的值修改的跟踪, 并创建 [ValueNode] 加入 [valueNodes] */ - public operator fun SerializerAwareValue.provideDelegate( + public operator fun > T.provideDelegate( thisRef: Any?, property: KProperty<*> - ): SerializerAwareValue + ): T = track(property.valueName) /** - * 值更新序列化器. 仅供内部使用 + * 供手动实现时值跟踪使用 (如 Java 用户). 一般 Kotlin 用户需使用 [provideDelegate] + */ + public fun > T.track( + /** + * 值名称. + * + * 如果属性带有 [ValueName], 则使用 [ValueName.value], + * 否则使用 [属性名称][KProperty.name] + * + * @see [ValueNode.value] + */ + valueName: String + ): T + + /** + * 所有 [valueNodes] 更新和保存序列化器. 仅供内部使用 * * @suppress 注意, 这是实验性 API. */ @@ -97,14 +152,70 @@ public interface PluginData { /** * 当所属于这个 [PluginData] 的 [Value] 的 [值][Value.value] 被修改时被调用. */ + @ConsoleInternalAPI public fun onValueChanged(value: Value<*>) /** * 当这个 [PluginData] 被放入一个 [PluginDataStorage] 时调用 */ + @ConsoleInternalAPI public fun setStorage(storage: PluginDataStorage) } +/** + * 获取这个 [KProperty] 委托的 [Value] + * + * 如, 对于 + * ``` + * object MyData : PluginData { + * val list: List by value() + * } + * + * val value: Value> = MyData.findBackingFieldValue(MyData::list) + * ``` + */ +@Suppress("UNCHECKED_CAST") +public fun PluginData.findBackingFieldValue(property: KProperty): Value? = + findBackingFieldValue(property.valueName) + +/** + * 获取这个 [KProperty] 委托的 [Value] + * + * 如, 对于 + * ``` + * object MyData : PluginData { + * @ValueName("theList") + * val list: List by value() + * val int: Int by value() + * } + * + * val value: Value> = MyData.findBackingFieldValue("theList") // 需使用 @ValueName 标注的名称 + * val intValue: Value = MyData.findBackingFieldValue("int") + * ``` + */ +@Suppress("UNCHECKED_CAST") +public fun PluginData.findBackingFieldValue(propertyValueName: String): Value? { + return this.valueNodes.find { it.valueName == propertyValueName }?.value as Value +} + + +/** + * 获取这个 [KProperty] 委托的 [Value] + * + * 如, 对于 + * ``` + * object MyData : PluginData { + * val list: List by value() + * } + * + * val value: PluginData.ValueNode> = MyData.findBackingFieldValueNode(MyData::list) + * ``` + */ +@Suppress("UNCHECKED_CAST") +public fun PluginData.findBackingFieldValueNode(property: KProperty): PluginData.ValueNode? { + return this.valueNodes.find { it == property } as PluginData.ValueNode? +} + /** * 用于支持属性委托 */ @@ -199,14 +310,14 @@ internal fun PluginData.valueImpl(type: KType, classifier: KClass<*>): Seria /** * 通过一个特定的 [KType] 创建 [Value], 并设置初始值. * - * 对于 [List], [Map], [Set] 等标准库类型, 这个函数会尝试构造 [LinkedHashMap] 等相关类型. - * 而对于自定义数据类型, 本函数只会反射获取 [objectInstance][KClass.objectInstance] 或使用无参构造器构造实例. + * 对于 [Map], [Set], [List] 等标准库类型, 这个函数会尝试构造 [LinkedHashMap], [LinkedHashSet], [ArrayList] 等相关类型. + * 而对于自定义数据类型, 本函数只会反射获取 [objectInstance][KClass.objectInstance] 或使用*无参构造器*构造实例. * * @param T 具体化参数类型 T. 仅支持: - * - 基础数据类型 + * - 基础数据类型, [String] * - 标准库集合类型 ([List], [Map], [Set]) * - 标准库数据类型 ([Map.Entry], [Pair], [Triple]) - * - 和使用 [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization) 的 [Serializable] 标记的 + * - 使用 [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization) 的 [Serializable] 标记的类 */ @Suppress("UNCHECKED_CAST") @ConsoleExperimentalAPI diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/SerialName.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/SerialName.kt index 9930f9ef4..277b3044e 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/SerialName.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/SerialName.kt @@ -5,9 +5,9 @@ package net.mamoe.mirai.console.data * * 例: * ``` - * @SerialName("accounts") + * @ValueName("accounts") * object AccountPluginData : PluginData by ... { - * @SerialName("info") + * @ValueName("info") * val map: Map by value("a" to "b") * } * ``` @@ -19,4 +19,6 @@ package net.mamoe.mirai.console.data * a: b * ``` */ -public typealias SerialName = kotlinx.serialization.SerialName +@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS) +@Retention(AnnotationRetention.BINARY) +public annotation class ValueName(val value: String) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/Value.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/Value.kt index 62aa495d8..67ef578f5 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/Value.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/Value.kt @@ -7,7 +7,7 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "unused", "NOTHING_TO_INLINE") +@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "unused", "NOTHING_TO_INLINE", "INAPPLICABLE_JVM_NAME") package net.mamoe.mirai.console.data @@ -31,6 +31,8 @@ import net.mamoe.mirai.console.util.ConsoleExperimentalAPI * @see CompositeValue 复合数据类型实现 */ public interface Value { + @get:JvmName("get") + @set:JvmName("set") public var value: T }