Support optional elements

This commit is contained in:
Him188 2020-03-06 17:51:48 +08:00
parent e52a394060
commit d01c3c4675
7 changed files with 155 additions and 46 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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