Support structured serializers for more extensibility, support comments in serialized files

This commit is contained in:
Him188 2020-09-02 19:46:25 +08:00
parent 42bcde38b8
commit 1dcff8149c
7 changed files with 102 additions and 36 deletions

View File

@ -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))

View File

@ -33,8 +33,8 @@ public abstract class AbstractPluginData : PluginData, PluginDataImpl() {
/**
* 供手动实现时值跟踪使用 ( Java 用户). 一般 Kotlin 用户需使用 [provideDelegate]
*/
public override fun <T : SerializerAwareValue<*>> T.track(valueName: String): T =
apply { valueNodes.add(ValueNode(valueName, this, this.serializer)) }
public override fun <T : SerializerAwareValue<*>> T.track(valueName: String, annotations: List<Annotation>): T =
apply { valueNodes.add(ValueNode(valueName, this, annotations, this.serializer)) }
/**
* 所有 [valueNodes] 更新和保存序列化器. 仅供内部使用

View File

@ -127,6 +127,7 @@ public interface PluginData {
/**
* [provideDelegate] 创建, 来自一个通过 `by value` 初始化的属性节点.
*/
@ConsoleExperimentalAPI
public data class ValueNode<T>(
/**
* 节点名称.
@ -139,10 +140,12 @@ public interface PluginData {
* 属性值代理
*/
val value: Value<out T>,
/**
* 注解列表
*/
val annotations: List<Annotation>,
/**
* 属性值更新器
*
* @suppress 注意, 这是实验性 API.
*/
val updaterSerializer: KSerializer<Unit>
)
@ -153,7 +156,7 @@ public interface PluginData {
public operator fun <T : SerializerAwareValue<*>> 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<Annotation>
): T
/**

View File

@ -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<String, String> 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)

View File

@ -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)

View File

@ -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<Any?>
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<Annotation> {
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<Comment>()!!
@Suppress("NOTHING_TO_INLINE")
internal inline operator fun <T : Any?> Constructor<T>.invoke(vararg args: Any?): T = this.newInstance(*args)
internal inline fun <reified T : Any> findAnnotationImplementationClassConstructor(): Constructor<out T>? {
@Suppress("UNCHECKED_CAST")
return T::class.nestedClasses
.also { println(it.joinToString()) }
.find { it.simpleName?.endsWith("Impl") == true }?.java?.run {
constructors.singleOrNull()
} as Constructor<out T>?
}

View File

@ -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<MutableMap<Long, MutableSet<Long>>>().withEmptyDefault()
.mapKeys(Bot::getInstance, Bot::id)