Envelope client-sever EAP

This commit is contained in:
Alexander Nozik 2019-09-09 16:15:35 +03:00
parent 10b8385324
commit c44e004495
14 changed files with 291 additions and 176 deletions

View File

@ -15,11 +15,18 @@ kotlin {
commonMain{ commonMain{
dependencies { dependencies {
api(project(":dataforge-context")) api(project(":dataforge-context"))
api("org.jetbrains.kotlinx:kotlinx-io:0.1.14")
}
}
jvmMain{
dependencies {
api("org.jetbrains.kotlinx:kotlinx-io-jvm:0.1.14")
} }
} }
jsMain{ jsMain{
dependencies{ dependencies{
api(npm("text-encoding")) //api(npm("text-encoding"))
api("org.jetbrains.kotlinx:kotlinx-io-js:0.1.14")
} }
} }
} }

View File

@ -39,8 +39,8 @@ interface RandomAccessBinary : Binary {
override fun <R> read(block: Input.() -> R): R = read(0.toUInt(), UInt.MAX_VALUE, block) override fun <R> read(block: Input.() -> R): R = read(0.toUInt(), UInt.MAX_VALUE, block)
} }
fun Binary.readAll(): ByteReadPacket = read { fun Binary.toBytes(): ByteArray = read {
ByteReadPacket(this.readBytes()) this.readBytes()
} }
@ExperimentalUnsignedTypes @ExperimentalUnsignedTypes
@ -56,10 +56,11 @@ object EmptyBinary : RandomAccessBinary {
override fun <R> read(from: UInt, size: UInt, block: Input.() -> R): R { override fun <R> read(from: UInt, size: UInt, block: Input.() -> R): R {
error("The binary is empty") error("The binary is empty")
} }
} }
@ExperimentalUnsignedTypes @ExperimentalUnsignedTypes
class ArrayBinary(val array: ByteArray) : RandomAccessBinary { inline class ArrayBinary(val array: ByteArray) : RandomAccessBinary {
override val size: ULong get() = array.size.toULong() override val size: ULong get() = array.size.toULong()
override fun <R> read(from: UInt, size: UInt, block: Input.() -> R): R { override fun <R> read(from: UInt, size: UInt, block: Input.() -> R): R {
@ -68,16 +69,6 @@ class ArrayBinary(val array: ByteArray) : RandomAccessBinary {
} }
} }
class PacketBinary(val packet: ByteReadPacket) : Binary {
override val size: ULong
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
override fun <R> read(block: Input.() -> R): R {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
/** /**
* Read given binary as object using given format * Read given binary as object using given format
*/ */
@ -87,13 +78,9 @@ fun <T : Any> Binary.readWith(format: IOFormat<T>): T = format.run {
} }
} }
/** fun <T : Any> IOFormat<T>.writeBinary(obj: T): Binary {
* Write this object to a binary
* TODO make a lazy binary that does not use intermediate array
*/
fun <T : Any> T.writeWith(format: IOFormat<T>): Binary = format.run {
val packet = buildPacket { val packet = buildPacket {
writeThis(this@writeWith) writeThis(obj)
} }
return@run ArrayBinary(packet.readBytes()) return ArrayBinary(packet.readBytes())
} }

View File

@ -1,6 +1,8 @@
package hep.dataforge.io package hep.dataforge.io
import hep.dataforge.meta.* import hep.dataforge.meta.*
import hep.dataforge.names.asName
import hep.dataforge.names.plus
import kotlinx.io.core.Output import kotlinx.io.core.Output
import kotlinx.io.core.buildPacket import kotlinx.io.core.buildPacket
import kotlinx.io.core.readBytes import kotlinx.io.core.readBytes
@ -14,10 +16,11 @@ interface Envelope {
/** /**
* meta keys * meta keys
*/ */
const val ENVELOPE_NODE = "@envelope" val ENVELOPE_NODE_KEY = "@envelope".asName()
const val ENVELOPE_TYPE_KEY = "$ENVELOPE_NODE.type" val ENVELOPE_TYPE_KEY = ENVELOPE_NODE_KEY + "type"
const val ENVELOPE_DATA_TYPE_KEY = "$ENVELOPE_NODE.dataType" val ENVELOPE_DATA_TYPE_KEY = ENVELOPE_NODE_KEY + "dataType"
const val ENVELOPE_DESCRIPTION_KEY = "$ENVELOPE_NODE.description" val ENVELOPE_DATA_ID_KEY = ENVELOPE_NODE_KEY + "dataID"
val ENVELOPE_DESCRIPTION_KEY = ENVELOPE_NODE_KEY + "description"
//const val ENVELOPE_TIME_KEY = "@envelope.time" //const val ENVELOPE_TIME_KEY = "@envelope.time"
/** /**
@ -32,24 +35,35 @@ class SimpleEnvelope(override val meta: Meta, override val data: Binary?) : Enve
/** /**
* The purpose of the envelope * The purpose of the envelope
* *
* @return
*/ */
val Envelope.type: String? get() = meta[Envelope.ENVELOPE_TYPE_KEY].string val Envelope.type: String? get() = meta[Envelope.ENVELOPE_TYPE_KEY].string
/** /**
* The type of data encoding * The type of data encoding
* *
* @return
*/ */
val Envelope.dataType: String? get() = meta[Envelope.ENVELOPE_DATA_TYPE_KEY].string val Envelope.dataType: String? get() = meta[Envelope.ENVELOPE_DATA_TYPE_KEY].string
/** /**
* Textual user friendly description * Textual user friendly description
* *
* @return
*/ */
val Envelope.description: String? get() = meta[Envelope.ENVELOPE_DESCRIPTION_KEY].string val Envelope.description: String? get() = meta[Envelope.ENVELOPE_DESCRIPTION_KEY].string
/**
* An optional unique identifier that is used for data comparison. Data without identifier could not be compared to another data.
*/
val Envelope.dataID: String? get() = meta[Envelope.ENVELOPE_DATA_ID_KEY].string
fun Envelope.metaEquals(other: Envelope): Boolean = this.meta == other.meta
fun Envelope.dataEquals(other: Envelope): Boolean = this.dataID != null && this.dataID == other.dataID
fun Envelope.contentEquals(other: Envelope): Boolean {
return (this === other || (metaEquals(other) && dataEquals(other)))
}
/** /**
* An envelope, which wraps existing envelope and adds one or several additional layers of meta * An envelope, which wraps existing envelope and adds one or several additional layers of meta
*/ */
@ -59,7 +73,7 @@ class ProxyEnvelope(val source: Envelope, vararg meta: Meta) : Envelope {
} }
/** /**
* Add few meta layers to existing envelope * Add few meta layers to existing envelope (on top of existing meta)
*/ */
fun Envelope.withMetaLayers(vararg layers: Meta): Envelope { fun Envelope.withMetaLayers(vararg layers: Meta): Envelope {
return when { return when {

View File

@ -36,6 +36,7 @@ interface IOFormat<T : Any> : Named {
fun <T : Any> IOFormat<T>.writePacket(obj: T): ByteReadPacket = buildPacket { writeThis(obj) } fun <T : Any> IOFormat<T>.writePacket(obj: T): ByteReadPacket = buildPacket { writeThis(obj) }
fun <T : Any> IOFormat<T>.writeBytes(obj: T): ByteArray = buildPacket { writeThis(obj) }.readBytes() fun <T : Any> IOFormat<T>.writeBytes(obj: T): ByteArray = buildPacket { writeThis(obj) }.readBytes()
fun <T: Any> IOFormat<T>.readBytes(array: ByteArray): T = ByteReadPacket(array).readThis()
object DoubleIOFormat : IOFormat<Double> { object DoubleIOFormat : IOFormat<Double> {

View File

@ -10,7 +10,7 @@ object TaggedEnvelopeFormat : EnvelopeFormat {
const val VERSION = "DF03" const val VERSION = "DF03"
private const val START_SEQUENCE = "#~" private const val START_SEQUENCE = "#~"
private const val END_SEQUENCE = "~#\r\n" private const val END_SEQUENCE = "~#\r\n"
private const val TAG_SIZE = 26u private const val TAG_SIZE = 24u
override val name: Name = super.name + VERSION override val name: Name = super.name + VERSION
@ -31,6 +31,8 @@ object TaggedEnvelopeFormat : EnvelopeFormat {
val metaFormatKey = readShort() val metaFormatKey = readShort()
val metaLength = readUInt() val metaLength = readUInt()
val dataLength = readULong() val dataLength = readULong()
val end = readTextExactBytes(4)
if (end != END_SEQUENCE) error("The input is not an envelope")
return Tag(metaFormatKey, metaLength, dataLength) return Tag(metaFormatKey, metaLength, dataLength)
} }
@ -55,10 +57,9 @@ object TaggedEnvelopeFormat : EnvelopeFormat {
?: error("Meta format with key ${tag.metaFormatKey} not found") ?: error("Meta format with key ${tag.metaFormatKey} not found")
val metaPacket = ByteReadPacket(readBytes(tag.metaSize.toInt())) val metaPacket = ByteReadPacket(readBytes(tag.metaSize.toInt()))
val meta = metaFormat.run { metaPacket.readThis() }
val dataBytes = readBytes(tag.dataSize.toInt()) val dataBytes = readBytes(tag.dataSize.toInt())
val meta = metaFormat.run { metaPacket.readThis() }
return SimpleEnvelope(meta, ArrayBinary(dataBytes)) return SimpleEnvelope(meta, ArrayBinary(dataBytes))
} }

View File

@ -0,0 +1,32 @@
package hep.dataforge.io
import kotlin.test.Test
import kotlin.test.assertEquals
class EnvelopeFormatTest {
val envelope = Envelope.build {
type = "test.format"
meta{
"d" to 22.2
}
data{
writeDouble(22.2)
}
}
@ExperimentalStdlibApi
@Test
fun testTaggedFormat(){
TaggedEnvelopeFormat.run {
val bytes = writeBytes(envelope)
println(bytes.decodeToString())
val res = readBytes(bytes)
assertEquals(envelope.meta,res.meta)
val double = res.data?.read {
readDouble()
}
assertEquals(22.2, double)
}
}
}

View File

@ -5,57 +5,58 @@ import hep.dataforge.context.ContextAware
import hep.dataforge.io.* import hep.dataforge.io.*
import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout import kotlinx.io.streams.writePacket
import kotlinx.io.streams.asInput
import kotlinx.io.streams.asOutput
import java.net.Socket import java.net.Socket
import java.util.concurrent.Executors import java.util.concurrent.Executors
import kotlin.time.Duration
import kotlin.time.ExperimentalTime import kotlin.time.ExperimentalTime
import kotlin.time.seconds
@ExperimentalTime @ExperimentalTime
class EnvelopeClient( class EnvelopeClient(
override val context: Context, override val context: Context,
val host: String, val host: String,
val port: Int, val port: Int,
val timeout: Duration = 2.seconds,
val format: EnvelopeFormat = TaggedEnvelopeFormat val format: EnvelopeFormat = TaggedEnvelopeFormat
) : Responder, ContextAware { ) : Responder, ContextAware {
private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
private var socket: Socket? = null // private var socket: SocketChannel? = null
//
private fun getSocket(): Socket { // private fun getSocket(): Socket {
val socket = socket ?: Socket(host, port).also { this.socket = it } // val socket = socket ?: Socket(host, port).also { this.socket = it }
return if (socket.isConnected) { // return if (socket.isConnected) {
socket // socket
} else { // } else {
Socket(host, port).also { this.socket = it } // Socket(host, port).also { this.socket = it }
} // }
} // }
suspend fun close() { suspend fun close() {
try {
respond( respond(
Envelope.build { Envelope.build {
type = EnvelopeServer.SHUTDOWN_ENVELOPE_TYPE type = EnvelopeServer.SHUTDOWN_ENVELOPE_TYPE
} }
) )
} catch (ex: Exception) {
logger.error { ex }
}
} }
override suspend fun respond(request: Envelope): Envelope = withContext(dispatcher) { override suspend fun respond(request: Envelope): Envelope = withContext(dispatcher) {
withTimeout(timeout.toLongMilliseconds()) { //val address = InetSocketAddress(host,port)
val socket = getSocket() val socket = Socket(host, port)
val input = socket.getInputStream().asInput() val input = socket.getInputStream().asInput()
val output = socket.getOutputStream().asOutput() val output = socket.getOutputStream()
format.run { format.run {
output.writeThis(request) output.writePacket {
writeThis(request)
}
logger.debug { "Sent request with type ${request.type} to ${socket.remoteSocketAddress}" } logger.debug { "Sent request with type ${request.type} to ${socket.remoteSocketAddress}" }
val res = input.readThis() val res = input.readThis()
logger.debug { "Received response with type ${res.type} from ${socket.remoteSocketAddress}" } logger.debug { "Received response with type ${res.type} from ${socket.remoteSocketAddress}" }
return@withTimeout res return@withContext res
}
} }
} }
} }

View File

@ -7,10 +7,10 @@ import hep.dataforge.io.Responder
import hep.dataforge.io.TaggedEnvelopeFormat import hep.dataforge.io.TaggedEnvelopeFormat
import hep.dataforge.io.type import hep.dataforge.io.type
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.io.streams.asInput import kotlinx.io.streams.writePacket
import kotlinx.io.streams.asOutput
import java.net.ServerSocket import java.net.ServerSocket
import java.net.Socket import java.net.Socket
import kotlin.concurrent.thread
class EnvelopeServer( class EnvelopeServer(
override val context: Context, override val context: Context,
@ -28,7 +28,7 @@ class EnvelopeServer(
val job = scope.launch(Dispatchers.IO) { val job = scope.launch(Dispatchers.IO) {
val serverSocket = ServerSocket(port) val serverSocket = ServerSocket(port)
//TODO add handshake and format negotiation //TODO add handshake and format negotiation
while (!serverSocket.isClosed) { while (isActive && !serverSocket.isClosed) {
val socket = serverSocket.accept() val socket = serverSocket.accept()
logger.info { "Accepted connection from ${socket.remoteSocketAddress}" } logger.info { "Accepted connection from ${socket.remoteSocketAddress}" }
readSocket(socket) readSocket(socket)
@ -43,22 +43,49 @@ class EnvelopeServer(
job = null job = null
} }
private fun CoroutineScope.readSocket(socket: Socket) { // private fun CoroutineScope.readSocket(socket: Socket) {
// launch(Dispatchers.IO) {
// val input = socket.getInputStream().asInput()
// val output = socket.getOutputStream().asOutput()
// format.run {
// while (isActive && socket.isConnected) {
// val request = input.readThis()
// logger.debug { "Accepted request with type ${request.type} from ${socket.remoteSocketAddress}" }
// if (request.type == SHUTDOWN_ENVELOPE_TYPE) {
// //Echo shutdown command
// logger.info { "Accepted graceful shutdown signal from ${socket.inetAddress}" }
// socket.close()
// cancel("Graceful connection shutdown requested by client")
// }
// val response = responder.respond(request)
// output.writeThis(response)
// }
// }
// }
// }
private fun readSocket(socket: Socket) {
thread {
val input = socket.getInputStream().asInput() val input = socket.getInputStream().asInput()
val output = socket.getOutputStream().asOutput() val outputStream = socket.getOutputStream()
format.run { format.run {
launch { while (socket.isConnected) {
while (isActive && socket.isConnected) {
val request = input.readThis() val request = input.readThis()
logger.debug { "Accepted request with type ${request.type} from ${socket.remoteSocketAddress}" } logger.debug { "Accepted request with type ${request.type} from ${socket.remoteSocketAddress}" }
if (request.type == SHUTDOWN_ENVELOPE_TYPE) { if (request.type == SHUTDOWN_ENVELOPE_TYPE) {
//Echo shutdown command //Echo shutdown command
logger.info { "Accepted graceful shutdown signal from ${socket.inetAddress}" } logger.info { "Accepted graceful shutdown signal from ${socket.inetAddress}" }
socket.close() socket.close()
cancel("Graceful connection shutdown requested by client") return@thread
// cancel("Graceful connection shutdown requested by client")
} }
runBlocking {
val response = responder.respond(request) val response = responder.respond(request)
output.writeThis(response) outputStream.writePacket {
writeThis(response)
}
logger.debug { "Sent response with type ${response.type} to ${socket.remoteSocketAddress}" }
}
} }
} }
} }

View File

@ -0,0 +1,34 @@
package hep.dataforge.io.tcp
import kotlinx.io.core.AbstractInput
import kotlinx.io.core.Input
import kotlinx.io.core.IoBuffer
import kotlinx.io.core.IoBuffer.Companion.NoPool
import kotlinx.io.core.writePacket
import kotlinx.io.streams.readPacketAtMost
import java.io.InputStream
/**
* Modified version of InputStream to Input converter that supports waiting for input
*/
internal class InputStreamAsInput(
private val stream: InputStream
) : AbstractInput(pool = NoPool) {
override fun fill(): IoBuffer? {
val packet = stream.readPacketAtMost(4096)
return pool.borrow().apply {
resetForWrite(4096)
writePacket(packet)
}
}
override fun closeSource() {
stream.close()
}
}
fun InputStream.asInput(): Input =
InputStreamAsInput(this)

View File

@ -3,16 +3,28 @@ package hep.dataforge.io.tcp
import hep.dataforge.context.Global import hep.dataforge.context.Global
import hep.dataforge.io.Envelope import hep.dataforge.io.Envelope
import hep.dataforge.io.Responder import hep.dataforge.io.Responder
import hep.dataforge.io.TaggedEnvelopeFormat
import hep.dataforge.io.writeBytes
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.runBlocking
import org.junit.AfterClass import org.junit.AfterClass
import org.junit.BeforeClass import org.junit.BeforeClass
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.time.ExperimentalTime import kotlin.time.ExperimentalTime
@ExperimentalStdlibApi
object EchoResponder : Responder { object EchoResponder : Responder {
override suspend fun respond(request: Envelope): Envelope = request override suspend fun respond(request: Envelope): Envelope {
val string = TaggedEnvelopeFormat.run { writeBytes(request).decodeToString() }
println("ECHO:")
println(string)
return request
}
} }
@ExperimentalTime @ExperimentalTime
@ExperimentalStdlibApi
class EnvelopeServerTest { class EnvelopeServerTest {
companion object { companion object {
@JvmStatic @JvmStatic
@ -32,27 +44,25 @@ class EnvelopeServerTest {
} }
// @Test @Test
// fun doEchoTest() { fun doEchoTest() {
// val client = EnvelopeClient(Global, host = "localhost", port = 7778) val client = EnvelopeClient(Global, host = "localhost", port = 7778)
// val request = Envelope.build { val request = Envelope.build {
// type = "test.echo" type = "test.echo"
// meta { meta {
// "test.value" to 22 "test.value" to 22
// } }
// data { data {
// writeDouble(22.7) writeDouble(22.7)
// } }
// } }
// val response = runBlocking { runBlocking {
// client.respond(request) val response = client.respond(request)
// }
//
// assertEquals(request.meta, response.meta) assertEquals(request.meta, response.meta)
// assertEquals(request.data, response.data) // assertEquals(request.data?.toBytes()?.decodeToString(), response.data?.toBytes()?.decodeToString())
// client.close()
// runBlocking { }
// client.close() }
// }
// }
} }

View File

@ -187,11 +187,6 @@ abstract class MetaBase: Meta{
override fun equals(other: Any?): Boolean = if(other is Meta) { override fun equals(other: Any?): Boolean = if(other is Meta) {
this.items == other.items this.items == other.items
// val items = items
// val otherItems = other.items
// (items.keys == otherItems.keys) && items.keys.all {
// items[it] == otherItems[it]
// }
} else { } else {
false false
} }

View File

@ -1,5 +1,7 @@
package hep.dataforge.meta package hep.dataforge.meta
import hep.dataforge.names.Name
/** /**
* Marker interface for classes with specifications * Marker interface for classes with specifications
*/ */
@ -61,5 +63,5 @@ fun <C : Specific, S : Specification<C>> S.createStyle(action: C.() -> Unit): Me
fun <C : Specific> Specific.spec( fun <C : Specific> Specific.spec(
spec: Specification<C>, spec: Specification<C>,
key: String? = null key: Name? = null
): MutableMorphDelegate<Config, C> = MutableMorphDelegate(config, key) { spec.wrap(it) } ): MutableMorphDelegate<Config, C> = MutableMorphDelegate(config, key) { spec.wrap(it) }

View File

@ -1,5 +1,6 @@
package hep.dataforge.meta package hep.dataforge.meta
import hep.dataforge.names.Name
import hep.dataforge.values.DoubleArrayValue import hep.dataforge.values.DoubleArrayValue
import hep.dataforge.values.Null import hep.dataforge.values.Null
import hep.dataforge.values.Value import hep.dataforge.values.Value
@ -11,126 +12,126 @@ import kotlin.jvm.JvmName
/** /**
* A property delegate that uses custom key * A property delegate that uses custom key
*/ */
fun Configurable.value(default: Any = Null, key: String? = null): MutableValueDelegate<Config> = fun Configurable.value(default: Any = Null, key: Name? = null): MutableValueDelegate<Config> =
MutableValueDelegate(config, key, Value.of(default)) MutableValueDelegate(config, key, Value.of(default))
fun <T> Configurable.value( fun <T> Configurable.value(
default: T? = null, default: T? = null,
key: String? = null, key: Name? = null,
writer: (T) -> Value = { Value.of(it) }, writer: (T) -> Value = { Value.of(it) },
reader: (Value?) -> T reader: (Value?) -> T
): ReadWriteDelegateWrapper<Value?, T> = ): ReadWriteDelegateWrapper<Value?, T> =
MutableValueDelegate(config, key, default?.let { Value.of(it) }).transform(reader = reader, writer = writer) MutableValueDelegate(config, key, default?.let { Value.of(it) }).transform(reader = reader, writer = writer)
fun Configurable.string(default: String? = null, key: String? = null): MutableStringDelegate<Config> = fun Configurable.string(default: String? = null, key: Name? = null): MutableStringDelegate<Config> =
MutableStringDelegate(config, key, default) MutableStringDelegate(config, key, default)
fun Configurable.boolean(default: Boolean? = null, key: String? = null): MutableBooleanDelegate<Config> = fun Configurable.boolean(default: Boolean? = null, key: Name? = null): MutableBooleanDelegate<Config> =
MutableBooleanDelegate(config, key, default) MutableBooleanDelegate(config, key, default)
fun Configurable.number(default: Number? = null, key: String? = null): MutableNumberDelegate<Config> = fun Configurable.number(default: Number? = null, key: Name? = null): MutableNumberDelegate<Config> =
MutableNumberDelegate(config, key, default) MutableNumberDelegate(config, key, default)
/* Number delegates*/ /* Number delegates*/
fun Configurable.int(default: Int? = null, key: String? = null) = fun Configurable.int(default: Int? = null, key: Name? = null) =
number(default, key).int number(default, key).int
fun Configurable.double(default: Double? = null, key: String? = null) = fun Configurable.double(default: Double? = null, key: Name? = null) =
number(default, key).double number(default, key).double
fun Configurable.long(default: Long? = null, key: String? = null) = fun Configurable.long(default: Long? = null, key: Name? = null) =
number(default, key).long number(default, key).long
fun Configurable.short(default: Short? = null, key: String? = null) = fun Configurable.short(default: Short? = null, key: Name? = null) =
number(default, key).short number(default, key).short
fun Configurable.float(default: Float? = null, key: String? = null) = fun Configurable.float(default: Float? = null, key: Name? = null) =
number(default, key).float number(default, key).float
@JvmName("safeString") @JvmName("safeString")
fun Configurable.string(default: String, key: String? = null) = fun Configurable.string(default: String, key: Name? = null) =
MutableSafeStringDelegate(config, key) { default } MutableSafeStringDelegate(config, key) { default }
@JvmName("safeBoolean") @JvmName("safeBoolean")
fun Configurable.boolean(default: Boolean, key: String? = null) = fun Configurable.boolean(default: Boolean, key: Name? = null) =
MutableSafeBooleanDelegate(config, key) { default } MutableSafeBooleanDelegate(config, key) { default }
@JvmName("safeNumber") @JvmName("safeNumber")
fun Configurable.number(default: Number, key: String? = null) = fun Configurable.number(default: Number, key: Name? = null) =
MutableSafeNumberDelegate(config, key) { default } MutableSafeNumberDelegate(config, key) { default }
@JvmName("safeString") @JvmName("safeString")
fun Configurable.string(key: String? = null, default: () -> String) = fun Configurable.string(key: Name? = null, default: () -> String) =
MutableSafeStringDelegate(config, key, default) MutableSafeStringDelegate(config, key, default)
@JvmName("safeBoolean") @JvmName("safeBoolean")
fun Configurable.boolean(key: String? = null, default: () -> Boolean) = fun Configurable.boolean(key: Name? = null, default: () -> Boolean) =
MutableSafeBooleanDelegate(config, key, default) MutableSafeBooleanDelegate(config, key, default)
@JvmName("safeNumber") @JvmName("safeNumber")
fun Configurable.number(key: String? = null, default: () -> Number) = fun Configurable.number(key: Name? = null, default: () -> Number) =
MutableSafeNumberDelegate(config, key, default) MutableSafeNumberDelegate(config, key, default)
/* Safe number delegates*/ /* Safe number delegates*/
@JvmName("safeInt") @JvmName("safeInt")
fun Configurable.int(default: Int, key: String? = null) = fun Configurable.int(default: Int, key: Name? = null) =
number(default, key).int number(default, key).int
@JvmName("safeDouble") @JvmName("safeDouble")
fun Configurable.double(default: Double, key: String? = null) = fun Configurable.double(default: Double, key: Name? = null) =
number(default, key).double number(default, key).double
@JvmName("safeLong") @JvmName("safeLong")
fun Configurable.long(default: Long, key: String? = null) = fun Configurable.long(default: Long, key: Name? = null) =
number(default, key).long number(default, key).long
@JvmName("safeShort") @JvmName("safeShort")
fun Configurable.short(default: Short, key: String? = null) = fun Configurable.short(default: Short, key: Name? = null) =
number(default, key).short number(default, key).short
@JvmName("safeFloat") @JvmName("safeFloat")
fun Configurable.float(default: Float, key: String? = null) = fun Configurable.float(default: Float, key: Name? = null) =
number(default, key).float number(default, key).float
/** /**
* Enum delegate * Enum delegate
*/ */
inline fun <reified E : Enum<E>> Configurable.enum(default: E, key: String? = null) = inline fun <reified E : Enum<E>> Configurable.enum(default: E, key: Name? = null) =
MutableSafeEnumvDelegate(config, key, default) { enumValueOf(it) } MutableSafeEnumvDelegate(config, key, default) { enumValueOf(it) }
/* Node delegates */ /* Node delegates */
fun Configurable.node(key: String? = null) = MutableNodeDelegate(config, key) fun Configurable.node(key: Name? = null) = MutableNodeDelegate(config, key)
fun <T : Specific> Configurable.spec(spec: Specification<T>, key: String? = null) = fun <T : Specific> Configurable.spec(spec: Specification<T>, key: Name? = null) =
MutableMorphDelegate(config, key) { spec.wrap(it) } MutableMorphDelegate(config, key) { spec.wrap(it) }
fun <T : Specific> Configurable.spec(builder: (Config) -> T, key: String? = null) = fun <T : Specific> Configurable.spec(builder: (Config) -> T, key: Name? = null) =
MutableMorphDelegate(config, key) { specification(builder).wrap(it) } MutableMorphDelegate(config, key) { specification(builder).wrap(it) }
/* /*
* Extra delegates for special cases * Extra delegates for special cases
*/ */
fun Configurable.stringList(key: String? = null): ReadWriteDelegateWrapper<Value?, List<String>> = fun Configurable.stringList(key: Name? = null): ReadWriteDelegateWrapper<Value?, List<String>> =
value(emptyList(), key) { it?.list?.map { value -> value.string } ?: emptyList() } value(emptyList(), key) { it?.list?.map { value -> value.string } ?: emptyList() }
fun Configurable.numberList(key: String? = null): ReadWriteDelegateWrapper<Value?, List<Number>> = fun Configurable.numberList(key: Name? = null): ReadWriteDelegateWrapper<Value?, List<Number>> =
value(emptyList(), key) { it?.list?.map { value -> value.number } ?: emptyList() } value(emptyList(), key) { it?.list?.map { value -> value.number } ?: emptyList() }
/** /**
* A special delegate for double arrays * A special delegate for double arrays
*/ */
fun Configurable.doubleArray(key: String? = null): ReadWriteDelegateWrapper<Value?, DoubleArray> = fun Configurable.doubleArray(key: Name? = null): ReadWriteDelegateWrapper<Value?, DoubleArray> =
value(doubleArrayOf(), key) { value(doubleArrayOf(), key) {
(it as? DoubleArrayValue)?.value (it as? DoubleArrayValue)?.value
?: it?.list?.map { value -> value.number.toDouble() }?.toDoubleArray() ?: it?.list?.map { value -> value.number.toDouble() }?.toDoubleArray()
?: doubleArrayOf() ?: doubleArrayOf()
} }
fun <T : Configurable> Configurable.child(key: String? = null, converter: (Meta) -> T) = fun <T : Configurable> Configurable.child(key: Name? = null, converter: (Meta) -> T) =
MutableMorphDelegate(config, key, converter) MutableMorphDelegate(config, key, converter)

View File

@ -1,5 +1,7 @@
package hep.dataforge.meta package hep.dataforge.meta
import hep.dataforge.names.Name
import hep.dataforge.names.asName
import hep.dataforge.values.Null import hep.dataforge.values.Null
import hep.dataforge.values.Value import hep.dataforge.values.Value
import hep.dataforge.values.asValue import hep.dataforge.values.asValue
@ -173,15 +175,15 @@ fun <T : Metoid> Metoid.child(key: String? = null, converter: (Meta) -> T) = Chi
class MutableValueDelegate<M : MutableMeta<M>>( class MutableValueDelegate<M : MutableMeta<M>>(
val meta: M, val meta: M,
private val key: String? = null, private val key: Name? = null,
private val default: Value? = null private val default: Value? = null
) : ReadWriteProperty<Any?, Value?> { ) : ReadWriteProperty<Any?, Value?> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Value? { override fun getValue(thisRef: Any?, property: KProperty<*>): Value? {
return meta[key ?: property.name]?.value ?: default return meta[key ?: property.name.asName()]?.value ?: default
} }
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Value?) { override fun setValue(thisRef: Any?, property: KProperty<*>, value: Value?) {
val name = key ?: property.name val name = key ?: property.name.asName()
if (value == null) { if (value == null) {
meta.remove(name) meta.remove(name)
} else { } else {
@ -195,15 +197,15 @@ class MutableValueDelegate<M : MutableMeta<M>>(
class MutableStringDelegate<M : MutableMeta<M>>( class MutableStringDelegate<M : MutableMeta<M>>(
val meta: M, val meta: M,
private val key: String? = null, private val key: Name? = null,
private val default: String? = null private val default: String? = null
) : ReadWriteProperty<Any?, String?> { ) : ReadWriteProperty<Any?, String?> {
override fun getValue(thisRef: Any?, property: KProperty<*>): String? { override fun getValue(thisRef: Any?, property: KProperty<*>): String? {
return meta[key ?: property.name]?.string ?: default return meta[key ?: property.name.asName()]?.string ?: default
} }
override fun setValue(thisRef: Any?, property: KProperty<*>, value: String?) { override fun setValue(thisRef: Any?, property: KProperty<*>, value: String?) {
val name = key ?: property.name val name = key ?: property.name.asName()
if (value == null) { if (value == null) {
meta.remove(name) meta.remove(name)
} else { } else {
@ -214,15 +216,15 @@ class MutableStringDelegate<M : MutableMeta<M>>(
class MutableBooleanDelegate<M : MutableMeta<M>>( class MutableBooleanDelegate<M : MutableMeta<M>>(
val meta: M, val meta: M,
private val key: String? = null, private val key: Name? = null,
private val default: Boolean? = null private val default: Boolean? = null
) : ReadWriteProperty<Any?, Boolean?> { ) : ReadWriteProperty<Any?, Boolean?> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean? { override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean? {
return meta[key ?: property.name]?.boolean ?: default return meta[key ?: property.name.asName()]?.boolean ?: default
} }
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean?) { override fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean?) {
val name = key ?: property.name val name = key ?: property.name.asName()
if (value == null) { if (value == null) {
meta.remove(name) meta.remove(name)
} else { } else {
@ -233,15 +235,15 @@ class MutableBooleanDelegate<M : MutableMeta<M>>(
class MutableNumberDelegate<M : MutableMeta<M>>( class MutableNumberDelegate<M : MutableMeta<M>>(
val meta: M, val meta: M,
private val key: String? = null, private val key: Name? = null,
private val default: Number? = null private val default: Number? = null
) : ReadWriteProperty<Any?, Number?> { ) : ReadWriteProperty<Any?, Number?> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Number? { override fun getValue(thisRef: Any?, property: KProperty<*>): Number? {
return meta[key ?: property.name]?.number ?: default return meta[key ?: property.name.asName()]?.number ?: default
} }
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Number?) { override fun setValue(thisRef: Any?, property: KProperty<*>, value: Number?) {
val name = key ?: property.name val name = key ?: property.name.asName()
if (value == null) { if (value == null) {
meta.remove(name) meta.remove(name)
} else { } else {
@ -260,52 +262,52 @@ class MutableNumberDelegate<M : MutableMeta<M>>(
class MutableSafeStringDelegate<M : MutableMeta<M>>( class MutableSafeStringDelegate<M : MutableMeta<M>>(
val meta: M, val meta: M,
private val key: String? = null, private val key: Name? = null,
default: () -> String default: () -> String
) : ReadWriteProperty<Any?, String> { ) : ReadWriteProperty<Any?, String> {
private val default: String by lazy(default) private val default: String by lazy(default)
override fun getValue(thisRef: Any?, property: KProperty<*>): String { override fun getValue(thisRef: Any?, property: KProperty<*>): String {
return meta[key ?: property.name]?.string ?: default return meta[key ?: property.name.asName()]?.string ?: default
} }
override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
meta.setValue(key ?: property.name, value.asValue()) meta.setValue(key ?: property.name.asName(), value.asValue())
} }
} }
class MutableSafeBooleanDelegate<M : MutableMeta<M>>( class MutableSafeBooleanDelegate<M : MutableMeta<M>>(
val meta: M, val meta: M,
private val key: String? = null, private val key: Name? = null,
default: () -> Boolean default: () -> Boolean
) : ReadWriteProperty<Any?, Boolean> { ) : ReadWriteProperty<Any?, Boolean> {
private val default: Boolean by lazy(default) private val default: Boolean by lazy(default)
override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean { override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean {
return meta[key ?: property.name]?.boolean ?: default return meta[key ?: property.name.asName()]?.boolean ?: default
} }
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) { override fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) {
meta.setValue(key ?: property.name, value.asValue()) meta.setValue(key ?: property.name.asName(), value.asValue())
} }
} }
class MutableSafeNumberDelegate<M : MutableMeta<M>>( class MutableSafeNumberDelegate<M : MutableMeta<M>>(
val meta: M, val meta: M,
private val key: String? = null, private val key: Name? = null,
default: () -> Number default: () -> Number
) : ReadWriteProperty<Any?, Number> { ) : ReadWriteProperty<Any?, Number> {
private val default: Number by lazy(default) private val default: Number by lazy(default)
override fun getValue(thisRef: Any?, property: KProperty<*>): Number { override fun getValue(thisRef: Any?, property: KProperty<*>): Number {
return meta[key ?: property.name]?.number ?: default return meta[key ?: property.name.asName()]?.number ?: default
} }
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Number) { override fun setValue(thisRef: Any?, property: KProperty<*>, value: Number) {
meta.setValue(key ?: property.name, value.asValue()) meta.setValue(key ?: property.name.asName(), value.asValue())
} }
val double get() = ReadWriteDelegateWrapper(this, reader = { it.toDouble() }, writer = { it }) val double get() = ReadWriteDelegateWrapper(this, reader = { it.toDouble() }, writer = { it })
@ -317,16 +319,16 @@ class MutableSafeNumberDelegate<M : MutableMeta<M>>(
class MutableSafeEnumvDelegate<M : MutableMeta<M>, E : Enum<E>>( class MutableSafeEnumvDelegate<M : MutableMeta<M>, E : Enum<E>>(
val meta: M, val meta: M,
private val key: String? = null, private val key: Name? = null,
private val default: E, private val default: E,
private val resolver: (String) -> E private val resolver: (String) -> E
) : ReadWriteProperty<Any?, E> { ) : ReadWriteProperty<Any?, E> {
override fun getValue(thisRef: Any?, property: KProperty<*>): E { override fun getValue(thisRef: Any?, property: KProperty<*>): E {
return (meta[key ?: property.name]?.string)?.let { resolver(it) } ?: default return (meta[key ?: property.name.asName()]?.string)?.let { resolver(it) } ?: default
} }
override fun setValue(thisRef: Any?, property: KProperty<*>, value: E) { override fun setValue(thisRef: Any?, property: KProperty<*>, value: E) {
meta.setValue(key ?: property.name, value.name.asValue()) meta.setValue(key ?: property.name.asName(), value.name.asValue())
} }
} }
@ -334,31 +336,31 @@ class MutableSafeEnumvDelegate<M : MutableMeta<M>, E : Enum<E>>(
class MutableNodeDelegate<M : MutableMeta<M>>( class MutableNodeDelegate<M : MutableMeta<M>>(
val meta: M, val meta: M,
private val key: String? = null private val key: Name? = null
) : ReadWriteProperty<Any?, Meta?> { ) : ReadWriteProperty<Any?, Meta?> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Meta? { override fun getValue(thisRef: Any?, property: KProperty<*>): Meta? {
return meta[key ?: property.name]?.node return meta[key ?: property.name.asName()]?.node
} }
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Meta?) { override fun setValue(thisRef: Any?, property: KProperty<*>, value: Meta?) {
meta[key ?: property.name] = value meta[key ?: property.name.asName()] = value
} }
} }
class MutableMorphDelegate<M : MutableMeta<M>, T : Configurable>( class MutableMorphDelegate<M : MutableMeta<M>, T : Configurable>(
val meta: M, val meta: M,
private val key: String? = null, private val key: Name? = null,
private val converter: (Meta) -> T private val converter: (Meta) -> T
) : ReadWriteProperty<Any?, T?> { ) : ReadWriteProperty<Any?, T?> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T? { override fun getValue(thisRef: Any?, property: KProperty<*>): T? {
return meta[key ?: property.name]?.node?.let(converter) return meta[key ?: property.name.asName()]?.node?.let(converter)
} }
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) { override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
if (value == null) { if (value == null) {
meta.remove(key ?: property.name) meta.remove(key ?: property.name.asName())
} else { } else {
meta[key ?: property.name] = value.config meta[key ?: property.name.asName()] = value.config
} }
} }
} }
@ -383,44 +385,45 @@ class ReadWriteDelegateWrapper<T, R>(
/** /**
* A property delegate that uses custom key * A property delegate that uses custom key
*/ */
fun <M : MutableMeta<M>> M.value(default: Value = Null, key: String? = null) = fun <M : MutableMeta<M>> M.value(default: Value = Null, key: Name? = null) =
MutableValueDelegate(this, key, default) MutableValueDelegate(this, key, default)
fun <M : MutableMeta<M>> M.string(default: String? = null, key: String? = null) = fun <M : MutableMeta<M>> M.string(default: String? = null, key: Name? = null) =
MutableStringDelegate(this, key, default) MutableStringDelegate(this, key, default)
fun <M : MutableMeta<M>> M.boolean(default: Boolean? = null, key: String? = null) = fun <M : MutableMeta<M>> M.boolean(default: Boolean? = null, key: Name? = null) =
MutableBooleanDelegate(this, key, default) MutableBooleanDelegate(this, key, default)
fun <M : MutableMeta<M>> M.number(default: Number? = null, key: String? = null) = fun <M : MutableMeta<M>> M.number(default: Number? = null, key: Name? = null) =
MutableNumberDelegate(this, key, default) MutableNumberDelegate(this, key, default)
fun <M : MutableMeta<M>> M.node(key: String? = null) = MutableNodeDelegate(this, key) fun <M : MutableMeta<M>> M.node(key: Name? = null) =
MutableNodeDelegate(this, key)
@JvmName("safeString") @JvmName("safeString")
fun <M : MutableMeta<M>> M.string(default: String, key: String? = null) = fun <M : MutableMeta<M>> M.string(default: String, key: Name? = null) =
MutableSafeStringDelegate(this, key) { default } MutableSafeStringDelegate(this, key) { default }
@JvmName("safeBoolean") @JvmName("safeBoolean")
fun <M : MutableMeta<M>> M.boolean(default: Boolean, key: String? = null) = fun <M : MutableMeta<M>> M.boolean(default: Boolean, key: Name? = null) =
MutableSafeBooleanDelegate(this, key) { default } MutableSafeBooleanDelegate(this, key) { default }
@JvmName("safeNumber") @JvmName("safeNumber")
fun <M : MutableMeta<M>> M.number(default: Number, key: String? = null) = fun <M : MutableMeta<M>> M.number(default: Number, key: Name? = null) =
MutableSafeNumberDelegate(this, key) { default } MutableSafeNumberDelegate(this, key) { default }
@JvmName("safeString") @JvmName("safeString")
fun <M : MutableMeta<M>> M.string(key: String? = null, default: () -> String) = fun <M : MutableMeta<M>> M.string(key: Name? = null, default: () -> String) =
MutableSafeStringDelegate(this, key, default) MutableSafeStringDelegate(this, key, default)
@JvmName("safeBoolean") @JvmName("safeBoolean")
fun <M : MutableMeta<M>> M.boolean(key: String? = null, default: () -> Boolean) = fun <M : MutableMeta<M>> M.boolean(key: Name? = null, default: () -> Boolean) =
MutableSafeBooleanDelegate(this, key, default) MutableSafeBooleanDelegate(this, key, default)
@JvmName("safeNumber") @JvmName("safeNumber")
fun <M : MutableMeta<M>> M.number(key: String? = null, default: () -> Number) = fun <M : MutableMeta<M>> M.number(key: Name? = null, default: () -> Number) =
MutableSafeNumberDelegate(this, key, default) MutableSafeNumberDelegate(this, key, default)
inline fun <M : MutableMeta<M>, reified E : Enum<E>> M.enum(default: E, key: String? = null) = inline fun <M : MutableMeta<M>, reified E : Enum<E>> M.enum(default: E, key: Name? = null) =
MutableSafeEnumvDelegate(this, key, default) { enumValueOf(it) } MutableSafeEnumvDelegate(this, key, default) { enumValueOf(it) }