Major rework of IO
This commit is contained in:
parent
5921556254
commit
56679cff23
@ -1,8 +0,0 @@
|
|||||||
package hep.dataforge.data
|
|
||||||
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Block the thread and get data content
|
|
||||||
*/
|
|
||||||
fun <T : Any> Data<T>.get(): T = runBlocking { await() }
|
|
@ -1,8 +1,14 @@
|
|||||||
package hep.dataforge.data
|
package hep.dataforge.data
|
||||||
|
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import kotlin.reflect.full.isSuperclassOf
|
import kotlin.reflect.full.isSuperclassOf
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block the thread and get data content
|
||||||
|
*/
|
||||||
|
fun <T : Any> Data<T>.get(): T = runBlocking { await() }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check that node is compatible with given type meaning that each element could be cast to the type
|
* Check that node is compatible with given type meaning that each element could be cast to the type
|
||||||
*/
|
*/
|
@ -80,7 +80,7 @@ object BinaryMetaFormat : MetaFormat {
|
|||||||
writeValue(item.value)
|
writeValue(item.value)
|
||||||
}
|
}
|
||||||
is MetaItem.NodeItem -> {
|
is MetaItem.NodeItem -> {
|
||||||
writeObject(item.node)
|
writeThis(item.node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package hep.dataforge.io
|
package hep.dataforge.io
|
||||||
|
|
||||||
|
import hep.dataforge.context.Named
|
||||||
import hep.dataforge.io.EnvelopeFormat.Companion.ENVELOPE_FORMAT_TYPE
|
import hep.dataforge.io.EnvelopeFormat.Companion.ENVELOPE_FORMAT_TYPE
|
||||||
|
import hep.dataforge.io.IOPlugin.Companion.defaultMetaFormats
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
import hep.dataforge.meta.get
|
import hep.dataforge.meta.get
|
||||||
import hep.dataforge.meta.string
|
import hep.dataforge.meta.string
|
||||||
@ -49,15 +51,23 @@ 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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A partially read envelope with meta, but without data
|
||||||
|
*/
|
||||||
|
@ExperimentalUnsignedTypes
|
||||||
data class PartialEnvelope(val meta: Meta, val dataOffset: UInt, val dataSize: ULong?)
|
data class PartialEnvelope(val meta: Meta, val dataOffset: UInt, val dataSize: ULong?)
|
||||||
|
|
||||||
@Type(ENVELOPE_FORMAT_TYPE)
|
@Type(ENVELOPE_FORMAT_TYPE)
|
||||||
interface EnvelopeFormat : IOFormat<Envelope>{
|
interface EnvelopeFormat : IOFormat<Envelope>, Named {
|
||||||
fun readPartial(input: Input): PartialEnvelope
|
fun Input.readPartial(formats: Collection<MetaFormat> = defaultMetaFormats): PartialEnvelope
|
||||||
|
|
||||||
fun Output.writeEnvelope(envelope: Envelope, format: MetaFormat)
|
fun Input.readEnvelope(formats: Collection<MetaFormat> = defaultMetaFormats): Envelope
|
||||||
|
|
||||||
override fun Output.writeObject(obj: Envelope) = writeEnvelope(obj, JsonMetaFormat)
|
override fun Input.readThis(): Envelope = readEnvelope()
|
||||||
|
|
||||||
|
fun Output.writeEnvelope(envelope: Envelope, format: MetaFormat = JsonMetaFormat)
|
||||||
|
|
||||||
|
override fun Output.writeThis(obj: Envelope) = writeEnvelope(obj)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val ENVELOPE_FORMAT_TYPE = "envelopeFormat"
|
const val ENVELOPE_FORMAT_TYPE = "envelopeFormat"
|
||||||
|
@ -6,9 +6,9 @@ import kotlinx.io.core.*
|
|||||||
* And interface for serialization facilities
|
* And interface for serialization facilities
|
||||||
*/
|
*/
|
||||||
interface IOFormat<T : Any> {
|
interface IOFormat<T : Any> {
|
||||||
fun Output.writeObject(obj: T)
|
fun Output.writeThis(obj: T)
|
||||||
fun Input.readObject(): T
|
fun Input.readThis(): T
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T : Any> IOFormat<T>.writePacket(obj: T): ByteReadPacket = buildPacket { writeObject(obj) }
|
fun <T : Any> IOFormat<T>.writePacket(obj: T): ByteReadPacket = buildPacket { writeThis(obj) }
|
||||||
fun <T : Any> IOFormat<T>.writeBytes(obj: T): ByteArray = buildPacket { writeObject(obj) }.readBytes()
|
fun <T : Any> IOFormat<T>.writeBytes(obj: T): ByteArray = buildPacket { writeThis(obj) }.readBytes()
|
@ -6,7 +6,6 @@ import hep.dataforge.context.PluginTag
|
|||||||
import hep.dataforge.context.content
|
import hep.dataforge.context.content
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.names.asName
|
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
class IOPlugin(meta: Meta) : AbstractPlugin(meta) {
|
class IOPlugin(meta: Meta) : AbstractPlugin(meta) {
|
||||||
@ -21,16 +20,15 @@ class IOPlugin(meta: Meta) : AbstractPlugin(meta) {
|
|||||||
|
|
||||||
override fun provideTop(target: String): Map<Name, Any> {
|
override fun provideTop(target: String): Map<Name, Any> {
|
||||||
return when (target) {
|
return when (target) {
|
||||||
MetaFormat.META_FORMAT_TYPE -> internalMetaFormats
|
MetaFormat.META_FORMAT_TYPE -> defaultMetaFormats.toMap()
|
||||||
EnvelopeFormat.ENVELOPE_FORMAT_TYPE -> mapOf(
|
EnvelopeFormat.ENVELOPE_FORMAT_TYPE -> defaultEnvelopeFormats.toMap()
|
||||||
TaggedEnvelopeFormat.VERSION.asName() to TaggedEnvelopeFormat(metaFormats)
|
|
||||||
)
|
|
||||||
else -> super.provideTop(target)
|
else -> super.provideTop(target)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object : PluginFactory<IOPlugin> {
|
companion object : PluginFactory<IOPlugin> {
|
||||||
private val internalMetaFormats = listOf(JsonMetaFormat, BinaryMetaFormat).toMap()
|
val defaultMetaFormats: List<MetaFormat> = listOf(JsonMetaFormat, BinaryMetaFormat)
|
||||||
|
val defaultEnvelopeFormats = listOf(TaggedEnvelopeFormat)
|
||||||
|
|
||||||
override val tag: PluginTag = PluginTag("io", group = PluginTag.DATAFORGE_GROUP)
|
override val tag: PluginTag = PluginTag("io", group = PluginTag.DATAFORGE_GROUP)
|
||||||
override val type: KClass<out IOPlugin> = IOPlugin::class
|
override val type: KClass<out IOPlugin> = IOPlugin::class
|
||||||
|
@ -56,15 +56,16 @@ fun Value.toJson(descriptor: ValueDescriptor? = null): JsonElement {
|
|||||||
|
|
||||||
//Use theese methods to customize JSON key mapping
|
//Use theese methods to customize JSON key mapping
|
||||||
private fun NameToken.toJsonKey(descriptor: ItemDescriptor?) = toString()
|
private fun NameToken.toJsonKey(descriptor: ItemDescriptor?) = toString()
|
||||||
|
|
||||||
private fun NodeDescriptor?.getDescriptor(key: String) = this?.items?.get(key)
|
private fun NodeDescriptor?.getDescriptor(key: String) = this?.items?.get(key)
|
||||||
|
|
||||||
fun Meta.toJson(descriptor: NodeDescriptor? = null): JsonObject {
|
fun Meta.toJson(descriptor: NodeDescriptor? = null): JsonObject {
|
||||||
|
|
||||||
//TODO search for same name siblings and arrange them into arrays
|
//TODO search for same name siblings and arrange them into arrays
|
||||||
val map = this.items.entries.associate {(name,item)->
|
val map = this.items.entries.associate { (name, item) ->
|
||||||
val itemDescriptor = descriptor?.items?.get(name.body)
|
val itemDescriptor = descriptor?.items?.get(name.body)
|
||||||
val key = name.toJsonKey(itemDescriptor)
|
val key = name.toJsonKey(itemDescriptor)
|
||||||
val value = when (item) {
|
val value = when (item) {
|
||||||
is MetaItem.ValueItem -> {
|
is MetaItem.ValueItem -> {
|
||||||
item.value.toJson(itemDescriptor as? ValueDescriptor)
|
item.value.toJson(itemDescriptor as? ValueDescriptor)
|
||||||
}
|
}
|
||||||
|
@ -15,11 +15,11 @@ interface MetaFormat : IOFormat<Meta>, Named {
|
|||||||
override val name: String
|
override val name: String
|
||||||
val key: Short
|
val key: Short
|
||||||
|
|
||||||
override fun Output.writeObject(obj: Meta) {
|
override fun Output.writeThis(obj: Meta) {
|
||||||
writeMeta(obj, null)
|
writeMeta(obj, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun Input.readObject(): Meta = readMeta(null)
|
override fun Input.readThis(): Meta = readMeta(null)
|
||||||
|
|
||||||
fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor? = null)
|
fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor? = null)
|
||||||
fun Input.readMeta(descriptor: NodeDescriptor? = null): Meta
|
fun Input.readMeta(descriptor: NodeDescriptor? = null): Meta
|
||||||
@ -30,20 +30,20 @@ interface MetaFormat : IOFormat<Meta>, Named {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun Meta.toString(format: MetaFormat = JsonMetaFormat): String = buildPacket {
|
fun Meta.toString(format: MetaFormat = JsonMetaFormat): String = buildPacket {
|
||||||
format.run { writeObject(this@toString) }
|
format.run { writeThis(this@toString) }
|
||||||
}.readText()
|
}.readText()
|
||||||
|
|
||||||
fun Meta.toBytes(format: MetaFormat = JsonMetaFormat): ByteReadPacket = buildPacket {
|
fun Meta.toBytes(format: MetaFormat = JsonMetaFormat): ByteReadPacket = buildPacket {
|
||||||
format.run { writeObject(this@toBytes) }
|
format.run { writeThis(this@toBytes) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun MetaFormat.parse(str: String): Meta {
|
fun MetaFormat.parse(str: String): Meta {
|
||||||
return ByteReadPacket(str.toByteArray()).readObject()
|
return ByteReadPacket(str.toByteArray()).readThis()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun MetaFormat.fromBytes(packet: ByteReadPacket): Meta {
|
fun MetaFormat.fromBytes(packet: ByteReadPacket): Meta {
|
||||||
return packet.readObject()
|
return packet.readThis()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,21 +2,75 @@ package hep.dataforge.io
|
|||||||
|
|
||||||
import kotlinx.io.core.*
|
import kotlinx.io.core.*
|
||||||
|
|
||||||
class TaggedEnvelopeFormat(val metaFormats: Collection<MetaFormat>) : EnvelopeFormat {
|
|
||||||
|
@ExperimentalUnsignedTypes
|
||||||
|
object TaggedEnvelopeFormat : EnvelopeFormat {
|
||||||
|
const val VERSION = "DF03"
|
||||||
|
private const val START_SEQUENCE = "#~"
|
||||||
|
private const val END_SEQUENCE = "~#\r\n"
|
||||||
|
private const val TAG_SIZE = 26u
|
||||||
|
|
||||||
|
override val name: String get() = VERSION
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
override fun Output.writeEnvelope(envelope: Envelope, format: MetaFormat) {
|
override fun Output.writeEnvelope(envelope: Envelope, format: MetaFormat) {
|
||||||
write(this, envelope, format)
|
val metaBytes = format.writeBytes(envelope.meta)
|
||||||
|
val tag = Tag(format.key, metaBytes.size.toUInt(), envelope.data?.size ?: 0.toULong())
|
||||||
|
writePacket(tag.toBytes())
|
||||||
|
writeFully(metaBytes)
|
||||||
|
envelope.data?.read { copyTo(this@writeEnvelope) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read an envelope from input into memory
|
* Read an envelope from input into memory
|
||||||
*
|
*
|
||||||
* @param input an input to read from
|
* @param input an input to read from
|
||||||
* @param metaFormats a collection of meta formats to resolve
|
* @param formats a collection of meta formats to resolve
|
||||||
*/
|
*/
|
||||||
override fun Input.readObject(): Envelope = read(this, metaFormats)
|
override fun Input.readEnvelope(formats: Collection<MetaFormat>): Envelope {
|
||||||
|
val tag = readTag()
|
||||||
|
|
||||||
override fun readPartial(input: Input): PartialEnvelope = Companion.readPartial(input, metaFormats)
|
val metaFormat = formats.find { it.key == tag.metaFormatKey }
|
||||||
|
?: error("Meta format with key ${tag.metaFormatKey} not found")
|
||||||
|
|
||||||
|
val metaPacket = ByteReadPacket(readBytes(tag.metaSize.toInt()))
|
||||||
|
val meta = metaFormat.run { metaPacket.readThis() }
|
||||||
|
|
||||||
|
val dataBytes = readBytes(tag.dataSize.toInt())
|
||||||
|
|
||||||
|
return SimpleEnvelope(meta, ArrayBinary(dataBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun Input.readPartial(formats: Collection<MetaFormat>): PartialEnvelope {
|
||||||
|
val tag = readTag()
|
||||||
|
|
||||||
|
val metaFormat = formats.find { it.key == tag.metaFormatKey }
|
||||||
|
?: error("Meta format with key ${tag.metaFormatKey} not found")
|
||||||
|
|
||||||
|
val metaPacket = ByteReadPacket(readBytes(tag.metaSize.toInt()))
|
||||||
|
val meta = metaFormat.run { metaPacket.readThis() }
|
||||||
|
|
||||||
|
return PartialEnvelope(meta, TAG_SIZE + tag.metaSize, tag.dataSize)
|
||||||
|
}
|
||||||
|
|
||||||
private data class Tag(
|
private data class Tag(
|
||||||
val metaFormatKey: Short,
|
val metaFormatKey: Short,
|
||||||
@ -24,64 +78,4 @@ class TaggedEnvelopeFormat(val metaFormats: Collection<MetaFormat>) : EnvelopeFo
|
|||||||
val dataSize: ULong
|
val dataSize: ULong
|
||||||
)
|
)
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val VERSION = "DF03"
|
|
||||||
private const val START_SEQUENCE = "#~"
|
|
||||||
private const val END_SEQUENCE = "~#\r\n"
|
|
||||||
private const val TAG_SIZE = 26u
|
|
||||||
|
|
||||||
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 readPartial(input: Input, metaFormats: Collection<MetaFormat>): PartialEnvelope {
|
|
||||||
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() }
|
|
||||||
|
|
||||||
return PartialEnvelope(meta, TAG_SIZE + tag.metaSize, tag.dataSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun write(out: Output, envelope: Envelope, metaFormat: MetaFormat) {
|
|
||||||
val metaBytes = metaFormat.writeBytes(envelope.meta)
|
|
||||||
val tag = Tag(metaFormat.key, metaBytes.size.toUInt(), envelope.data?.size ?: 0.toULong())
|
|
||||||
out.writePacket(tag.toBytes())
|
|
||||||
out.writeFully(metaBytes)
|
|
||||||
envelope.data?.read { copyTo(out) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -7,6 +7,7 @@ import java.nio.file.Files
|
|||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.nio.file.StandardOpenOption
|
import java.nio.file.StandardOpenOption
|
||||||
|
|
||||||
|
@ExperimentalUnsignedTypes
|
||||||
class FileBinary(val path: Path, private val offset: UInt = 0u, size: ULong? = null) : RandomAccessBinary {
|
class FileBinary(val path: Path, private val offset: UInt = 0u, size: ULong? = null) : RandomAccessBinary {
|
||||||
|
|
||||||
override val size: ULong = size ?: (Files.size(path).toULong() - offset).toULong()
|
override val size: ULong = size ?: (Files.size(path).toULong() - offset).toULong()
|
||||||
|
@ -2,6 +2,7 @@ package hep.dataforge.io
|
|||||||
|
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
import kotlinx.io.nio.asInput
|
import kotlinx.io.nio.asInput
|
||||||
|
import kotlinx.io.nio.asOutput
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.nio.file.StandardOpenOption
|
import java.nio.file.StandardOpenOption
|
||||||
@ -13,7 +14,7 @@ class FileEnvelope internal constructor(val path: Path, val format: EnvelopeForm
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
val input = Files.newByteChannel(path, StandardOpenOption.READ).asInput()
|
val input = Files.newByteChannel(path, StandardOpenOption.READ).asInput()
|
||||||
partialEnvelope = format.readPartial(input)
|
partialEnvelope = format.run { input.readPartial() }
|
||||||
}
|
}
|
||||||
|
|
||||||
override val meta: Meta get() = partialEnvelope.meta
|
override val meta: Meta get() = partialEnvelope.meta
|
||||||
@ -21,4 +22,21 @@ class FileEnvelope internal constructor(val path: Path, val format: EnvelopeForm
|
|||||||
override val data: Binary? = FileBinary(path, partialEnvelope.dataOffset, partialEnvelope.dataSize)
|
override val data: Binary? = FileBinary(path, partialEnvelope.dataOffset, partialEnvelope.dataSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Path.readEnvelope(format: EnvelopeFormat) = FileEnvelope(this,format)
|
fun Path.readEnvelope(format: EnvelopeFormat) = FileEnvelope(this, format)
|
||||||
|
|
||||||
|
fun Path.writeEnvelope(
|
||||||
|
envelope: Envelope,
|
||||||
|
format: EnvelopeFormat = TaggedEnvelopeFormat,
|
||||||
|
metaFormat: MetaFormat = JsonMetaFormat
|
||||||
|
) {
|
||||||
|
val output = Files.newByteChannel(
|
||||||
|
this,
|
||||||
|
StandardOpenOption.WRITE,
|
||||||
|
StandardOpenOption.CREATE,
|
||||||
|
StandardOpenOption.TRUNCATE_EXISTING
|
||||||
|
).asOutput()
|
||||||
|
|
||||||
|
with(format) {
|
||||||
|
output.writeEnvelope(envelope, metaFormat)
|
||||||
|
}
|
||||||
|
}
|
@ -62,7 +62,7 @@ suspend fun <T : Any> Context.readData(
|
|||||||
format.run {
|
format.run {
|
||||||
Files.newByteChannel(path, StandardOpenOption.READ)
|
Files.newByteChannel(path, StandardOpenOption.READ)
|
||||||
.asInput()
|
.asInput()
|
||||||
.readObject()
|
.readThis()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user