summaryrefslogtreecommitdiff
path: root/runtime/commonMain/src/kotlinx/serialization/Tagged.kt
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/commonMain/src/kotlinx/serialization/Tagged.kt')
-rw-r--r--runtime/commonMain/src/kotlinx/serialization/Tagged.kt311
1 files changed, 311 insertions, 0 deletions
diff --git a/runtime/commonMain/src/kotlinx/serialization/Tagged.kt b/runtime/commonMain/src/kotlinx/serialization/Tagged.kt
new file mode 100644
index 00000000..63f9a2e5
--- /dev/null
+++ b/runtime/commonMain/src/kotlinx/serialization/Tagged.kt
@@ -0,0 +1,311 @@
+/*
+ * Copyright 2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package kotlinx.serialization
+
+import kotlinx.serialization.CompositeDecoder.Companion.READ_ALL
+import kotlinx.serialization.modules.EmptyModule
+import kotlinx.serialization.modules.SerialModule
+import kotlinx.serialization.internal.EnumDescriptor
+
+@SerialInfo
+@Target(AnnotationTarget.PROPERTY)
+annotation class SerialId(val id: Int)
+
+@SerialInfo
+@Target(AnnotationTarget.PROPERTY)
+annotation class SerialTag(val tag: String)
+
+
+abstract class TaggedEncoder<Tag : Any?> : Encoder, CompositeEncoder {
+
+ /**
+ * Provides a tag object for given serial descriptor and index.
+ * Tag object allows to associate given user information with particular element of composite serializable entity..
+ */
+ protected abstract fun SerialDescriptor.getTag(index: Int): Tag
+
+ override val context: SerialModule
+ get() = EmptyModule
+
+ // ---- API ----
+ open fun encodeTaggedValue(tag: Tag, value: Any): Unit
+ = throw SerializationException("Non-serializable ${value::class} is not supported by ${this::class} encoder")
+
+ open fun encodeTaggedNotNullMark(tag: Tag) {}
+ open fun encodeTaggedNull(tag: Tag): Unit = throw SerializationException("null is not supported")
+
+ private fun encodeTaggedNullable(tag: Tag, value: Any?) {
+ if (value == null) {
+ encodeTaggedNull(tag)
+ } else {
+ encodeTaggedNotNullMark(tag)
+ encodeTaggedValue(tag, value)
+ }
+ }
+
+ open fun encodeTaggedUnit(tag: Tag) = encodeTaggedValue(tag, Unit)
+ open fun encodeTaggedInt(tag: Tag, value: Int) = encodeTaggedValue(tag, value)
+ open fun encodeTaggedByte(tag: Tag, value: Byte) = encodeTaggedValue(tag, value)
+ open fun encodeTaggedShort(tag: Tag, value: Short) = encodeTaggedValue(tag, value)
+ open fun encodeTaggedLong(tag: Tag, value: Long) = encodeTaggedValue(tag, value)
+ open fun encodeTaggedFloat(tag: Tag, value: Float) = encodeTaggedValue(tag, value)
+ open fun encodeTaggedDouble(tag: Tag, value: Double) = encodeTaggedValue(tag, value)
+ open fun encodeTaggedBoolean(tag: Tag, value: Boolean) = encodeTaggedValue(tag, value)
+ open fun encodeTaggedChar(tag: Tag, value: Char) = encodeTaggedValue(tag, value)
+ open fun encodeTaggedString(tag: Tag, value: String) = encodeTaggedValue(tag, value)
+
+ open fun encodeTaggedEnum(
+ tag: Tag,
+ enumDescription: EnumDescriptor,
+ ordinal: Int
+ ) = encodeTaggedValue(tag, ordinal)
+
+ // ---- Implementation of low-level API ----
+
+ fun encodeElement(desc: SerialDescriptor, index: Int): Boolean {
+ val tag = desc.getTag(index)
+ val shouldWriteElement = shouldWriteElement(desc, tag, index)
+ if (shouldWriteElement) {
+ pushTag(tag)
+ }
+ return shouldWriteElement
+ }
+
+ // For format-specific behaviour, invoked only on
+ open fun shouldWriteElement(desc: SerialDescriptor, tag: Tag, index: Int) = true
+
+ final override fun encodeNotNullMark() = encodeTaggedNotNullMark(currentTag)
+ final override fun encodeNull() = encodeTaggedNull(popTag())
+
+ final override fun encodeUnit() = encodeTaggedUnit(popTag())
+ final override fun encodeBoolean(value: Boolean) = encodeTaggedBoolean(popTag(), value)
+ final override fun encodeByte(value: Byte) = encodeTaggedByte(popTag(), value)
+ final override fun encodeShort(value: Short) = encodeTaggedShort(popTag(), value)
+ final override fun encodeInt(value: Int) = encodeTaggedInt(popTag(), value)
+ final override fun encodeLong(value: Long) = encodeTaggedLong(popTag(), value)
+ final override fun encodeFloat(value: Float) = encodeTaggedFloat(popTag(), value)
+ final override fun encodeDouble(value: Double) = encodeTaggedDouble(popTag(), value)
+ final override fun encodeChar(value: Char) = encodeTaggedChar(popTag(), value)
+ final override fun encodeString(value: String) = encodeTaggedString(popTag(), value)
+
+ final override fun encodeEnum(
+ enumDescription: EnumDescriptor,
+ ordinal: Int
+ ) = encodeTaggedEnum(popTag(), enumDescription, ordinal)
+
+ override fun beginStructure(desc: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeEncoder {
+ return this
+ }
+
+ final override fun endStructure(desc: SerialDescriptor) {
+ if (tagStack.isNotEmpty()) popTag(); endEncode(desc)
+ }
+
+ /**
+ * Format-specific replacement for [endStructure], because latter is overridden to manipulate tag stack.
+ */
+ open fun endEncode(desc: SerialDescriptor) {}
+
+ final override fun encodeNonSerializableElement(desc: SerialDescriptor, index: Int, value: Any) = encodeTaggedValue(desc.getTag(index), value)
+
+ final override fun encodeUnitElement(desc: SerialDescriptor, index: Int) = encodeTaggedUnit(desc.getTag(index))
+ final override fun encodeBooleanElement(desc: SerialDescriptor, index: Int, value: Boolean) = encodeTaggedBoolean(desc.getTag(index), value)
+ final override fun encodeByteElement(desc: SerialDescriptor, index: Int, value: Byte) = encodeTaggedByte(desc.getTag(index), value)
+ final override fun encodeShortElement(desc: SerialDescriptor, index: Int, value: Short) = encodeTaggedShort(desc.getTag(index), value)
+ final override fun encodeIntElement(desc: SerialDescriptor, index: Int, value: Int) = encodeTaggedInt(desc.getTag(index), value)
+ final override fun encodeLongElement(desc: SerialDescriptor, index: Int, value: Long) = encodeTaggedLong(desc.getTag(index), value)
+ final override fun encodeFloatElement(desc: SerialDescriptor, index: Int, value: Float) = encodeTaggedFloat(desc.getTag(index), value)
+ final override fun encodeDoubleElement(desc: SerialDescriptor, index: Int, value: Double) = encodeTaggedDouble(desc.getTag(index), value)
+ final override fun encodeCharElement(desc: SerialDescriptor, index: Int, value: Char) = encodeTaggedChar(desc.getTag(index), value)
+ final override fun encodeStringElement(desc: SerialDescriptor, index: Int, value: String) = encodeTaggedString(desc.getTag(index), value)
+
+ final override fun <T : Any?> encodeSerializableElement(desc: SerialDescriptor, index: Int, serializer: SerializationStrategy<T>, value: T) {
+ if (encodeElement(desc, index))
+ encodeSerializableValue(serializer, value)
+ }
+
+ final override fun <T : Any> encodeNullableSerializableElement(desc: SerialDescriptor, index: Int, serializer: SerializationStrategy<T>, value: T?) {
+ if (encodeElement(desc, index))
+ encodeNullableSerializableValue(serializer, value)
+ }
+
+ private val tagStack = arrayListOf<Tag>()
+ protected val currentTag: Tag
+ get() = tagStack.last()
+ protected val currentTagOrNull
+ get() = tagStack.lastOrNull()
+
+ private fun pushTag(name: Tag) {
+ tagStack.add(name)
+ }
+
+ private fun popTag() =
+ if (tagStack.isNotEmpty())
+ tagStack.removeAt(tagStack.lastIndex)
+ else
+ throw SerializationException("No tag in stack for requested element")
+}
+
+abstract class IntTaggedEncoder : TaggedEncoder<Int?>() {
+ final override fun SerialDescriptor.getTag(index: Int): Int? = getSerialId(this, index)
+}
+
+abstract class NamedValueEncoder(val rootName: String = "") : TaggedEncoder<String>() {
+ final override fun SerialDescriptor.getTag(index: Int): String = nested(elementName(this, index))
+
+ protected fun nested(nestedName: String) = composeName(currentTagOrNull ?: rootName, nestedName)
+ open fun elementName(desc: SerialDescriptor, index: Int) = desc.getElementName(index)
+ open fun composeName(parentName: String, childName: String) = if (parentName.isEmpty()) childName else parentName + "." + childName
+}
+
+internal fun getSerialId(desc: SerialDescriptor, index: Int): Int?
+ = desc.findAnnotation<SerialId>(index)?.id
+
+internal fun getSerialTag(desc: SerialDescriptor, index: Int): String?
+ = desc.findAnnotation<SerialTag>(index)?.tag
+
+abstract class TaggedDecoder<Tag : Any?> : Decoder, CompositeDecoder {
+ override val context: SerialModule
+ get() = EmptyModule
+
+ override val updateMode: UpdateMode = UpdateMode.UPDATE
+
+ protected abstract fun SerialDescriptor.getTag(index: Int): Tag
+
+
+ // ---- API ----
+ open fun decodeTaggedValue(tag: Tag): Any
+ = throw SerializationException("${this::class} can't retrieve untyped values")
+
+ open fun decodeTaggedNotNullMark(tag: Tag): Boolean = true
+ open fun decodeTaggedNull(tag: Tag): Nothing? = null
+
+ private fun decodeTaggedNullable(tag: Tag): Any? {
+ return if (decodeTaggedNotNullMark(tag)) {
+ decodeTaggedValue(tag)
+ } else {
+ decodeTaggedNull(tag)
+ }
+ }
+
+ open fun decodeTaggedUnit(tag: Tag): Unit = decodeTaggedValue(tag) as Unit
+ open fun decodeTaggedBoolean(tag: Tag): Boolean = decodeTaggedValue(tag) as Boolean
+ open fun decodeTaggedByte(tag: Tag): Byte = decodeTaggedValue(tag) as Byte
+ open fun decodeTaggedShort(tag: Tag): Short = decodeTaggedValue(tag) as Short
+ open fun decodeTaggedInt(tag: Tag): Int = decodeTaggedValue(tag) as Int
+ open fun decodeTaggedLong(tag: Tag): Long = decodeTaggedValue(tag) as Long
+ open fun decodeTaggedFloat(tag: Tag): Float = decodeTaggedValue(tag) as Float
+ open fun decodeTaggedDouble(tag: Tag): Double = decodeTaggedValue(tag) as Double
+ open fun decodeTaggedChar(tag: Tag): Char = decodeTaggedValue(tag) as Char
+ open fun decodeTaggedString(tag: Tag): String = decodeTaggedValue(tag) as String
+ open fun decodeTaggedEnum(tag: Tag, enumDescription: EnumDescriptor): Int = decodeTaggedValue(tag) as Int
+
+
+ // ---- Implementation of low-level API ----
+
+ final override fun decodeNotNullMark(): Boolean = decodeTaggedNotNullMark(currentTag)
+ final override fun decodeNull(): Nothing? = null
+
+ final override fun decodeUnit() = decodeTaggedUnit(popTag())
+ final override fun decodeBoolean(): Boolean = decodeTaggedBoolean(popTag())
+ final override fun decodeByte(): Byte = decodeTaggedByte(popTag())
+ final override fun decodeShort(): Short = decodeTaggedShort(popTag())
+ final override fun decodeInt(): Int = decodeTaggedInt(popTag())
+ final override fun decodeLong(): Long = decodeTaggedLong(popTag())
+ final override fun decodeFloat(): Float = decodeTaggedFloat(popTag())
+ final override fun decodeDouble(): Double = decodeTaggedDouble(popTag())
+ final override fun decodeChar(): Char = decodeTaggedChar(popTag())
+ final override fun decodeString(): String = decodeTaggedString(popTag())
+
+ final override fun decodeEnum(enumDescription: EnumDescriptor): Int = decodeTaggedEnum(popTag(), enumDescription)
+
+ override fun beginStructure(desc: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder {
+ return this
+ }
+
+ /**
+ * Assumes that all elements go in order by default.
+ */
+ override fun decodeElementIndex(desc: SerialDescriptor): Int = READ_ALL
+
+ final override fun decodeUnitElement(desc: SerialDescriptor, index: Int) = decodeTaggedUnit(desc.getTag(index))
+ final override fun decodeBooleanElement(desc: SerialDescriptor, index: Int): Boolean = decodeTaggedBoolean(desc.getTag(index))
+ final override fun decodeByteElement(desc: SerialDescriptor, index: Int): Byte = decodeTaggedByte(desc.getTag(index))
+ final override fun decodeShortElement(desc: SerialDescriptor, index: Int): Short = decodeTaggedShort(desc.getTag(index))
+ final override fun decodeIntElement(desc: SerialDescriptor, index: Int): Int = decodeTaggedInt(desc.getTag(index))
+ final override fun decodeLongElement(desc: SerialDescriptor, index: Int): Long = decodeTaggedLong(desc.getTag(index))
+ final override fun decodeFloatElement(desc: SerialDescriptor, index: Int): Float = decodeTaggedFloat(desc.getTag(index))
+ final override fun decodeDoubleElement(desc: SerialDescriptor, index: Int): Double = decodeTaggedDouble(desc.getTag(index))
+ final override fun decodeCharElement(desc: SerialDescriptor, index: Int): Char = decodeTaggedChar(desc.getTag(index))
+ final override fun decodeStringElement(desc: SerialDescriptor, index: Int): String = decodeTaggedString(desc.getTag(index))
+
+ final override fun <T : Any?> decodeSerializableElement(desc: SerialDescriptor, index: Int, deserializer: DeserializationStrategy<T>): T =
+ tagBlock(desc.getTag(index)) { decodeSerializableValue(deserializer) }
+
+ final override fun <T : Any> decodeNullableSerializableElement(desc: SerialDescriptor, index: Int, deserializer: DeserializationStrategy<T?>): T? =
+ tagBlock(desc.getTag(index)) { decodeNullableSerializableValue(deserializer) }
+
+ override fun <T> updateSerializableElement(desc: SerialDescriptor, index: Int, deserializer: DeserializationStrategy<T>, old: T): T =
+ tagBlock(desc.getTag(index)) { updateSerializableValue(deserializer, old) }
+
+ override fun <T : Any> updateNullableSerializableElement(desc: SerialDescriptor, index: Int, deserializer: DeserializationStrategy<T?>, old: T?): T? =
+ tagBlock(desc.getTag(index)) { updateNullableSerializableValue(deserializer, old) }
+
+ private fun <E> tagBlock(tag: Tag, block: () -> E): E {
+ pushTag(tag)
+ val r = block()
+ if (!flag) {
+ popTag()
+ }
+ flag = false
+ return r
+ }
+
+ private val tagStack = arrayListOf<Tag>()
+ protected val currentTag: Tag
+ get() = tagStack.last()
+ protected val currentTagOrNull
+ get() = tagStack.lastOrNull()
+
+ protected fun pushTag(name: Tag) {
+ tagStack.add(name)
+ }
+
+ private var flag = false
+
+ private fun popTag(): Tag {
+ val r = tagStack.removeAt(tagStack.lastIndex)
+ flag = true
+ return r
+ }
+}
+
+abstract class IntTaggedDecoder: TaggedDecoder<Int?>() {
+ final override fun SerialDescriptor.getTag(index: Int): Int? = getSerialId(this, index)
+}
+
+abstract class StringTaggedDecoder : TaggedDecoder<String?>() {
+ final override fun SerialDescriptor.getTag(index: Int): String? = getSerialTag(this, index)
+}
+
+abstract class NamedValueDecoder(val rootName: String = "") : TaggedDecoder<String>() {
+ final override fun SerialDescriptor.getTag(index: Int): String = nested(elementName(this, index))
+
+ protected fun nested(nestedName: String) = composeName(currentTagOrNull ?: rootName, nestedName)
+ open fun elementName(desc: SerialDescriptor, index: Int) = desc.getElementName(index)
+ open fun composeName(parentName: String, childName: String) = if (parentName.isEmpty()) childName else parentName + "." + childName
+}