Browse Source

Merge pull request #952 from mamoe/fix_polymorphic_serializaiton

Fix polymorphic serializaiton
Him188 5 years ago
parent
commit
5b80df7b3d

+ 4 - 0
binary-compatibility-validator/api/binary-compatibility-validator.api

@@ -4702,6 +4702,7 @@ public final class net/mamoe/mirai/message/data/MusicKind : java/lang/Enum {
 
 public final class net/mamoe/mirai/message/data/MusicShare : net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/MessageContent {
 	public static final field Key Lnet/mamoe/mirai/message/data/MusicShare$Key;
+	public static final field SERIAL_NAME Ljava/lang/String;
 	public synthetic fun <init> (ILnet/mamoe/mirai/message/data/MusicKind;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
 	public fun <init> (Lnet/mamoe/mirai/message/data/MusicKind;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
 	public fun <init> (Lnet/mamoe/mirai/message/data/MusicKind;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
@@ -5084,14 +5085,17 @@ public final class net/mamoe/mirai/message/data/RichMessageKind : java/lang/Enum
 
 public final class net/mamoe/mirai/message/data/RichMessageOrigin : net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/MessageMetadata {
 	public static final field Key Lnet/mamoe/mirai/message/data/RichMessageOrigin$Key;
+	public static final field SERIAL_NAME Ljava/lang/String;
 	public synthetic fun <init> (ILnet/mamoe/mirai/message/data/RichMessage;Ljava/lang/String;Lnet/mamoe/mirai/message/data/RichMessageKind;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
 	public fun <init> (Lnet/mamoe/mirai/message/data/RichMessage;Ljava/lang/String;Lnet/mamoe/mirai/message/data/RichMessageKind;)V
 	public fun contentToString ()Ljava/lang/String;
+	public fun equals (Ljava/lang/Object;)Z
 	public synthetic fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey;
 	public fun getKey ()Lnet/mamoe/mirai/message/data/RichMessageOrigin$Key;
 	public final fun getKind ()Lnet/mamoe/mirai/message/data/RichMessageKind;
 	public final fun getOrigin ()Lnet/mamoe/mirai/message/data/RichMessage;
 	public final fun getResourceId ()Ljava/lang/String;
+	public fun hashCode ()I
 	public fun toString ()Ljava/lang/String;
 	public static final fun write$Self (Lnet/mamoe/mirai/message/data/RichMessageOrigin;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V
 }

+ 1 - 1
mirai-console

@@ -1 +1 @@
-Subproject commit a5481accb5f882d121ff9fc1d55e4e5f3e908e76
+Subproject commit 8683e888926fbe2c76abf6362830f7470fc7c029

+ 66 - 41
mirai-core-api/src/commonMain/kotlin/internal/message/MessageSerializersImpl.kt

@@ -22,9 +22,10 @@ import kotlinx.serialization.modules.polymorphic
 import net.mamoe.mirai.Mirai
 import net.mamoe.mirai.message.MessageSerializers
 import net.mamoe.mirai.message.data.*
-import net.mamoe.mirai.message.data.MessageChainImpl
 import net.mamoe.mirai.utils.MiraiInternalApi
 import kotlin.reflect.KClass
+import kotlin.reflect.full.allSuperclasses
+import kotlin.reflect.full.isSubclassOf
 
 
 internal fun ClassSerialDescriptorBuilder.takeElementsFrom(descriptor: SerialDescriptor) {
@@ -70,38 +71,45 @@ public open class MessageSourceSerializerImpl(serialName: String) :
 
 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())
-
-
-        // 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(Image::class, Image.Serializer)
-        contextual(PlainText::class, PlainText.serializer())
-        contextual(QuoteReply::class, QuoteReply.serializer())
-
-        contextual(ForwardMessage::class, ForwardMessage.serializer())
-
-
-        contextual(LightApp::class, LightApp.serializer())
-        contextual(SimpleServiceMessage::class, SimpleServiceMessage.serializer())
-        contextual(AbstractServiceMessage::class, AbstractServiceMessage.serializer())
-
-        contextual(PttMessage::class, PttMessage.serializer())
-        contextual(Voice::class, Voice.serializer())
-        contextual(PokeMessage::class, PokeMessage.serializer())
-        contextual(VipFace::class, VipFace.serializer())
-        contextual(FlashImage::class, FlashImage.serializer())
-
-        contextual(MusicShare::class, MusicShare.serializer())
+        // NOTE: contextual serializers disabled because of https://github.com/mamoe/mirai/issues/951
+
+//        // non-Message classes
+//        contextual(RawForwardMessage::class, RawForwardMessage.serializer())
+//        contextual(ForwardMessage.Node::class, ForwardMessage.Node.serializer())
+//        contextual(VipFace.Kind::class, VipFace.Kind.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(Image::class, Image.Serializer)
+//        contextual(PlainText::class, PlainText.serializer())
+//        contextual(QuoteReply::class, QuoteReply.serializer())
+//
+//        contextual(ForwardMessage::class, ForwardMessage.serializer())
+//
+//
+//        contextual(LightApp::class, LightApp.serializer())
+//        contextual(SimpleServiceMessage::class, SimpleServiceMessage.serializer())
+//        contextual(AbstractServiceMessage::class, AbstractServiceMessage.serializer())
+//
+//        contextual(PttMessage::class, PttMessage.serializer())
+//        contextual(Voice::class, Voice.serializer())
+//        contextual(PokeMessage::class, PokeMessage.serializer())
+//        contextual(VipFace::class, VipFace.serializer())
+//        contextual(FlashImage::class, FlashImage.serializer())
+//
+//        contextual(MusicShare::class, MusicShare.serializer())
+//
+//        contextual(MessageSource::class, MessageSource.serializer())
+
+//        contextual(SingleMessage::class, SingleMessage.Serializer)
+        contextual(MessageChain::class, MessageChain.Serializer)
+        contextual(MessageChainImpl::class, MessageChainImpl.serializer())
 
-        contextual(MessageSource::class, MessageSource.serializer())
 
         fun PolymorphicModuleBuilder<MessageMetadata>.messageMetadataSubclasses() {
             subclass(MessageSource::class, MessageSource.serializer())
@@ -132,11 +140,6 @@ private val builtInSerializersModule by lazy {
             subclass(MusicShare::class, MusicShare.serializer())
         }
 
-        contextual(SingleMessage::class, SingleMessage.Serializer)
-        contextual(MessageChain::class, MessageChain.Serializer)
-        contextual(MessageChainImpl::class, MessageChainImpl.serializer())
-
-//        polymorphicDefault(MessageChain::class) { MessageChainImpl.serializer() }
 
         //  polymorphic(SingleMessage::class) {
         //      subclass(MessageSource::class, MessageSource.serializer())
@@ -153,6 +156,23 @@ private val builtInSerializersModule by lazy {
             messageMetadataSubclasses()
         }
 
+        polymorphic(MessageContent::class) {
+            messageContentSubclasses()
+        }
+
+        polymorphic(MessageMetadata::class) {
+            messageMetadataSubclasses()
+        }
+
+        polymorphic(RichMessage::class) {
+            subclass(SimpleServiceMessage::class, SimpleServiceMessage.serializer())
+            subclass(LightApp::class, LightApp.serializer())
+        }
+
+        polymorphic(ServiceMessage::class) {
+            subclass(SimpleServiceMessage::class, SimpleServiceMessage.serializer())
+        }
+
         //contextual(SingleMessage::class, SingleMessage.Serializer)
         // polymorphic(SingleMessage::class, SingleMessage.Serializer) {
         //     messageContentSubclasses()
@@ -180,11 +200,16 @@ internal object MessageSerializersImpl : MessageSerializers {
     override val serializersModule: SerializersModule get() = serializersModuleField ?: builtInSerializersModule
 
     @Synchronized
-    override fun <M : SingleMessage> registerSerializer(baseClass: KClass<M>, serializer: KSerializer<M>) {
+    override fun <M : SingleMessage> registerSerializer(type: KClass<M>, serializer: KSerializer<M>) {
         serializersModuleField = serializersModule.overwriteWith(SerializersModule {
-            contextual(baseClass, serializer)
-            polymorphic(SingleMessage::class) {
-                subclass(baseClass, serializer)
+            // contextual(type, serializer)
+            for (superclass in type.allSuperclasses) {
+                if (superclass.isFinal) continue
+                if (!superclass.isSubclassOf(SingleMessage::class)) continue
+                @Suppress("UNCHECKED_CAST")
+                polymorphic(superclass as KClass<Any>) {
+                    subclass(type, serializer)
+                }
             }
         })
     }

+ 14 - 7
mirai-core-api/src/commonMain/kotlin/message/MessageSerializers.kt

@@ -12,7 +12,9 @@ package net.mamoe.mirai.message
 import kotlinx.serialization.ContextualSerializer
 import kotlinx.serialization.KSerializer
 import kotlinx.serialization.json.Json
-import kotlinx.serialization.modules.*
+import kotlinx.serialization.modules.PolymorphicModuleBuilder
+import kotlinx.serialization.modules.SerializersModule
+import kotlinx.serialization.modules.subclass
 import net.mamoe.mirai.internal.message.MessageSerializersImpl
 import net.mamoe.mirai.message.data.Message
 import net.mamoe.mirai.message.data.MessageChain
@@ -50,21 +52,26 @@ public interface MessageSerializers {
     public val serializersModule: SerializersModule
 
     /**
-     * 注册一个 [SerializersModuleBuilder.contextual] 和 [SingleMessage] 多态域的 [PolymorphicModuleBuilder.subclass].
+     * 注册 [serializer] 到 [type] 的所有为 [SingleMessage] 子类型的超类型的多态域 [PolymorphicModuleBuilder.subclass]
      *
-     * 相当于
+     * 实现:
      * ```
-     * contextual(baseClass, serializer)
-     * polymorphic(SingleMessage::class) {
-     *     subclass(baseClass, serializer)
+     * for (superclass in type.allSuperclasses) {
+     *     if (superclass.isFinal) continue
+     *     if (superclass.isSubclassOf(SingleMessage::class)) continue
+     *     polymorphic(superclass) {
+     *         subclass(type, serializer)
+     *     }
      * }
      * ```
      *
      *
      * 若要自己实现消息类型, 务必在这里注册对应序列化器, 否则在 [MessageChain.serializeToJsonString] 时将会出错.
+     *
+     * @since 2.0, revised 2.3
      */
     @MiraiExperimentalApi
-    public fun <M : SingleMessage> registerSerializer(baseClass: KClass<M>, serializer: KSerializer<M>)
+    public fun <M : SingleMessage> registerSerializer(type: KClass<M>, serializer: KSerializer<M>)
 
     /**
      * 合并 [serializersModule] 到 [MessageSerializers.serializersModule] 并覆盖.

+ 1 - 1
mirai-core-api/src/commonMain/kotlin/message/data/MessageChain.kt

@@ -245,7 +245,7 @@ public interface MessageChain :
      */
     public object Serializer : KSerializer<MessageChain> {
         @Suppress("DEPRECATION_ERROR")
-        private val delegate = ListSerializer(PolymorphicSerializer(SingleMessage::class))
+        private val delegate = ListSerializer(SingleMessage.Serializer)
         override val descriptor: SerialDescriptor = delegate.descriptor
         override fun deserialize(decoder: Decoder): MessageChain = delegate.deserialize(decoder).toMessageChain()
         override fun serialize(encoder: Encoder, value: MessageChain): Unit = delegate.serialize(encoder, value)

+ 9 - 1
mirai-core-api/src/commonMain/kotlin/message/data/MusicShare.kt

@@ -11,6 +11,7 @@
 
 package net.mamoe.mirai.message.data
 
+import kotlinx.serialization.SerialName
 import kotlinx.serialization.Serializable
 import net.mamoe.mirai.utils.MiraiExperimentalApi
 import net.mamoe.mirai.utils.MiraiInternalApi
@@ -22,6 +23,7 @@ import net.mamoe.mirai.utils.safeCast
  * @since 2.1
  */
 @Serializable
+@SerialName(MusicShare.SERIAL_NAME)
 public data class MusicShare(
     /**
      * 音乐应用类型
@@ -93,7 +95,13 @@ public data class MusicShare(
      */
     public companion object Key :
         AbstractPolymorphicMessageKey<@MiraiExperimentalApi MessageContent, MusicShare>
-            (MessageContent, { it.safeCast() })
+            (MessageContent, { it.safeCast() }) {
+
+        /**
+         * @since 2.3
+         */
+        public const val SERIAL_NAME: String = "MusicShare"
+    }
 }
 
 /**

+ 25 - 2
mirai-core-api/src/commonMain/kotlin/message/data/RichMessageOrigin.kt

@@ -37,7 +37,7 @@ import net.mamoe.mirai.utils.safeCast
  * @since 2.3
  */
 @Serializable
-@SerialName("RichMessageOrigin")
+@SerialName(RichMessageOrigin.SERIAL_NAME)
 @MiraiExperimentalApi("RichMessageOrigin 不稳定")
 public class RichMessageOrigin(
     /**
@@ -66,7 +66,30 @@ public class RichMessageOrigin(
 
     override fun contentToString(): String = ""
 
-    public companion object Key : AbstractMessageKey<RichMessageOrigin>({ it.safeCast() })
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (javaClass != other?.javaClass) return false
+
+        other as RichMessageOrigin
+
+        if (origin != other.origin) return false
+        if (resourceId != other.resourceId) return false
+        if (kind != other.kind) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = origin.hashCode()
+        result = 31 * result + (resourceId?.hashCode() ?: 0)
+        result = 31 * result + kind.hashCode()
+        return result
+    }
+
+
+    public companion object Key : AbstractMessageKey<RichMessageOrigin>({ it.safeCast() }) {
+        public const val SERIAL_NAME: String = "RichMessageOrigin"
+    }
 }
 
 /**

+ 18 - 3
mirai-core/src/jvmTest/kotlin/message/data/MessageSerializationTest.kt

@@ -9,14 +9,16 @@
 
 package net.mamoe.mirai.internal.message.data
 
-import kotlinx.serialization.KSerializer
+import kotlinx.serialization.*
 import kotlinx.serialization.json.Json
-import kotlinx.serialization.serializer
+import kotlinx.serialization.json.JsonObject
+import kotlinx.serialization.json.JsonPrimitive
 import net.mamoe.mirai.Mirai
 import net.mamoe.mirai.internal.message.MarketFaceImpl
 import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
 import net.mamoe.mirai.message.MessageSerializers
 import net.mamoe.mirai.message.data.*
+import net.mamoe.mirai.utils.cast
 import org.junit.jupiter.api.BeforeAll
 import org.junit.jupiter.api.Test
 import kotlin.test.assertEquals
@@ -101,7 +103,8 @@ internal class MessageSerializationTest {
         LightApp("lightApp"),
         image.flash(),
         image.toForwardMessage(1L, "test"),
-        MusicShare(MusicKind.NeteaseCloudMusic, "123", "123", "123", "123", "123", "123")
+        MusicShare(MusicKind.NeteaseCloudMusic, "123", "123", "123", "123", "123", "123"),
+        RichMessageOrigin(SimpleServiceMessage(1, "content"), "resource id", RichMessageKind.LONG)
     )
 
     companion object {
@@ -114,7 +117,19 @@ internal class MessageSerializationTest {
 
     @Test
     fun `test polymorphic serialization`() {
+        @Serializable
+        data class RichWrapper(
+            val richMessage: RichMessage
+        )
 
+        val string = format.encodeToString(RichWrapper.serializer(), RichWrapper(SimpleServiceMessage(1, "content")))
+        println(string)
+        var element = format.parseToJsonElement(string)
+        element as JsonObject
+        element = element["richMessage"] as JsonObject
+        assertEquals("SimpleServiceMessage", element["type"]?.cast<JsonPrimitive>()?.content)
+        assertEquals("content", element["content"]?.cast<JsonPrimitive>()?.content)
+        assertEquals(1, element["serviceId"]?.cast<JsonPrimitive>()?.content?.toInt())
     }
 
     @Test