Add stream codec

This commit is contained in:
DBotThePony 2024-08-10 19:20:52 +07:00
parent 7babb729b0
commit 57aea29da2
Signed by: DBot
GPG Key ID: DCC23B5715498507

View File

@ -0,0 +1,338 @@
package ru.dbotthepony.kstarbound.io
import ru.dbotthepony.kommons.io.readBigDecimal
import ru.dbotthepony.kommons.io.readBinaryString
import ru.dbotthepony.kommons.io.readByteArray
import ru.dbotthepony.kommons.io.readKOptional
import ru.dbotthepony.kommons.io.readMap
import ru.dbotthepony.kommons.io.readOptional
import ru.dbotthepony.kommons.io.readSignedVarInt
import ru.dbotthepony.kommons.io.readSignedVarLong
import ru.dbotthepony.kommons.io.readVarInt
import ru.dbotthepony.kommons.io.readVarLong
import ru.dbotthepony.kommons.io.writeBigDecimal
import ru.dbotthepony.kommons.io.writeBinaryString
import ru.dbotthepony.kommons.io.writeByteArray
import ru.dbotthepony.kommons.io.writeKOptional
import ru.dbotthepony.kommons.io.writeMap
import ru.dbotthepony.kommons.io.writeOptional
import ru.dbotthepony.kommons.io.writeSignedVarInt
import ru.dbotthepony.kommons.io.writeSignedVarLong
import ru.dbotthepony.kommons.io.writeVarInt
import ru.dbotthepony.kommons.io.writeVarLong
import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.kommons.util.KOptional
import java.io.DataInputStream
import java.io.DataOutputStream
import java.util.*
import kotlin.reflect.KClass
/**
* Represents value which can be encoded onto or decoded from stream.
*/
interface StreamCodec<V> {
fun read(stream: DataInputStream): V
fun write(stream: DataOutputStream, value: V)
/**
* Defensive copy
*/
fun copy(value: V): V
/**
* Optional custom equality check
*/
fun compare(a: V, b: V): Boolean {
return a == b
}
class Impl<V>(
private val reader: (stream: DataInputStream) -> V,
private val writer: (stream: DataOutputStream, value: V) -> Unit,
private val copier: ((value: V) -> V) = { it },
private val comparator: ((a: V, b: V) -> Boolean) = { a, b -> a == b }
) : StreamCodec<V> {
constructor(
reader: (stream: DataInputStream) -> V,
payloadSize: Long,
writer: (stream: DataOutputStream, value: V) -> Unit,
copier: ((value: V) -> V) = { it },
comparator: ((a: V, b: V) -> Boolean) = { a, b -> a == b }
) : this({ stream -> reader.invoke(stream) }, writer, copier, comparator)
override fun read(stream: DataInputStream): V {
return reader.invoke(stream)
}
override fun write(stream: DataOutputStream, value: V) {
writer.invoke(stream, value)
}
override fun copy(value: V): V {
return copier.invoke(value)
}
override fun compare(a: V, b: V): Boolean {
return comparator.invoke(a, b)
}
}
class Nullable<V>(val parent: StreamCodec<V>) : StreamCodec<V?> {
override fun read(stream: DataInputStream): V? {
return if (stream.read() == 0) null else parent.read(stream)
}
override fun write(stream: DataOutputStream, value: V?) {
if (value === null)
stream.write(0)
else {
stream.write(1)
parent.write(stream, value)
}
}
override fun copy(value: V?): V? {
return if (value === null) null else parent.copy(value)
}
override fun compare(a: V?, b: V?): Boolean {
if (a === null && b === null) return true
if (a === null || b === null) return false
return parent.compare(a, b)
}
}
class Collection<E, C : MutableCollection<E>>(val elementCodec: StreamCodec<E>, val collectionFactory: (Int) -> C) : StreamCodec<C> {
override fun read(stream: DataInputStream): C {
val size = stream.readVarInt()
if (size <= 0) {
return collectionFactory.invoke(0)
}
val collection = collectionFactory.invoke(size)
for (i in 0 until size) {
collection.add(elementCodec.read(stream))
}
return collection
}
override fun write(stream: DataOutputStream, value: C) {
stream.writeVarInt(value.size)
value.forEach { elementCodec.write(stream, it) }
}
override fun copy(value: C): C {
val new = collectionFactory.invoke(value.size)
value.forEach { new.add(elementCodec.copy(it)) }
return new
}
}
class Map<K, V, C : MutableMap<K, V>>(val keyCodec: StreamCodec<K>, val valueCodec: StreamCodec<V>, val factory: (Int) -> C): StreamCodec<C> {
override fun read(stream: DataInputStream): C {
return stream.readMap(keyCodec::read, valueCodec::read, factory)
}
override fun write(stream: DataOutputStream, value: C) {
stream.writeMap(value, keyCodec::write, valueCodec::write)
}
override fun copy(value: C): C {
val new = factory.invoke(value.size)
for ((k, v) in value.entries) {
new[keyCodec.copy(k)] = valueCodec.copy(v)
}
return new
}
}
class Enum<V : kotlin.Enum<V>>(clazz: Class<out V>) : StreamCodec<V> {
val clazz = searchClass(clazz)
val values: List<V> = listOf(*this.clazz.enumConstants!!)
val valuesMap = values.associateBy { it.name }
override fun read(stream: DataInputStream): V {
val id = stream.readVarInt()
return values.getOrNull(id) ?: throw NoSuchElementException("No such enum with index $id")
}
override fun write(stream: DataOutputStream, value: V) {
stream.writeVarInt(value.ordinal)
}
override fun copy(value: V): V {
return value
}
override fun compare(a: V, b: V): Boolean {
return a === b
}
companion object {
/**
* FIXME: enums with abstract methods which get compiled to subclasses, whose DO NOT expose "parent's" enum constants array
*
* is there an already existing solution?
*/
fun <V : kotlin.Enum<V>> searchClass(clazz: Class<out V>): Class<out V> {
var search: Class<*> = clazz
while (search.enumConstants == null && search.superclass != null) {
search = search.superclass
}
if (search.enumConstants == null) {
throw ClassCastException("$clazz does not represent an enum or enum subclass")
}
return search as Class<out V>
}
}
}
class KOptional<V>(val codec: StreamCodec<V>) : StreamCodec<ru.dbotthepony.kommons.util.KOptional<V>> {
override fun read(stream: DataInputStream): ru.dbotthepony.kommons.util.KOptional<V> {
return stream.readKOptional(codec::read)
}
override fun write(stream: DataOutputStream, value: ru.dbotthepony.kommons.util.KOptional<V>) {
stream.writeKOptional(value, codec::write)
}
override fun copy(value: ru.dbotthepony.kommons.util.KOptional<V>): ru.dbotthepony.kommons.util.KOptional<V> {
return value.map(codec::copy)
}
}
class Optional<V : Any>(val codec: StreamCodec<V>) : StreamCodec<java.util.Optional<V>> {
override fun read(stream: DataInputStream): java.util.Optional<V> {
return stream.readOptional(codec::read)
}
override fun write(stream: DataOutputStream, value: java.util.Optional<V>) {
stream.writeOptional(value, codec::write)
}
override fun copy(value: java.util.Optional<V>): java.util.Optional<V> {
return value.map(codec::copy)
}
}
class Mapping<T, R>(private val codec: StreamCodec<T>, private val from: T.() -> R, private val to: R.() -> T, private val copy: R.() -> R = { this }) : StreamCodec<R> {
override fun read(stream: DataInputStream): R {
return from(codec.read(stream))
}
override fun write(stream: DataOutputStream, value: R) {
codec.write(stream, to(value))
}
override fun copy(value: R): R {
return copy.invoke(value)
}
}
class Pair<L, R>(private val left: StreamCodec<L>, private val right: StreamCodec<R>) : StreamCodec<kotlin.Pair<L, R>> {
override fun read(stream: DataInputStream): kotlin.Pair<L, R> {
val left = left.read(stream)
val right = right.read(stream)
return left to right
}
override fun write(stream: DataOutputStream, value: kotlin.Pair<L, R>) {
left.write(stream, value.first)
right.write(stream, value.second)
}
override fun copy(value: kotlin.Pair<L, R>): kotlin.Pair<L, R> {
val left = left.copy(value.first)
val right = right.copy(value.second)
if (left !== this.left && right !== this.right) {
return left to right
} else {
return value
}
}
}
}
fun <T, R> StreamCodec<T>.map(from: T.() -> R, to: R.() -> T): StreamCodec<R> {
return StreamCodec.Mapping(this, from, to)
}
fun <T, R> StreamCodec<T>.map(from: T.() -> R, to: R.() -> T, copy: R.() -> R): StreamCodec<R> {
return StreamCodec.Mapping(this, from, to, copy)
}
fun <T : Any> StreamCodec<T>.optional(): StreamCodec<Optional<T>> = StreamCodec.Optional(this)
fun <T> StreamCodec<T>.koptional(): StreamCodec<KOptional<T>> = StreamCodec.KOptional(this)
fun <T> StreamCodec<T>.nullable(): StreamCodec<T?> = StreamCodec.Nullable(this)
val NullValueCodec = StreamCodec.Impl({ _ -> null }, { _, _ -> })
val BooleanValueCodec = StreamCodec.Impl(DataInputStream::readBoolean, 1L, DataOutputStream::writeBoolean)
val ByteValueCodec = StreamCodec.Impl(DataInputStream::readByte, 1L, { s, v -> s.writeByte(v.toInt()) })
val ShortValueCodec = StreamCodec.Impl(DataInputStream::readShort, 2L, { s, v -> s.writeShort(v.toInt()) })
val CharValueCodec = StreamCodec.Impl(DataInputStream::readChar, 2L, { s, v -> s.writeShort(v.code) })
val IntValueCodec = StreamCodec.Impl(DataInputStream::readInt, 4L, DataOutputStream::writeInt)
val LongValueCodec = StreamCodec.Impl(DataInputStream::readLong, 8L, DataOutputStream::writeLong)
val FloatValueCodec = StreamCodec.Impl(DataInputStream::readFloat, 4L, DataOutputStream::writeFloat)
val DoubleValueCodec = StreamCodec.Impl(DataInputStream::readDouble, 8L, DataOutputStream::writeDouble)
val BigDecimalValueCodec = StreamCodec.Impl(DataInputStream::readBigDecimal, DataOutputStream::writeBigDecimal)
val UUIDValueCodec = StreamCodec.Impl({ s -> UUID(s.readLong(), s.readLong()) }, { s, v -> s.writeLong(v.mostSignificantBits); s.writeLong(v.leastSignificantBits) })
val VarIntValueCodec = StreamCodec.Impl(DataInputStream::readSignedVarInt, DataOutputStream::writeSignedVarInt)
val VarLongValueCodec = StreamCodec.Impl(DataInputStream::readSignedVarLong, DataOutputStream::writeSignedVarLong)
val BinaryStringCodec = StreamCodec.Impl(DataInputStream::readBinaryString, DataOutputStream::writeBinaryString)
val UnsignedVarLongCodec = StreamCodec.Impl(DataInputStream::readVarLong, DataOutputStream::writeVarLong)
val UnsignedVarIntCodec = StreamCodec.Impl(DataInputStream::readVarInt, DataOutputStream::writeVarInt)
val ByteArrayCodec = StreamCodec.Impl(DataInputStream::readByteArray, DataOutputStream::writeByteArray)
val RGBCodec = StreamCodec.Impl(
{ s -> RGBAColor(s.readFloat(), s.readFloat(), s.readFloat()) },
{ s, v -> s.writeFloat(v.red); s.writeFloat(v.green); s.writeFloat(v.blue) })
val RGBACodec = StreamCodec.Impl(
{ s -> RGBAColor(s.readFloat(), s.readFloat(), s.readFloat(), s.readFloat()) },
{ s, v -> s.writeFloat(v.red); s.writeFloat(v.green); s.writeFloat(v.blue); s.writeFloat(v.alpha) })
val OptionalBooleanValueCodec = StreamCodec.Optional(BooleanValueCodec)
val OptionalByteValueCodec = StreamCodec.Optional(ByteValueCodec)
val OptionalShortValueCodec = StreamCodec.Optional(ShortValueCodec)
val OptionalCharValueCodec = StreamCodec.Optional(CharValueCodec)
val OptionalIntValueCodec = StreamCodec.Optional(IntValueCodec)
val OptionalLongValueCodec = StreamCodec.Optional(LongValueCodec)
val OptionalFloatValueCodec = StreamCodec.Optional(FloatValueCodec)
val OptionalDoubleValueCodec = StreamCodec.Optional(DoubleValueCodec)
val OptionalBigDecimalValueCodec = StreamCodec.Optional(BigDecimalValueCodec)
val OptionalUUIDValueCodec = StreamCodec.Optional(UUIDValueCodec)
val OptionalVarIntValueCodec = StreamCodec.Optional(VarIntValueCodec)
val OptionalVarLongValueCodec = StreamCodec.Optional(VarLongValueCodec)
val OptionalBinaryStringCodec = StreamCodec.Optional(BinaryStringCodec)
val OptionalRGBCodec = StreamCodec.Optional(RGBCodec)
val OptionalRGBACodec = StreamCodec.Optional(RGBACodec)
val KOptionalBooleanValueCodec = StreamCodec.KOptional(BooleanValueCodec)
val KOptionalByteValueCodec = StreamCodec.KOptional(ByteValueCodec)
val KOptionalShortValueCodec = StreamCodec.KOptional(ShortValueCodec)
val KOptionalCharValueCodec = StreamCodec.KOptional(CharValueCodec)
val KOptionalIntValueCodec = StreamCodec.KOptional(IntValueCodec)
val KOptionalLongValueCodec = StreamCodec.KOptional(LongValueCodec)
val KOptionalFloatValueCodec = StreamCodec.KOptional(FloatValueCodec)
val KOptionalDoubleValueCodec = StreamCodec.KOptional(DoubleValueCodec)
val KOptionalBigDecimalValueCodec = StreamCodec.KOptional(BigDecimalValueCodec)
val KOptionalUUIDValueCodec = StreamCodec.KOptional(UUIDValueCodec)
val KOptionalVarIntValueCodec = StreamCodec.KOptional(VarIntValueCodec)
val KOptionalVarLongValueCodec = StreamCodec.KOptional(VarLongValueCodec)
val KOptionalBinaryStringCodec = StreamCodec.KOptional(BinaryStringCodec)
val KOptionalRGBCodec = StreamCodec.KOptional(RGBCodec)
val KOptionalRGBACodec = StreamCodec.KOptional(RGBACodec)
fun <E : Enum<E>> Class<E>.codec() = StreamCodec.Enum(this)
fun <E : Enum<E>> KClass<E>.codec() = StreamCodec.Enum(this.java)