From f59fcf7d5d42879aeaf4ddf72e8c9c81f29ddfa0 Mon Sep 17 00:00:00 2001 From: Him188 Date: Fri, 11 Dec 2020 15:52:10 +0800 Subject: [PATCH] Message serialization --- .../kotlin/message/MessageSerializer.kt | 155 ++++++++++++++---- .../commonMain/kotlin/message/data/Image.kt | 22 ++- .../commonMain/kotlin/message/data/Message.kt | 11 +- mirai-core/src/commonMain/kotlin/MiraiImpl.kt | 5 +- .../kotlin/AtomicResizeCacheListTest.kt | 2 + .../message/data/MessageSerializationTest.kt | 81 +++++++++ mirai-core/src/jvmTest/kotlin/package.kt | 10 ++ 7 files changed, 242 insertions(+), 44 deletions(-) create mode 100644 mirai-core/src/jvmTest/kotlin/message/data/MessageSerializationTest.kt create mode 100644 mirai-core/src/jvmTest/kotlin/package.kt diff --git a/mirai-core-api/src/commonMain/kotlin/message/MessageSerializer.kt b/mirai-core-api/src/commonMain/kotlin/message/MessageSerializer.kt index a3fb2d42d..59fd525c5 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/MessageSerializer.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/MessageSerializer.kt @@ -14,12 +14,12 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.modules.PolymorphicModuleBuilder import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.plus -import kotlinx.serialization.modules.serializersModuleOf +import kotlinx.serialization.modules.polymorphic import net.mamoe.mirai.Mirai import net.mamoe.mirai.message.data.* -import net.mamoe.mirai.message.data.CombinedMessage import net.mamoe.mirai.utils.MiraiExperimentalApi import kotlin.reflect.KClass @@ -29,6 +29,8 @@ public interface MessageSerializer { public fun registerSerializer(clazz: KClass, serializer: KSerializer) + public fun registerSerializers(serializersModule: SerializersModule) + public fun clearRegisteredSerializers() } @@ -80,54 +82,135 @@ internal object MessageSourceSerializer : KSerializer { } -private val builtInSerializersModule = SerializersModule { - // In case Proguard or something else obfuscated the Kotlin metadata, providing the serializesrs explicity will help. - contextual(At::class, At.serializer()) - contextual(AtAll::class, AtAll.serializer()) - contextual(CombinedMessage::class, CombinedMessage.serializer()) - contextual(CustomMessage::class, CustomMessage.serializer()) - contextual(CustomMessageMetadata::class, CustomMessageMetadata.serializer()) - contextual(Face::class, Face.serializer()) - contextual(MessageSource::class, MessageSource.serializer()) - contextual(Image::class, Image.Serializer) - contextual(PlainText::class, PlainText.serializer()) - contextual(QuoteReply::class, QuoteReply.serializer()) - - contextual(ForwardMessage::class, ForwardMessage.serializer()) - contextual(RawForwardMessage::class, RawForwardMessage.serializer()) - contextual(ForwardMessage.Node::class, ForwardMessage.Node.serializer()) +private val builtInSerializersModule by lazy { + SerializersModule { + // non-Message classes + contextual(RawForwardMessage::class, RawForwardMessage.serializer()) + contextual(ForwardMessage.Node::class, ForwardMessage.Node.serializer()) + contextual(VipFace.Kind::class, VipFace.Kind.serializer()) - contextual(LightApp::class, LightApp.serializer()) - contextual(SimpleServiceMessage::class, SimpleServiceMessage.serializer()) - contextual(AbstractServiceMessage::class, AbstractServiceMessage.serializer()) - contextual(LongMessage::class, LongMessage.serializer()) - contextual(ForwardMessageInternal::class, ForwardMessageInternal.serializer()) + // In case Proguard or something else obfuscated the Kotlin metadata, providing the serializers explicitly will help. + contextual(At::class, At.serializer()) + contextual(AtAll::class, AtAll.serializer()) + contextual(CustomMessage::class, CustomMessage.serializer()) + contextual(CustomMessageMetadata::class, CustomMessageMetadata.serializer()) + contextual(Face::class, Face.serializer()) + contextual(MessageSource::class, MessageSource.serializer()) + contextual(Image::class, Image.Serializer) + contextual(PlainText::class, PlainText.serializer()) + contextual(QuoteReply::class, QuoteReply.serializer()) - contextual(PttMessage::class, PttMessage.serializer()) - contextual(Voice::class, Voice.serializer()) - - contextual(HummerMessage::class, HummerMessage.serializer()) - contextual(PokeMessage::class, PokeMessage.serializer()) - contextual(VipFace::class, VipFace.serializer()) - contextual(FlashImage::class, FlashImage.serializer()) - contextual(VipFace.Kind::class, VipFace.Kind.serializer()) + contextual(ForwardMessage::class, ForwardMessage.serializer()) - contextual(Message::class, Message.Serializer) - contextual(MessageChain::class, MessageChain.Serializer) + contextual(LightApp::class, LightApp.serializer()) + contextual(SimpleServiceMessage::class, SimpleServiceMessage.serializer()) + contextual(AbstractServiceMessage::class, AbstractServiceMessage.serializer()) + contextual(LongMessage::class, LongMessage.serializer()) + contextual(ForwardMessageInternal::class, ForwardMessageInternal.serializer()) + + contextual(PttMessage::class, PttMessage.serializer()) + contextual(Voice::class, Voice.serializer()) + + contextual(HummerMessage::class, HummerMessage.serializer()) + contextual(PokeMessage::class, PokeMessage.serializer()) + contextual(VipFace::class, VipFace.serializer()) + contextual(FlashImage::class, FlashImage.serializer()) + + fun PolymorphicModuleBuilder.singleMessageSubclasses() { + } + + fun PolymorphicModuleBuilder.messageMetadataSubclasses() { + subclass(MessageSource::class, MessageSource.serializer()) + subclass(QuoteReply::class, QuoteReply.serializer()) + } + + fun PolymorphicModuleBuilder.messageContentSubclasses() { + subclass(At::class, At.serializer()) + subclass(AtAll::class, AtAll.serializer()) + subclass(Face::class, Face.serializer()) + subclass(Image::class, Image.Serializer) + subclass(PlainText::class, PlainText.serializer()) + + subclass(ForwardMessage::class, ForwardMessage.serializer()) + + + subclass(LightApp::class, LightApp.serializer()) + subclass(SimpleServiceMessage::class, SimpleServiceMessage.serializer()) + subclass(LongMessage::class, LongMessage.serializer()) + subclass(ForwardMessageInternal::class, ForwardMessageInternal.serializer()) + + // subclass(PttMessage::class, PttMessage.serializer()) + subclass(Voice::class, Voice.serializer()) + + // subclass(HummerMessage::class, HummerMessage.serializer()) + subclass(PokeMessage::class, PokeMessage.serializer()) + subclass(VipFace::class, VipFace.serializer()) + subclass(FlashImage::class, FlashImage.serializer()) + } + + contextual(MessageChain::class, MessageChain.Serializer) + polymorphicDefault(MessageChain::class) { MessageChainImpl.serializer() } + + polymorphic(AbstractServiceMessage::class) { + subclass(LongMessage::class, LongMessage.serializer()) + subclass(ForwardMessageInternal::class, ForwardMessageInternal.serializer()) + } + + polymorphicDefault(Image::class) { Image.Serializer } + + contextual(Message::class, Message.Serializer) + // polymorphic(Message::class) { + // subclass(PlainText::class, PlainText.serializer()) + // } + polymorphic(Message::class) { + messageContentSubclasses() + messageMetadataSubclasses() + singleMessageSubclasses() + } + + //contextual(SingleMessage::class, SingleMessage.Serializer) + // polymorphic(SingleMessage::class, SingleMessage.Serializer) { + // messageContentSubclasses() + // messageMetadataSubclasses() + // singleMessageSubclasses() + // } + + // contextual(MessageContent::class, MessageContent.Serializer) + // polymorphic(MessageContent::class, MessageContent.Serializer) { + // messageContentSubclasses() + // } + + // contextual(MessageMetadata::class, MessageMetadata.Serializer) + // polymorphic(MessageMetadata::class, MessageMetadata.Serializer) { + // messageMetadataSubclasses() + // } + } } internal object MessageSerializerImpl : MessageSerializer { - override var serializersModule: SerializersModule = builtInSerializersModule + @Volatile + private var serializersModuleField: SerializersModule? = null + override val serializersModule: SerializersModule get() = serializersModuleField ?: builtInSerializersModule @Synchronized override fun registerSerializer(clazz: KClass, serializer: KSerializer) { - serializersModule = serializersModule.plus(serializersModuleOf(clazz, serializer)) + serializersModuleField = serializersModule.plus(SerializersModule { + contextual(clazz, serializer) + polymorphic(Message::class) { + subclass(clazz, serializer) + } + }) + } + + @Synchronized + override fun registerSerializers(serializersModule: SerializersModule) { + serializersModuleField = serializersModule } @Synchronized override fun clearRegisteredSerializers() { - serializersModule = builtInSerializersModule + serializersModuleField = builtInSerializersModule } } \ No newline at end of file diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/Image.kt b/mirai-core-api/src/commonMain/kotlin/message/data/Image.kt index b9a7c03fc..2dab3d5c1 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/Image.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/Image.kt @@ -28,8 +28,11 @@ import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.serializer import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.encoding.decodeStructure +import kotlinx.serialization.encoding.encodeStructure import net.mamoe.kjbb.JvmBlockingBridge import net.mamoe.mirai.Bot import net.mamoe.mirai.IMirai @@ -97,14 +100,21 @@ public interface Image : Message, MessageContent, CodableMessage { public val imageId: String public object Serializer : KSerializer { - override val descriptor: SerialDescriptor = String.serializer().descriptor - override fun deserialize(decoder: Decoder): Image { - val id = String.serializer().deserialize(decoder) - return Image(id) + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("net.mamoe.mirai.message.data.Image") { + element("imageId", String.serializer().descriptor) } - override fun serialize(encoder: Encoder, value: Image) { - String.serializer().serialize(encoder, value.imageId) + override fun deserialize(decoder: Decoder): Image = decoder.decodeStructure(descriptor) { + val imageId = if (decodeSequentially()) { + decodeStringElement(descriptor, 0) + } else { + decodeStringElement(descriptor, decodeElementIndex(descriptor)) + } + Image(imageId) + } + + override fun serialize(encoder: Encoder, value: Image): Unit = encoder.encodeStructure(descriptor) { + encodeStringElement(descriptor, 0, value.imageId) } } diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/Message.kt b/mirai-core-api/src/commonMain/kotlin/message/data/Message.kt index 655c3e861..218989626 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/Message.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/Message.kt @@ -274,7 +274,10 @@ public inline operator fun Message.times(count: Int): MessageChain = this.repeat /** * 单个消息元素. 与之相对的是 [MessageChain], 是多个 [SingleMessage] 的集合. */ -public interface SingleMessage : Message +@Serializable(SingleMessage.Serializer::class) +public interface SingleMessage : Message { + public object Serializer : KSerializer by PolymorphicSerializer(SingleMessage::class) +} /** * 消息元数据, 即不含内容的元素. @@ -289,11 +292,14 @@ public interface SingleMessage : Message * * @see ConstrainSingle 约束一个 [MessageChain] 中只存在这一种类型的元素 */ +@Serializable(MessageMetadata.Serializer::class) public interface MessageMetadata : SingleMessage { /** * 返回空字符串 */ override fun contentToString(): String = "" + + public object Serializer : KSerializer by PolymorphicSerializer(MessageMetadata::class) } /** @@ -378,9 +384,12 @@ public abstract class AbstractPolymorphicMessageKey({ it.safeCast() }) + + public object Serializer : KSerializer by PolymorphicSerializer(MessageContent::class) } /** diff --git a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt index 19394c619..060ab3622 100644 --- a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt +++ b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt @@ -56,7 +56,10 @@ import kotlin.random.Random internal open class MiraiImpl : IMirai, LowLevelApiAccessor { companion object INSTANCE : MiraiImpl() { @Suppress("ObjectPropertyName", "unused") - private val _init = Mirai.let { } + private val _init = Mirai.let { + Message.Serializer.registerSerializer(OfflineGroupImage::class, OfflineGroupImage.serializer()) + Message.Serializer.registerSerializer(OfflineFriendImage::class, OfflineFriendImage.serializer()) + } } override val BotFactory: BotFactory diff --git a/mirai-core/src/jvmTest/kotlin/AtomicResizeCacheListTest.kt b/mirai-core/src/jvmTest/kotlin/AtomicResizeCacheListTest.kt index 157d298b9..974d99f26 100644 --- a/mirai-core/src/jvmTest/kotlin/AtomicResizeCacheListTest.kt +++ b/mirai-core/src/jvmTest/kotlin/AtomicResizeCacheListTest.kt @@ -7,6 +7,8 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +package net.mamoe.mirai.internal + /* internal class AtomicResizeCacheListTest { diff --git a/mirai-core/src/jvmTest/kotlin/message/data/MessageSerializationTest.kt b/mirai-core/src/jvmTest/kotlin/message/data/MessageSerializationTest.kt new file mode 100644 index 000000000..429becbef --- /dev/null +++ b/mirai-core/src/jvmTest/kotlin/message/data/MessageSerializationTest.kt @@ -0,0 +1,81 @@ +/* + * Copyright 2019-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 + */ + +package net.mamoe.mirai.internal.message.data + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.json.Json +import kotlinx.serialization.serializer +import net.mamoe.mirai.Mirai +import net.mamoe.mirai.message.data.* +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +internal class MessageSerializationTest { + private val module = Message.Serializer.serializersModule + private val format = Json { + serializersModule = module + useArrayPolymorphism = false + } + + private inline fun T.serialize(serializer: KSerializer = module.serializer()): String { + return format.encodeToString(serializer, this) + } + + private inline fun String.deserialize(serializer: KSerializer = module.serializer()): T { + return format.decodeFromString(serializer, this) + } + + private inline fun testSerialization(t: T, serializer: KSerializer = module.serializer()) { + assertEquals( + t, + t.serialize(serializer).deserialize(serializer), + message = "serialized string: ${t.serialize(serializer)}" + ) + } + + private val testMessageContentInstances: Array = arrayOf( + PlainText("test"), + At._lowLevelConstructAtInstance(123456, ""), + AtAll, + Image("{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.mirai"), + ) + + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") + private val testConstrainSingleMessageInstances: Array = arrayOf( + LongMessage("content", "resId") + ) + + companion object { + @BeforeAll + @JvmStatic + fun init() { + Mirai + } + } + + @Test + fun `test serialize each message contents`() { + for (message in testMessageContentInstances) { + testSerialization(message, module.serializer(message.javaClass)) + } + for (message in testConstrainSingleMessageInstances) { + testSerialization(message, module.serializer(message.javaClass)) + } + } + + @Test + fun `test serialize message chain`() { + val chain = testMessageContentInstances.asMessageChain() + println(chain.serialize()) // [["net.mamoe.mirai.message.data.PlainText",{"content":"test"}],["net.mamoe.mirai.message.data.At",{"target":123456,"display":""}],["net.mamoe.mirai.message.data.AtAll",{}],["net.mamoe.mirai.internal.message.OfflineGroupImage",{"imageId":"{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.mirai"}]] + + testSerialization(chain) + } +} \ No newline at end of file diff --git a/mirai-core/src/jvmTest/kotlin/package.kt b/mirai-core/src/jvmTest/kotlin/package.kt new file mode 100644 index 000000000..d0d089745 --- /dev/null +++ b/mirai-core/src/jvmTest/kotlin/package.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2019-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 + */ + +package net.mamoe.mirai.internal \ No newline at end of file