mirror of
https://github.com/mamoe/mirai.git
synced 2025-03-11 21:30:11 +08:00
Support optional elements
This commit is contained in:
parent
e52a394060
commit
d01c3c4675
@ -19,7 +19,8 @@ buildscript {
|
||||
classpath("com.android.tools.build:gradle:3.5.3")
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
|
||||
classpath("com.github.jengelman.gradle.plugins:shadow:5.2.0")
|
||||
classpath("org.jetbrains.kotlin:kotlin-serialization:$kotlinVersion")
|
||||
@kotlin.Suppress("GradleDependency") // 1.3.70 有 bug, 无法编译添加 SerialInfo
|
||||
classpath("org.jetbrains.kotlin:kotlin-serialization:1.3.61") // 不要用 $kotlinVersion
|
||||
classpath("org.jetbrains.kotlinx:atomicfu-gradle-plugin:$atomicFuVersion")
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import kotlinx.serialization.SerializationStrategy
|
||||
|
||||
interface IOFormat : SerialFormat {
|
||||
|
||||
fun <T> dump(serializer: SerializationStrategy<T>, input: Input): ByteArray
|
||||
fun <T> dump(serializer: SerializationStrategy<T>, output: Output): ByteArray
|
||||
|
||||
fun <T> load(deserializer: DeserializationStrategy<T>, output: Output): T
|
||||
fun <T> load(deserializer: DeserializationStrategy<T>, input: Input): T
|
||||
}
|
||||
|
@ -10,13 +10,14 @@
|
||||
package net.mamoe.mirai.qqandroid.io.serialization.jce
|
||||
|
||||
import kotlinx.serialization.*
|
||||
import kotlinx.serialization.builtins.ByteArraySerializer
|
||||
import kotlinx.serialization.internal.TaggedDecoder
|
||||
import kotlinx.serialization.modules.SerialModule
|
||||
import kotlinx.serialization.protobuf.ProtoId
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.Jce
|
||||
|
||||
|
||||
@OptIn(InternalSerializationApi::class) // 将来 kotlinx 修改后再复制过来 mirai.
|
||||
private class JceDecoder(
|
||||
internal class JceDecoder(
|
||||
val jce: JceInput, override val context: SerialModule
|
||||
) : TaggedDecoder<JceTag>() {
|
||||
override val updateMode: UpdateMode
|
||||
@ -25,23 +26,103 @@ private class JceDecoder(
|
||||
override fun SerialDescriptor.getTag(index: Int): JceTag {
|
||||
val annotations = this.getElementAnnotations(index)
|
||||
|
||||
val id = (annotations.asSequence().filterIsInstance<JceId>().firstOrNull()?.id
|
||||
?: annotations.asSequence().filterIsInstance<ProtoId>().firstOrNull()?.id) // 旧版本兼容
|
||||
?: error("cannot find @JceId or @ProtoId for ${this.getElementName(index)} in ${this.serialName}")
|
||||
val id = annotations.filterIsInstance<JceId>().single().id
|
||||
// ?: error("cannot find @JceId or @ProtoId for ${this.getElementName(index)} in ${this.serialName}")
|
||||
println("getTag: ${this.getElementName(index)}=$id")
|
||||
|
||||
return JceTag(
|
||||
id,
|
||||
this.getElementDescriptor(index).isNullable
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
override fun beginStructure(descriptor: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder {
|
||||
TODO("Not yet implemented")
|
||||
fun SerialDescriptor.getJceTagId(index: Int): Int {
|
||||
return getElementAnnotations(index).filterIsInstance<JceId>().single().id
|
||||
}
|
||||
|
||||
|
||||
private val ByteArraySerializer = ByteArraySerializer()
|
||||
|
||||
// TODO: 2020/3/6 can be object
|
||||
private inner class SimpleByteArrayReader() : CompositeDecoder by this {
|
||||
override fun decodeSequentially(): Boolean = true
|
||||
override fun decodeByteElement(descriptor: SerialDescriptor, index: Int): Byte {
|
||||
return jce.input.readByte()
|
||||
}
|
||||
|
||||
override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun decodeCollectionSize(descriptor: SerialDescriptor): Int {
|
||||
return jce.useHead { jce.readJceIntValue(it) }
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: 2020/3/6 can be object
|
||||
private inner class ListReader() : CompositeDecoder by this {
|
||||
override fun decodeSequentially(): Boolean = false
|
||||
override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun decodeCollectionSize(descriptor: SerialDescriptor): Int {
|
||||
return jce.useHead { jce.readJceIntValue(it) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun beginStructure(descriptor: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder {
|
||||
if (descriptor == ByteArraySerializer.descriptor) {
|
||||
return jce.skipToHeadAndUseIfPossibleOrFail(popTag().id) {
|
||||
when (it.type) {
|
||||
Jce.SIMPLE_LIST -> SimpleByteArrayReader()
|
||||
Jce.LIST -> ListReader()
|
||||
else -> error("type mismatch. Expected SIMPLE_LIST or LIST, got ${it.type} instead")
|
||||
}
|
||||
}
|
||||
}
|
||||
return when (descriptor.kind) {
|
||||
StructureKind.MAP -> {
|
||||
error("map")
|
||||
}
|
||||
StructureKind.LIST -> ListReader()
|
||||
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
|
||||
override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T {
|
||||
println("decodeSerializableValue: ${deserializer.descriptor}")
|
||||
return super.decodeSerializableValue(deserializer)
|
||||
}
|
||||
|
||||
private var currentIndex = 0
|
||||
|
||||
|
||||
override fun decodeSequentially(): Boolean = false
|
||||
override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
|
||||
TODO("Not yet implemented")
|
||||
val jceHead = jce.currentHeadOrNull ?: return CompositeDecoder.READ_DONE
|
||||
repeat(descriptor.elementsCount){
|
||||
val tag = descriptor.getJceTagId(it)
|
||||
if (tag == jceHead.tag) {
|
||||
return it
|
||||
}
|
||||
}
|
||||
|
||||
return CompositeDecoder.READ_DONE // optional support
|
||||
}
|
||||
|
||||
override fun decodeTaggedNull(tag: JceTag): Nothing? {
|
||||
println("decodeTaggedNull")
|
||||
return super.decodeTaggedNull(tag)
|
||||
}
|
||||
|
||||
override fun <T : Any> decodeNullableSerializableValue(deserializer: DeserializationStrategy<T?>): T? {
|
||||
return super.decodeNullableSerializableValue(deserializer)
|
||||
}
|
||||
|
||||
override fun decodeTaggedValue(tag: JceTag): Any {
|
||||
println("decodeTaggedValue")
|
||||
return super.decodeTaggedValue(tag)
|
||||
}
|
||||
|
||||
override fun decodeTaggedInt(tag: JceTag): Int =
|
||||
@ -77,7 +158,6 @@ private class JceDecoder(
|
||||
}
|
||||
|
||||
override fun decodeTaggedNotNullMark(tag: JceTag): Boolean {
|
||||
println("!! decodeTaggedNotNullMark: $tag")
|
||||
return super.decodeTaggedNotNullMark(tag)
|
||||
return jce.skipToHeadOrNull(tag.id) != null
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ import net.mamoe.mirai.utils.io.readString
|
||||
/**
|
||||
* Jce Input. 需要手动管理 head.
|
||||
*/
|
||||
class JceInput(
|
||||
internal class JceInput(
|
||||
val input: Input, val charset: JceCharset
|
||||
) {
|
||||
private var _head: JceHead? = null
|
||||
@ -95,7 +95,7 @@ class JceInput(
|
||||
}
|
||||
|
||||
tailrec fun skipToHeadOrNull(tag: Int): JceHead? {
|
||||
val current: JceHead = currentHead // no backing field
|
||||
val current: JceHead = currentHeadOrNull ?: return null // no backing field
|
||||
|
||||
return when {
|
||||
current.tag > tag -> null // tag 大了,即找不到
|
||||
@ -167,6 +167,7 @@ class JceInput(
|
||||
|
||||
// region readers
|
||||
fun readJceIntValue(head: JceHead): Int {
|
||||
println("readJceIntValue: $head")
|
||||
return when (head.type) {
|
||||
Jce.ZERO_TYPE -> 0
|
||||
Jce.BYTE -> input.readByte().toInt()
|
||||
@ -197,6 +198,7 @@ class JceInput(
|
||||
}
|
||||
|
||||
fun readJceByteValue(head: JceHead): Byte {
|
||||
println("readJceByteValue: $head")
|
||||
return when (head.type) {
|
||||
Jce.ZERO_TYPE -> 0
|
||||
Jce.BYTE -> input.readByte()
|
||||
|
@ -9,30 +9,15 @@
|
||||
|
||||
package net.mamoe.mirai.qqandroid.io.serialization.jce
|
||||
|
||||
import io.ktor.utils.io.core.*
|
||||
import kotlinx.serialization.*
|
||||
import kotlinx.serialization.internal.TaggedDecoder
|
||||
import io.ktor.utils.io.core.Input
|
||||
import io.ktor.utils.io.core.Output
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
import kotlinx.serialization.SerialFormat
|
||||
import kotlinx.serialization.SerializationStrategy
|
||||
import kotlinx.serialization.modules.EmptyModule
|
||||
import kotlinx.serialization.modules.SerialModule
|
||||
import kotlinx.serialization.protobuf.ProtoId
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.IOFormat
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.Jce
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.BYTE
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.DOUBLE
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.FLOAT
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.INT
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.LIST
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.LONG
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.MAP
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.SHORT
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.SIMPLE_LIST
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.STRING1
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.STRING4
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.STRUCT_BEGIN
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.STRUCT_END
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.Jce.Companion.ZERO_TYPE
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.JceCharset
|
||||
import net.mamoe.mirai.utils.io.readString
|
||||
|
||||
/**
|
||||
* Jce 数据结构序列化和反序列化器.
|
||||
@ -40,18 +25,19 @@ import net.mamoe.mirai.utils.io.readString
|
||||
* @author Him188
|
||||
*/
|
||||
class JceNew(
|
||||
override val context: SerialModule
|
||||
override val context: SerialModule,
|
||||
val charset: JceCharset
|
||||
) : SerialFormat, IOFormat {
|
||||
companion object Default : IOFormat by JceNew(
|
||||
EmptyModule
|
||||
)
|
||||
|
||||
override fun <T> dump(serializer: SerializationStrategy<T>, input: Input): ByteArray {
|
||||
override fun <T> dump(serializer: SerializationStrategy<T>, output: Output): ByteArray {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun <T> load(deserializer: DeserializationStrategy<T>, output: Output): T {
|
||||
TODO("Not yet implemented")
|
||||
override fun <T> load(deserializer: DeserializationStrategy<T>, input: Input): T {
|
||||
return JceDecoder(JceInput(input, charset), context).decodeSerializableValue(deserializer)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val UTF_8 = JceNew(EmptyModule, JceCharset.UTF8)
|
||||
val GBK = JceNew(EmptyModule, JceCharset.GBK)
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ import net.mamoe.mirai.qqandroid.io.serialization.Jce
|
||||
* 标注 JCE 序列化时使用的 ID
|
||||
*/
|
||||
@SerialInfo
|
||||
@Target(AnnotationTarget.PROPERTY)
|
||||
annotation class JceId(val id: Int)
|
||||
|
||||
/**
|
||||
@ -31,6 +32,9 @@ internal data class JceTag(
|
||||
val isNullable: Boolean
|
||||
)
|
||||
|
||||
fun JceHead.checkType(type: Byte) {
|
||||
check(this.type == type) {"type mismatch. Expected $type, actual ${this.type}"}
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal fun Output.writeJceHead(type: Byte, tag: Int) {
|
||||
|
@ -1,9 +1,12 @@
|
||||
@file:Suppress("unused")
|
||||
@file:Suppress("unused", "DEPRECATION_ERROR")
|
||||
|
||||
package net.mamoe.mirai.qqandroid.io.serialization
|
||||
|
||||
import io.ktor.utils.io.core.*
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.jce.JceId
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.jce.JceInput
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.jce.JceNew
|
||||
import net.mamoe.mirai.qqandroid.io.serialization.jce.writeJceHead
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
@ -35,6 +38,39 @@ internal const val ZERO_TYPE: Byte = 12
|
||||
*/
|
||||
@Suppress("INVISIBLE_MEMBER") // bug
|
||||
internal class JceInputTest {
|
||||
@Serializable
|
||||
data class TestSerializableClassA(
|
||||
@JceId(0) val byte: Byte = 66,
|
||||
@JceId(1) val short: Short = 123,
|
||||
@JceId(3) val int: Int = 123456,
|
||||
@JceId(8) val float: Float = 123f,
|
||||
@JceId(15) val long: Long = 123456789123456789L,
|
||||
@JceId(16) val double: Double = 123456.0,
|
||||
@JceId(17) val boolean: Boolean = true,
|
||||
@JceId(11111) val nullable: Int? = null
|
||||
)
|
||||
|
||||
@Test
|
||||
fun testSerializableClassA() {
|
||||
val input = buildPacket {
|
||||
writeJceHead(BYTE, 0)
|
||||
writeByte(66)
|
||||
writeJceHead(SHORT, 1)
|
||||
writeShort(123)
|
||||
writeJceHead(INT, 3)
|
||||
writeInt(123456)
|
||||
writeJceHead(FLOAT, 8)
|
||||
writeFloat(123f)
|
||||
writeJceHead(LONG, 15)
|
||||
writeLong(123456789123456789L)
|
||||
writeJceHead(DOUBLE, 16)
|
||||
writeDouble(123456.0)
|
||||
writeJceHead(BYTE, 17)
|
||||
writeByte(1) // boolean
|
||||
}
|
||||
|
||||
assertEquals(TestSerializableClassA(), JceNew.UTF_8.load(TestSerializableClassA.serializer(), input))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHeadSkip() {
|
||||
|
Loading…
Reference in New Issue
Block a user