From 1dcff8149c3106ef850092f6e1c3511ae3025f2e Mon Sep 17 00:00:00 2001 From: Him188 Date: Wed, 2 Sep 2020 19:46:25 +0800 Subject: [PATCH] Support structured serializers for more extensibility, support comments in serialized files --- backend/mirai-console/build.gradle.kts | 5 +- .../mirai/console/data/AbstractPluginData.kt | 4 +- .../mamoe/mirai/console/data/PluginData.kt | 12 ++-- .../mirai/console/data/ValueDescription.kt | 35 +++++++++ .../net/mamoe/mirai/console/data/ValueName.kt | 4 +- .../console/internal/data/PluginDataImpl.kt | 72 ++++++++++++------- .../internal/data/builtin/BotManagerImpl.kt | 6 ++ 7 files changed, 102 insertions(+), 36 deletions(-) create mode 100644 backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/ValueDescription.kt diff --git a/backend/mirai-console/build.gradle.kts b/backend/mirai-console/build.gradle.kts index 8a19ee9cb..e589b4a89 100644 --- a/backend/mirai-console/build.gradle.kts +++ b/backend/mirai-console/build.gradle.kts @@ -1,10 +1,7 @@ @file:Suppress("UnusedImport") import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import java.text.SimpleDateFormat import java.time.Instant -import java.util.Date -import java.util.TimeZone plugins { kotlin("jvm") @@ -65,7 +62,7 @@ dependencies { implementation(kotlinx("serialization-core", Versions.serialization)) implementation(kotlin("reflect")) - implementation("net.mamoe.yamlkt:yamlkt:${Versions.yamlkt}") + api("net.mamoe.yamlkt:yamlkt:${Versions.yamlkt}") implementation("org.jetbrains.kotlinx:atomicfu:${Versions.atomicFU}") api("org.jetbrains:annotations:19.0.0") api(kotlinx("coroutines-jdk8", Versions.coroutines)) 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 51950aca7..274bc3aa4 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 @@ -33,8 +33,8 @@ public abstract class AbstractPluginData : PluginData, PluginDataImpl() { /** * 供手动实现时值跟踪使用 (如 Java 用户). 一般 Kotlin 用户需使用 [provideDelegate] */ - public override fun > T.track(valueName: String): T = - apply { valueNodes.add(ValueNode(valueName, this, this.serializer)) } + public override fun > T.track(valueName: String, annotations: List): T = + apply { valueNodes.add(ValueNode(valueName, this, annotations, this.serializer)) } /** * 所有 [valueNodes] 更新和保存序列化器. 仅供内部使用 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 ca4753674..094867ef3 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 @@ -127,6 +127,7 @@ public interface PluginData { /** * 由 [provideDelegate] 创建, 来自一个通过 `by value` 初始化的属性节点. */ + @ConsoleExperimentalAPI public data class ValueNode( /** * 节点名称. @@ -139,10 +140,12 @@ public interface PluginData { * 属性值代理 */ val value: Value, + /** + * 注解列表 + */ + val annotations: List, /** * 属性值更新器 - * - * @suppress 注意, 这是实验性 API. */ val updaterSerializer: KSerializer ) @@ -153,7 +156,7 @@ public interface PluginData { public operator fun > T.provideDelegate( thisRef: Any?, property: KProperty<*> - ): T = track(property.valueName) + ): T = track(property.valueName, property.getAnnotationListForValueSerialization()) /** * 供手动实现时值跟踪使用 (如 Java 用户). 一般 Kotlin 用户需使用 [provideDelegate] @@ -167,7 +170,8 @@ public interface PluginData { * * @see [ValueNode.value] */ - valueName: String + valueName: String, + annotations: List ): T /** diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/ValueDescription.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/ValueDescription.kt new file mode 100644 index 000000000..f014df959 --- /dev/null +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/ValueDescription.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2019-2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.data + +/** + * 序列化之后的注释. + * + * 例: + * ``` + * object AccountPluginData : PluginData by ... { + * @ValueDescription(""" + * 一个 map + * """) + * val map: Map by value("a" to "b") + * } + * ``` + * + * 将被保存为配置 (YAML 作为示例): + * ```yaml + * AccountPluginData: + * # 一个 map + * map: + * a: b + * ``` + */ +@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +public annotation class ValueDescription(val value: String) \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/ValueName.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/ValueName.kt index 957902e49..9737bf73a 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/ValueName.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/ValueName.kt @@ -23,10 +23,10 @@ package net.mamoe.mirai.console.data * 将被保存为配置 (YAML 作为示例): * ```yaml * AccountPluginData: - * info: + * map: * a: b * ``` */ @Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS) -@Retention(AnnotationRetention.BINARY) +@Retention(AnnotationRetention.RUNTIME) public annotation class ValueName(val value: String) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/PluginDataImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/PluginDataImpl.kt index d85a1078b..75d21c61e 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/PluginDataImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/PluginDataImpl.kt @@ -12,7 +12,7 @@ package net.mamoe.mirai.console.internal.data import kotlinx.serialization.KSerializer -import kotlinx.serialization.builtins.MapSerializer +import kotlinx.serialization.SerialName import kotlinx.serialization.builtins.serializer import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.CompositeDecoder @@ -21,7 +21,12 @@ import kotlinx.serialization.encoding.Encoder import net.mamoe.mirai.console.data.PluginData import net.mamoe.mirai.console.data.PluginData.ValueNode import net.mamoe.mirai.console.data.Value +import net.mamoe.mirai.console.data.ValueDescription +import net.mamoe.mirai.console.data.ValueName +import net.mamoe.yamlkt.Comment import net.mamoe.yamlkt.YamlNullableDynamicSerializer +import java.lang.reflect.Constructor +import kotlin.reflect.KAnnotatedElement /** * Internal implementation for [PluginData] including: @@ -53,30 +58,22 @@ internal abstract class PluginDataImpl { } } else { outerLoop@ while (true) { - var valueName: String? = null innerLoop@ while (true) { val index = decodeElementIndex(descriptor) if (index == CompositeDecoder.DECODE_DONE) { - check(valueName == null) { "name must be null at this moment." } + //check(valueName == null) { "name must be null at this moment." } break@outerLoop } - if (!index.isOdd()) { // key - check(valueName == null) { "name must be null at this moment" } - valueName = decodeSerializableElement(descriptor, index, String.serializer()) + val node = findNodeInstance(descriptor.getElementName(index)) + if (node == null) { + decodeSerializableElement(descriptor, index, YamlNullableDynamicSerializer) } else { - check(valueName != null) { "name must not be null at this moment" } - - val node = findNodeInstance(valueName) - if (node == null) { - decodeSerializableElement(descriptor, index, YamlNullableDynamicSerializer) - } else { - decodeSerializableElement(descriptor, index, node.updaterSerializer) - } - - - break@innerLoop + decodeSerializableElement(descriptor, index, node.updaterSerializer) } + + + break@innerLoop } } @@ -92,8 +89,8 @@ internal abstract class PluginDataImpl { var index = 0 // val vSerializer = dataUpdaterSerializerTypeArguments[1] as KSerializer - valueNodes.forEach { (valueName, _, valueSerializer) -> - encodeStringElement(descriptor, index++, valueName) + valueNodes.forEach { (_, _, _, valueSerializer) -> + //encodeStringElement(descriptor, index, valueName) encodeSerializableElement(descriptor, index++, valueSerializer, Unit) } endStructure(descriptor) @@ -106,10 +103,37 @@ internal abstract class PluginDataImpl { * flatten */ abstract fun onValueChanged(value: Value<*>) - - companion object { - private val dataUpdaterSerializerTypeArguments = arrayOf(String.serializer(), YamlNullableDynamicSerializer) - private val dataUpdaterSerializerDescriptor = - MapSerializer(dataUpdaterSerializerTypeArguments[0], dataUpdaterSerializerTypeArguments[1]).descriptor + private val dataUpdaterSerializerDescriptor by lazy { + kotlinx.serialization.descriptors.buildClassSerialDescriptor((this as PluginData).saveName) { + for (valueNode in valueNodes) valueNode.run { + element(valueName, updaterSerializer.descriptor, isOptional = true) + } + } } +} + +internal fun KAnnotatedElement.getAnnotationListForValueSerialization(): List { + return this.annotations.mapNotNull { + when (it) { + is SerialName -> error("@SerialName is not supported on Value. Please use @ValueName instead") + is ValueName -> null + is ValueDescription -> COMMENT_CONSTRUCTOR(it.value) + else -> it + } + } +} + + +private val COMMENT_CONSTRUCTOR = findAnnotationImplementationClassConstructor()!! + +@Suppress("NOTHING_TO_INLINE") +internal inline operator fun Constructor.invoke(vararg args: Any?): T = this.newInstance(*args) + +internal inline fun findAnnotationImplementationClassConstructor(): Constructor? { + @Suppress("UNCHECKED_CAST") + return T::class.nestedClasses + .also { println(it.joinToString()) } + .find { it.simpleName?.endsWith("Impl") == true }?.java?.run { + constructors.singleOrNull() + } as Constructor? } \ No newline at end of file diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/builtin/BotManagerImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/builtin/BotManagerImpl.kt index 5d648c3ee..0a582a57e 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/builtin/BotManagerImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/data/builtin/BotManagerImpl.kt @@ -15,6 +15,7 @@ import net.mamoe.mirai.Bot import net.mamoe.mirai.console.data.AutoSavePluginConfig import net.mamoe.mirai.console.data.PluginDataExtensions.mapKeys import net.mamoe.mirai.console.data.PluginDataExtensions.withEmptyDefault +import net.mamoe.mirai.console.data.ValueDescription import net.mamoe.mirai.console.data.value import net.mamoe.mirai.console.util.BotManager import net.mamoe.mirai.contact.User @@ -38,6 +39,11 @@ internal object ManagersConfig : AutoSavePluginConfig() { override val saveName: String get() = "Managers" + @ValueDescription( + """ + 管理员列表 + """ + ) private val managers by value>>().withEmptyDefault() .mapKeys(Bot::getInstance, Bot::id)