Envelope IO
This commit is contained in:
parent
54c7b55bc4
commit
7c38cadddd
@ -0,0 +1,60 @@
|
|||||||
|
package hep.dataforge.io
|
||||||
|
|
||||||
|
import kotlinx.io.core.ByteReadPacket
|
||||||
|
import kotlinx.io.core.Input
|
||||||
|
import kotlinx.io.core.readBytes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A source of binary data
|
||||||
|
*/
|
||||||
|
interface Binary {
|
||||||
|
/**
|
||||||
|
* The size of binary in bytes
|
||||||
|
*/
|
||||||
|
val size: ULong
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read continuous [Input] from this binary stating from the beginning.
|
||||||
|
* The input is automatically closed on scope close.
|
||||||
|
* Some implementation may forbid this to be called twice. In this case second call will throw an exception.
|
||||||
|
*/
|
||||||
|
fun <R> read(block: Input.() -> R): R
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [Binary] with addition random access functionality. It by default allows multiple [read] operations.
|
||||||
|
*/
|
||||||
|
interface RandomAccessBinary : Binary {
|
||||||
|
/**
|
||||||
|
* Read at most [size] of bytes starting at [from] offset from the beginning of the binary.
|
||||||
|
* This method could be called multiple times simultaneously.
|
||||||
|
*/
|
||||||
|
fun <R> read(from: UInt, size: UInt = UInt.MAX_VALUE, block: Input.() -> R): R
|
||||||
|
|
||||||
|
override fun <R> read(block: Input.() -> R): R = read(0.toUInt(), UInt.MAX_VALUE, block)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Binary.readAll(): ByteReadPacket = read {
|
||||||
|
ByteReadPacket(this.readBytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun RandomAccessBinary.readPacket(from: UInt, size: UInt): ByteReadPacket = read(from, size) {
|
||||||
|
ByteReadPacket(this.readBytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
object EmptyBinary : RandomAccessBinary {
|
||||||
|
|
||||||
|
override val size: ULong = 0.toULong()
|
||||||
|
|
||||||
|
override fun <R> read(from: UInt, size: UInt, block: Input.() -> R): R {
|
||||||
|
error("The binary is empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ArrayBinary(val array: ByteArray) : RandomAccessBinary {
|
||||||
|
override val size: ULong = array.size.toULong()
|
||||||
|
|
||||||
|
override fun <R> read(from: UInt, size: UInt, block: Input.() -> R): R {
|
||||||
|
return ByteReadPacket(array, from.toInt(), size.toInt()).block()
|
||||||
|
}
|
||||||
|
}
|
@ -8,12 +8,11 @@ import kotlinx.io.core.readText
|
|||||||
import kotlinx.io.core.writeText
|
import kotlinx.io.core.writeText
|
||||||
|
|
||||||
object BinaryMetaFormat : MetaFormat {
|
object BinaryMetaFormat : MetaFormat {
|
||||||
override fun write(obj: Meta, out: Output) {
|
override val name: String = "bin"
|
||||||
out.writeMeta(obj)
|
override val key: Short = 0x4249//BI
|
||||||
}
|
|
||||||
|
|
||||||
override fun read(input: Input): Meta {
|
override fun Input.readObject(): Meta {
|
||||||
return (input.readMetaItem() as MetaItem.NodeItem).node
|
return (readMetaItem() as MetaItem.NodeItem).node
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Output.writeChar(char: Char) = writeByte(char.toByte())
|
private fun Output.writeChar(char: Char) = writeByte(char.toByte())
|
||||||
@ -70,7 +69,7 @@ object BinaryMetaFormat : MetaFormat {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Output.writeMeta(meta: Meta) {
|
override fun Output.writeObject(meta: Meta) {
|
||||||
writeChar('M')
|
writeChar('M')
|
||||||
writeInt(meta.items.size)
|
writeInt(meta.items.size)
|
||||||
meta.items.forEach { (key, item) ->
|
meta.items.forEach { (key, item) ->
|
||||||
@ -80,7 +79,7 @@ object BinaryMetaFormat : MetaFormat {
|
|||||||
writeValue(item.value)
|
writeValue(item.value)
|
||||||
}
|
}
|
||||||
is MetaItem.NodeItem -> {
|
is MetaItem.NodeItem -> {
|
||||||
writeMeta(item.node)
|
writeObject(item.node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -122,11 +121,4 @@ object BinaryMetaFormat : MetaFormat {
|
|||||||
else -> error("Unknown serialization key character: $keyChar")
|
else -> error("Unknown serialization key character: $keyChar")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
class BinaryMetaFormatFactory : MetaFormatFactory {
|
|
||||||
override val name: String = "bin"
|
|
||||||
override val key: Short = 0x4249//BI
|
|
||||||
|
|
||||||
override fun build(): MetaFormat = BinaryMetaFormat
|
|
||||||
}
|
}
|
@ -7,7 +7,7 @@ import kotlinx.io.core.Input
|
|||||||
|
|
||||||
interface Envelope {
|
interface Envelope {
|
||||||
val meta: Meta
|
val meta: Meta
|
||||||
val data: Input?
|
val data: Binary?
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
@ -23,11 +23,7 @@ interface Envelope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SimpleEnvelope(override val meta: Meta, val dataProvider: () -> Input?) : Envelope{
|
class SimpleEnvelope(override val meta: Meta, override val data: Binary?) : Envelope
|
||||||
override val data: Input?
|
|
||||||
get() = dataProvider()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The purpose of the envelope
|
* The purpose of the envelope
|
||||||
@ -50,3 +46,4 @@ val Envelope.dataType: String? get() = meta[Envelope.ENVELOPE_DATA_TYPE_KEY].str
|
|||||||
*/
|
*/
|
||||||
val Envelope.description: String? get() = meta[Envelope.ENVELOPE_DESCRIPTION_KEY].string
|
val Envelope.description: String? get() = meta[Envelope.ENVELOPE_DESCRIPTION_KEY].string
|
||||||
|
|
||||||
|
typealias EnvelopeFormat = IOFormat<Envelope>
|
@ -1,10 +1,14 @@
|
|||||||
package hep.dataforge.io
|
package hep.dataforge.io
|
||||||
|
|
||||||
import kotlinx.io.core.Input
|
import kotlinx.io.core.*
|
||||||
import kotlinx.io.core.Output
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* And interface for serialization facilities
|
||||||
|
*/
|
||||||
interface IOFormat<T : Any> {
|
interface IOFormat<T : Any> {
|
||||||
fun write(obj: T, out: Output)
|
fun Output.writeObject(obj: T)
|
||||||
fun read(input: Input): T
|
fun Input.readObject(): T
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T : Any> IOFormat<T>.writePacket(obj: T): ByteReadPacket = buildPacket { writeObject(obj) }
|
||||||
|
fun <T : Any> IOFormat<T>.writeBytes(obj: T): ByteArray = buildPacket { writeObject(obj) }.readBytes()
|
@ -14,13 +14,16 @@ import kotlinx.serialization.json.*
|
|||||||
|
|
||||||
object JsonMetaFormat : MetaFormat {
|
object JsonMetaFormat : MetaFormat {
|
||||||
|
|
||||||
override fun write(obj: Meta, out: Output) {
|
override val name: String = "json"
|
||||||
|
override val key: Short = 0x4a53//"JS"
|
||||||
|
|
||||||
|
override fun Output.writeObject(obj: Meta) {
|
||||||
val str = obj.toJson().toString()
|
val str = obj.toJson().toString()
|
||||||
out.writeText(str)
|
writeText(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun read(input: Input): Meta {
|
override fun Input.readObject(): Meta {
|
||||||
val str = input.readText()
|
val str = readText()
|
||||||
val json = Json.plain.parseJson(str)
|
val json = Json.plain.parseJson(str)
|
||||||
|
|
||||||
if (json is JsonObject) {
|
if (json is JsonObject) {
|
||||||
@ -97,11 +100,4 @@ class JsonMeta(val json: JsonObject) : Meta {
|
|||||||
json.forEach { (key, value) -> map[key] = value }
|
json.forEach { (key, value) -> map[key] = value }
|
||||||
map.mapKeys { it.key.toName().first()!! }
|
map.mapKeys { it.key.toName().first()!! }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
class JsonMetaFormatFactory : MetaFormatFactory {
|
|
||||||
override val name: String = "json"
|
|
||||||
override val key: Short = 0x4a53//"JS"
|
|
||||||
|
|
||||||
override fun build() = JsonMetaFormat
|
|
||||||
}
|
}
|
@ -1,33 +1,26 @@
|
|||||||
package hep.dataforge.io
|
package hep.dataforge.io
|
||||||
|
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
import kotlinx.io.core.BytePacketBuilder
|
|
||||||
import kotlinx.io.core.ByteReadPacket
|
import kotlinx.io.core.ByteReadPacket
|
||||||
|
import kotlinx.io.core.buildPacket
|
||||||
import kotlinx.io.core.toByteArray
|
import kotlinx.io.core.toByteArray
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A format for meta serialization
|
* A format for meta serialization
|
||||||
*/
|
*/
|
||||||
interface MetaFormat: IOFormat<Meta>
|
interface MetaFormat : IOFormat<Meta> {
|
||||||
|
|
||||||
/**
|
|
||||||
* ServiceLoader compatible factory
|
|
||||||
*/
|
|
||||||
interface MetaFormatFactory {
|
|
||||||
val name: String
|
val name: String
|
||||||
val key: Short
|
val key: Short
|
||||||
|
|
||||||
fun build(): MetaFormat
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Meta.asString(format: MetaFormat = JsonMetaFormat): String {
|
fun Meta.asString(format: MetaFormat = JsonMetaFormat): String {
|
||||||
val builder = BytePacketBuilder()
|
return buildPacket {
|
||||||
format.write(this, builder)
|
format.run { writeObject(this@asString) }
|
||||||
return builder.build().readText()
|
}.readText()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun MetaFormat.parse(str: String): Meta {
|
fun MetaFormat.parse(str: String): Meta {
|
||||||
return read(ByteReadPacket(str.toByteArray()))
|
return ByteReadPacket(str.toByteArray()).readObject()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -0,0 +1,80 @@
|
|||||||
|
package hep.dataforge.io
|
||||||
|
|
||||||
|
import kotlinx.io.core.*
|
||||||
|
|
||||||
|
class TaggedEnvelopeFormat(
|
||||||
|
val metaFormats: Collection<MetaFormat>,
|
||||||
|
val outputMetaFormat: MetaFormat = metaFormats.first()
|
||||||
|
) : EnvelopeFormat {
|
||||||
|
|
||||||
|
override fun Output.writeObject(obj: Envelope) {
|
||||||
|
write(obj, this, outputMetaFormat)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read an envelope from input into memory
|
||||||
|
*
|
||||||
|
* @param input an input to read from
|
||||||
|
* @param metaFormats a collection of meta formats to resolve
|
||||||
|
*/
|
||||||
|
override fun Input.readObject(): Envelope = read(this, metaFormats)
|
||||||
|
|
||||||
|
|
||||||
|
private data class Tag(
|
||||||
|
val metaFormatKey: Short,
|
||||||
|
val metaSize: UInt,
|
||||||
|
val dataSize: ULong
|
||||||
|
)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val VERSION = "DF03"
|
||||||
|
private const val START_SEQUENCE = "#~"
|
||||||
|
private const val END_SEQUENCE = "~#\r\n"
|
||||||
|
|
||||||
|
private fun Tag.toBytes(): ByteReadPacket = buildPacket(24) {
|
||||||
|
writeText(START_SEQUENCE)
|
||||||
|
writeText(VERSION)
|
||||||
|
writeShort(metaFormatKey)
|
||||||
|
writeUInt(metaSize)
|
||||||
|
writeULong(dataSize)
|
||||||
|
writeText(END_SEQUENCE)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Input.readTag(): Tag {
|
||||||
|
val start = readTextExactBytes(2)
|
||||||
|
if (start != START_SEQUENCE) error("The input is not an envelope")
|
||||||
|
val version = readTextExactBytes(4)
|
||||||
|
if (version != VERSION) error("Wrong version of DataForge: expected $VERSION but found $version")
|
||||||
|
val metaFormatKey = readShort()
|
||||||
|
val metaLength = readUInt()
|
||||||
|
val dataLength = readULong()
|
||||||
|
return Tag(metaFormatKey, metaLength, dataLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun read(input: Input, metaFormats: Collection<MetaFormat>): Envelope {
|
||||||
|
val tag = input.readTag()
|
||||||
|
|
||||||
|
val metaFormat = metaFormats.find { it.key == tag.metaFormatKey }
|
||||||
|
?: error("Meta format with key ${tag.metaFormatKey} not found")
|
||||||
|
|
||||||
|
val metaPacket = ByteReadPacket(input.readBytes(tag.metaSize.toInt()))
|
||||||
|
val meta = metaFormat.run { metaPacket.readObject() }
|
||||||
|
|
||||||
|
val dataBytes = input.readBytes(tag.dataSize.toInt())
|
||||||
|
|
||||||
|
return SimpleEnvelope(meta, ArrayBinary(dataBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun write(obj: Envelope, out: Output, metaFormat: MetaFormat) {
|
||||||
|
val metaBytes = metaFormat.writeBytes(obj.meta)
|
||||||
|
val tag = Tag(metaFormat.key, metaBytes.size.toUInt(), obj.data?.size ?: 0.toULong())
|
||||||
|
out.writePacket(tag.toBytes())
|
||||||
|
out.writeFully(metaBytes)
|
||||||
|
obj.data?.read {
|
||||||
|
while (!endOfInput){
|
||||||
|
out.writeByte(readByte())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package hep.dataforge.io
|
||||||
|
|
||||||
|
import kotlinx.io.core.ByteReadPacket
|
||||||
|
import kotlinx.io.core.Input
|
||||||
|
import java.nio.channels.FileChannel
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.StandardOpenOption
|
||||||
|
|
||||||
|
class FileBinary(val path: Path, private val offset: Int = 0) : RandomAccessBinary {
|
||||||
|
override fun <R> read(from: Long, size: Long, block: Input.() -> R): R {
|
||||||
|
FileChannel.open(path, StandardOpenOption.READ).use {
|
||||||
|
val buffer = it.map(FileChannel.MapMode.READ_ONLY, from + offset, size)
|
||||||
|
return ByteReadPacket(buffer).block()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -95,12 +95,13 @@ abstract class MutableMetaNode<M : MutableMetaNode<M>> : AbstractMetaNode<M>(),
|
|||||||
fun <M : MutableMeta<M>> MutableMeta<M>.remove(name: Name) = set(name, null)
|
fun <M : MutableMeta<M>> MutableMeta<M>.remove(name: Name) = set(name, null)
|
||||||
fun <M : MutableMeta<M>> MutableMeta<M>.remove(name: String) = remove(name.toName())
|
fun <M : MutableMeta<M>> MutableMeta<M>.remove(name: String) = remove(name.toName())
|
||||||
|
|
||||||
fun <M : MutableMeta<M>> MutableMeta<M>.setValue(name: Name, value: Value) = set(name, MetaItem.ValueItem(value))
|
fun <M : MutableMeta<M>> MutableMeta<M>.setValue(name: Name, value: Value) =
|
||||||
//fun <M : MutableMeta<M>> MutableMeta<M>.setItem(name: String, item: MetaItem<M>) = set(name.toName(), item)
|
set(name, MetaItem.ValueItem(value))
|
||||||
fun <M : MutableMeta<M>> MutableMeta<M>.setValue(name: String, value: Value) =
|
fun <M : MutableMeta<M>> MutableMeta<M>.setValue(name: String, value: Value) =
|
||||||
set(name.toName(), MetaItem.ValueItem(value))
|
set(name.toName(), MetaItem.ValueItem(value))
|
||||||
|
|
||||||
//fun <M : MutableMeta<M>> MutableMeta<M>.setItem(token: NameToken, item: MetaItem<M>?) = set(token.asName(), item)
|
//fun <M : MutableMeta<M>> MutableMeta<M>.setItem(token: NameToken, item: MetaItem<M>?) = set(token.asName(), item)
|
||||||
|
//fun <M : MutableMeta<M>> MutableMeta<M>.setItem(name: String, item: MetaItem<M>) = set(name.toName(), item)
|
||||||
|
|
||||||
fun <M : MutableMetaNode<M>> MutableMetaNode<M>.setItem(name: Name, item: MetaItem<*>) {
|
fun <M : MutableMetaNode<M>> MutableMetaNode<M>.setItem(name: Name, item: MetaItem<*>) {
|
||||||
when (item) {
|
when (item) {
|
||||||
|
Loading…
Reference in New Issue
Block a user