From c44e004495bd35056e8720e796801c58c3d3977b Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 9 Sep 2019 16:15:35 +0300 Subject: [PATCH] Envelope client-sever EAP --- dataforge-io/build.gradle.kts | 9 +- .../kotlin/hep/dataforge/io/Binary.kt | 27 ++---- .../kotlin/hep/dataforge/io/Envelope.kt | 30 +++++-- .../kotlin/hep/dataforge/io/IOFormat.kt | 1 + .../hep/dataforge/io/TaggedEnvelopeFormat.kt | 7 +- .../hep/dataforge/io/EnvelopeFormatTest.kt | 32 +++++++ .../hep/dataforge/io/tcp/EnvelopeClient.kt | 61 ++++++------- .../hep/dataforge/io/tcp/EnvelopeServer.kt | 51 ++++++++--- .../dataforge/io/tcp/InputStreamAsInput.kt | 34 +++++++ .../dataforge/io/tcp/EnvelopeServerTest.kt | 58 +++++++----- .../kotlin/hep/dataforge/meta/Meta.kt | 5 -- .../kotlin/hep/dataforge/meta/Specific.kt | 4 +- .../hep/dataforge/meta/configDelegates.kt | 59 ++++++------ .../hep/dataforge/meta/metaDelegates.kt | 89 ++++++++++--------- 14 files changed, 291 insertions(+), 176 deletions(-) create mode 100644 dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopeFormatTest.kt create mode 100644 dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/InputStreamAsInput.kt diff --git a/dataforge-io/build.gradle.kts b/dataforge-io/build.gradle.kts index b4306efc..ce3a8be8 100644 --- a/dataforge-io/build.gradle.kts +++ b/dataforge-io/build.gradle.kts @@ -15,11 +15,18 @@ kotlin { commonMain{ dependencies { 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{ dependencies{ - api(npm("text-encoding")) + //api(npm("text-encoding")) + api("org.jetbrains.kotlinx:kotlinx-io-js:0.1.14") } } } diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Binary.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Binary.kt index da62113b..429ab185 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Binary.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Binary.kt @@ -39,8 +39,8 @@ interface RandomAccessBinary : Binary { override fun read(block: Input.() -> R): R = read(0.toUInt(), UInt.MAX_VALUE, block) } -fun Binary.readAll(): ByteReadPacket = read { - ByteReadPacket(this.readBytes()) +fun Binary.toBytes(): ByteArray = read { + this.readBytes() } @ExperimentalUnsignedTypes @@ -56,10 +56,11 @@ object EmptyBinary : RandomAccessBinary { override fun read(from: UInt, size: UInt, block: Input.() -> R): R { error("The binary is empty") } + } @ExperimentalUnsignedTypes -class ArrayBinary(val array: ByteArray) : RandomAccessBinary { +inline class ArrayBinary(val array: ByteArray) : RandomAccessBinary { override val size: ULong get() = array.size.toULong() override fun 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 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 */ @@ -87,13 +78,9 @@ fun Binary.readWith(format: IOFormat): T = format.run { } } -/** - * Write this object to a binary - * TODO make a lazy binary that does not use intermediate array - */ -fun T.writeWith(format: IOFormat): Binary = format.run { +fun IOFormat.writeBinary(obj: T): Binary { val packet = buildPacket { - writeThis(this@writeWith) + writeThis(obj) } - return@run ArrayBinary(packet.readBytes()) + return ArrayBinary(packet.readBytes()) } \ No newline at end of file diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt index 36178d98..6c3b45b9 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt @@ -1,6 +1,8 @@ package hep.dataforge.io import hep.dataforge.meta.* +import hep.dataforge.names.asName +import hep.dataforge.names.plus import kotlinx.io.core.Output import kotlinx.io.core.buildPacket import kotlinx.io.core.readBytes @@ -14,10 +16,11 @@ interface Envelope { /** * meta keys */ - const val ENVELOPE_NODE = "@envelope" - const val ENVELOPE_TYPE_KEY = "$ENVELOPE_NODE.type" - const val ENVELOPE_DATA_TYPE_KEY = "$ENVELOPE_NODE.dataType" - const val ENVELOPE_DESCRIPTION_KEY = "$ENVELOPE_NODE.description" + val ENVELOPE_NODE_KEY = "@envelope".asName() + val ENVELOPE_TYPE_KEY = ENVELOPE_NODE_KEY + "type" + val ENVELOPE_DATA_TYPE_KEY = ENVELOPE_NODE_KEY + "dataType" + val ENVELOPE_DATA_ID_KEY = ENVELOPE_NODE_KEY + "dataID" + val ENVELOPE_DESCRIPTION_KEY = ENVELOPE_NODE_KEY + "description" //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 * - * @return */ val Envelope.type: String? get() = meta[Envelope.ENVELOPE_TYPE_KEY].string /** * The type of data encoding * - * @return */ val Envelope.dataType: String? get() = meta[Envelope.ENVELOPE_DATA_TYPE_KEY].string /** * Textual user friendly description * - * @return */ 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 */ @@ -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 { return when { diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt index 0ebf9485..e2a1f16e 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt @@ -36,6 +36,7 @@ interface IOFormat : Named { fun IOFormat.writePacket(obj: T): ByteReadPacket = buildPacket { writeThis(obj) } fun IOFormat.writeBytes(obj: T): ByteArray = buildPacket { writeThis(obj) }.readBytes() +fun IOFormat.readBytes(array: ByteArray): T = ByteReadPacket(array).readThis() object DoubleIOFormat : IOFormat { diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt index 47e8ab5d..7bb04ec4 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt @@ -10,7 +10,7 @@ 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 + private const val TAG_SIZE = 24u override val name: Name = super.name + VERSION @@ -31,6 +31,8 @@ object TaggedEnvelopeFormat : EnvelopeFormat { val metaFormatKey = readShort() val metaLength = readUInt() val dataLength = readULong() + val end = readTextExactBytes(4) + if (end != END_SEQUENCE) error("The input is not an envelope") return Tag(metaFormatKey, metaLength, dataLength) } @@ -55,10 +57,9 @@ object TaggedEnvelopeFormat : EnvelopeFormat { ?: 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()) + val meta = metaFormat.run { metaPacket.readThis() } return SimpleEnvelope(meta, ArrayBinary(dataBytes)) } diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopeFormatTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopeFormatTest.kt new file mode 100644 index 00000000..059c0f19 --- /dev/null +++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopeFormatTest.kt @@ -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) + } + } +} \ No newline at end of file diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeClient.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeClient.kt index 61b792fd..17b0429d 100644 --- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeClient.kt +++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeClient.kt @@ -5,57 +5,58 @@ import hep.dataforge.context.ContextAware import hep.dataforge.io.* import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.withContext -import kotlinx.coroutines.withTimeout -import kotlinx.io.streams.asInput -import kotlinx.io.streams.asOutput +import kotlinx.io.streams.writePacket import java.net.Socket import java.util.concurrent.Executors -import kotlin.time.Duration import kotlin.time.ExperimentalTime -import kotlin.time.seconds @ExperimentalTime class EnvelopeClient( override val context: Context, val host: String, val port: Int, - val timeout: Duration = 2.seconds, val format: EnvelopeFormat = TaggedEnvelopeFormat ) : Responder, ContextAware { private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() - private var socket: Socket? = null +// private var socket: SocketChannel? = null +// +// private fun getSocket(): Socket { +// val socket = socket ?: Socket(host, port).also { this.socket = it } +// return if (socket.isConnected) { +// socket +// } else { +// Socket(host, port).also { this.socket = it } +// } +// } - private fun getSocket(): Socket { - val socket = socket ?: Socket(host, port).also { this.socket = it } - return if (socket.isConnected) { - socket - } else { - Socket(host, port).also { this.socket = it } + suspend fun close() { + try { + respond( + Envelope.build { + type = EnvelopeServer.SHUTDOWN_ENVELOPE_TYPE + } + ) + } catch (ex: Exception) { + logger.error { ex } } } - suspend fun close() { - respond( - Envelope.build { - type = EnvelopeServer.SHUTDOWN_ENVELOPE_TYPE - } - ) - } override suspend fun respond(request: Envelope): Envelope = withContext(dispatcher) { - withTimeout(timeout.toLongMilliseconds()) { - val socket = getSocket() - val input = socket.getInputStream().asInput() - val output = socket.getOutputStream().asOutput() - format.run { - output.writeThis(request) - logger.debug { "Sent request with type ${request.type} to ${socket.remoteSocketAddress}" } - val res = input.readThis() - logger.debug { "Received response with type ${res.type} from ${socket.remoteSocketAddress}" } - return@withTimeout res + //val address = InetSocketAddress(host,port) + val socket = Socket(host, port) + val input = socket.getInputStream().asInput() + val output = socket.getOutputStream() + format.run { + output.writePacket { + writeThis(request) } + logger.debug { "Sent request with type ${request.type} to ${socket.remoteSocketAddress}" } + val res = input.readThis() + logger.debug { "Received response with type ${res.type} from ${socket.remoteSocketAddress}" } + return@withContext res } } } \ No newline at end of file diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeServer.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeServer.kt index 9d6c5826..68da8375 100644 --- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeServer.kt +++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeServer.kt @@ -7,10 +7,10 @@ import hep.dataforge.io.Responder import hep.dataforge.io.TaggedEnvelopeFormat import hep.dataforge.io.type import kotlinx.coroutines.* -import kotlinx.io.streams.asInput -import kotlinx.io.streams.asOutput +import kotlinx.io.streams.writePacket import java.net.ServerSocket import java.net.Socket +import kotlin.concurrent.thread class EnvelopeServer( override val context: Context, @@ -28,7 +28,7 @@ class EnvelopeServer( val job = scope.launch(Dispatchers.IO) { val serverSocket = ServerSocket(port) //TODO add handshake and format negotiation - while (!serverSocket.isClosed) { + while (isActive && !serverSocket.isClosed) { val socket = serverSocket.accept() logger.info { "Accepted connection from ${socket.remoteSocketAddress}" } readSocket(socket) @@ -43,22 +43,49 @@ class EnvelopeServer( job = null } - private fun CoroutineScope.readSocket(socket: Socket) { - val input = socket.getInputStream().asInput() - val output = socket.getOutputStream().asOutput() - format.run { - launch { - while (isActive && socket.isConnected) { +// 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 outputStream = socket.getOutputStream() + format.run { + while (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") + return@thread +// cancel("Graceful connection shutdown requested by client") + } + runBlocking { + val response = responder.respond(request) + outputStream.writePacket { + writeThis(response) + } + logger.debug { "Sent response with type ${response.type} to ${socket.remoteSocketAddress}" } } - val response = responder.respond(request) - output.writeThis(response) } } } diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/InputStreamAsInput.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/InputStreamAsInput.kt new file mode 100644 index 00000000..6dab414d --- /dev/null +++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/InputStreamAsInput.kt @@ -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) diff --git a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/tcp/EnvelopeServerTest.kt b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/tcp/EnvelopeServerTest.kt index a2642564..f00a3754 100644 --- a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/tcp/EnvelopeServerTest.kt +++ b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/tcp/EnvelopeServerTest.kt @@ -3,16 +3,28 @@ package hep.dataforge.io.tcp import hep.dataforge.context.Global import hep.dataforge.io.Envelope import hep.dataforge.io.Responder +import hep.dataforge.io.TaggedEnvelopeFormat +import hep.dataforge.io.writeBytes import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.runBlocking import org.junit.AfterClass import org.junit.BeforeClass +import org.junit.Test +import kotlin.test.assertEquals import kotlin.time.ExperimentalTime +@ExperimentalStdlibApi 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 +@ExperimentalStdlibApi class EnvelopeServerTest { companion object { @JvmStatic @@ -32,27 +44,25 @@ class EnvelopeServerTest { } -// @Test -// fun doEchoTest() { -// val client = EnvelopeClient(Global, host = "localhost", port = 7778) -// val request = Envelope.build { -// type = "test.echo" -// meta { -// "test.value" to 22 -// } -// data { -// writeDouble(22.7) -// } -// } -// val response = runBlocking { -// client.respond(request) -// } -// -// assertEquals(request.meta, response.meta) -// assertEquals(request.data, response.data) -// -// runBlocking { -// client.close() -// } -// } + @Test + fun doEchoTest() { + val client = EnvelopeClient(Global, host = "localhost", port = 7778) + val request = Envelope.build { + type = "test.echo" + meta { + "test.value" to 22 + } + data { + writeDouble(22.7) + } + } + runBlocking { + val response = client.respond(request) + + + assertEquals(request.meta, response.meta) +// assertEquals(request.data?.toBytes()?.decodeToString(), response.data?.toBytes()?.decodeToString()) + client.close() + } + } } \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt index 9e0dedf9..45a27250 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt @@ -187,11 +187,6 @@ abstract class MetaBase: Meta{ override fun equals(other: Any?): Boolean = if(other is Meta) { this.items == other.items -// val items = items -// val otherItems = other.items -// (items.keys == otherItems.keys) && items.keys.all { -// items[it] == otherItems[it] -// } } else { false } diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt index a89a44a1..571e1ee6 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt @@ -1,5 +1,7 @@ package hep.dataforge.meta +import hep.dataforge.names.Name + /** * Marker interface for classes with specifications */ @@ -61,5 +63,5 @@ fun > S.createStyle(action: C.() -> Unit): Me fun Specific.spec( spec: Specification, - key: String? = null + key: Name? = null ): MutableMorphDelegate = MutableMorphDelegate(config, key) { spec.wrap(it) } \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configDelegates.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configDelegates.kt index 6871b6e3..45b6b4d2 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configDelegates.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configDelegates.kt @@ -1,5 +1,6 @@ package hep.dataforge.meta +import hep.dataforge.names.Name import hep.dataforge.values.DoubleArrayValue import hep.dataforge.values.Null import hep.dataforge.values.Value @@ -11,126 +12,126 @@ import kotlin.jvm.JvmName /** * A property delegate that uses custom key */ -fun Configurable.value(default: Any = Null, key: String? = null): MutableValueDelegate = +fun Configurable.value(default: Any = Null, key: Name? = null): MutableValueDelegate = MutableValueDelegate(config, key, Value.of(default)) fun Configurable.value( default: T? = null, - key: String? = null, + key: Name? = null, writer: (T) -> Value = { Value.of(it) }, reader: (Value?) -> T ): ReadWriteDelegateWrapper = MutableValueDelegate(config, key, default?.let { Value.of(it) }).transform(reader = reader, writer = writer) -fun Configurable.string(default: String? = null, key: String? = null): MutableStringDelegate = +fun Configurable.string(default: String? = null, key: Name? = null): MutableStringDelegate = MutableStringDelegate(config, key, default) -fun Configurable.boolean(default: Boolean? = null, key: String? = null): MutableBooleanDelegate = +fun Configurable.boolean(default: Boolean? = null, key: Name? = null): MutableBooleanDelegate = MutableBooleanDelegate(config, key, default) -fun Configurable.number(default: Number? = null, key: String? = null): MutableNumberDelegate = +fun Configurable.number(default: Number? = null, key: Name? = null): MutableNumberDelegate = MutableNumberDelegate(config, key, default) /* Number delegates*/ -fun Configurable.int(default: Int? = null, key: String? = null) = +fun Configurable.int(default: Int? = null, key: Name? = null) = 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 -fun Configurable.long(default: Long? = null, key: String? = null) = +fun Configurable.long(default: Long? = null, key: Name? = null) = 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 -fun Configurable.float(default: Float? = null, key: String? = null) = +fun Configurable.float(default: Float? = null, key: Name? = null) = number(default, key).float @JvmName("safeString") -fun Configurable.string(default: String, key: String? = null) = +fun Configurable.string(default: String, key: Name? = null) = MutableSafeStringDelegate(config, key) { default } @JvmName("safeBoolean") -fun Configurable.boolean(default: Boolean, key: String? = null) = +fun Configurable.boolean(default: Boolean, key: Name? = null) = MutableSafeBooleanDelegate(config, key) { default } @JvmName("safeNumber") -fun Configurable.number(default: Number, key: String? = null) = +fun Configurable.number(default: Number, key: Name? = null) = MutableSafeNumberDelegate(config, key) { default } @JvmName("safeString") -fun Configurable.string(key: String? = null, default: () -> String) = +fun Configurable.string(key: Name? = null, default: () -> String) = MutableSafeStringDelegate(config, key, default) @JvmName("safeBoolean") -fun Configurable.boolean(key: String? = null, default: () -> Boolean) = +fun Configurable.boolean(key: Name? = null, default: () -> Boolean) = MutableSafeBooleanDelegate(config, key, default) @JvmName("safeNumber") -fun Configurable.number(key: String? = null, default: () -> Number) = +fun Configurable.number(key: Name? = null, default: () -> Number) = MutableSafeNumberDelegate(config, key, default) /* Safe number delegates*/ @JvmName("safeInt") -fun Configurable.int(default: Int, key: String? = null) = +fun Configurable.int(default: Int, key: Name? = null) = number(default, key).int @JvmName("safeDouble") -fun Configurable.double(default: Double, key: String? = null) = +fun Configurable.double(default: Double, key: Name? = null) = number(default, key).double @JvmName("safeLong") -fun Configurable.long(default: Long, key: String? = null) = +fun Configurable.long(default: Long, key: Name? = null) = number(default, key).long @JvmName("safeShort") -fun Configurable.short(default: Short, key: String? = null) = +fun Configurable.short(default: Short, key: Name? = null) = number(default, key).short @JvmName("safeFloat") -fun Configurable.float(default: Float, key: String? = null) = +fun Configurable.float(default: Float, key: Name? = null) = number(default, key).float /** * Enum delegate */ -inline fun > Configurable.enum(default: E, key: String? = null) = +inline fun > Configurable.enum(default: E, key: Name? = null) = MutableSafeEnumvDelegate(config, key, default) { enumValueOf(it) } /* Node delegates */ -fun Configurable.node(key: String? = null) = MutableNodeDelegate(config, key) +fun Configurable.node(key: Name? = null) = MutableNodeDelegate(config, key) -fun Configurable.spec(spec: Specification, key: String? = null) = +fun Configurable.spec(spec: Specification, key: Name? = null) = MutableMorphDelegate(config, key) { spec.wrap(it) } -fun Configurable.spec(builder: (Config) -> T, key: String? = null) = +fun Configurable.spec(builder: (Config) -> T, key: Name? = null) = MutableMorphDelegate(config, key) { specification(builder).wrap(it) } /* * Extra delegates for special cases */ -fun Configurable.stringList(key: String? = null): ReadWriteDelegateWrapper> = +fun Configurable.stringList(key: Name? = null): ReadWriteDelegateWrapper> = value(emptyList(), key) { it?.list?.map { value -> value.string } ?: emptyList() } -fun Configurable.numberList(key: String? = null): ReadWriteDelegateWrapper> = +fun Configurable.numberList(key: Name? = null): ReadWriteDelegateWrapper> = value(emptyList(), key) { it?.list?.map { value -> value.number } ?: emptyList() } /** * A special delegate for double arrays */ -fun Configurable.doubleArray(key: String? = null): ReadWriteDelegateWrapper = +fun Configurable.doubleArray(key: Name? = null): ReadWriteDelegateWrapper = value(doubleArrayOf(), key) { (it as? DoubleArrayValue)?.value ?: it?.list?.map { value -> value.number.toDouble() }?.toDoubleArray() ?: doubleArrayOf() } -fun Configurable.child(key: String? = null, converter: (Meta) -> T) = +fun Configurable.child(key: Name? = null, converter: (Meta) -> T) = MutableMorphDelegate(config, key, converter) diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt index 9d606b6f..e9c19864 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt @@ -1,5 +1,7 @@ package hep.dataforge.meta +import hep.dataforge.names.Name +import hep.dataforge.names.asName import hep.dataforge.values.Null import hep.dataforge.values.Value import hep.dataforge.values.asValue @@ -173,15 +175,15 @@ fun Metoid.child(key: String? = null, converter: (Meta) -> T) = Chi class MutableValueDelegate>( val meta: M, - private val key: String? = null, + private val key: Name? = null, private val default: Value? = null ) : ReadWriteProperty { 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?) { - val name = key ?: property.name + val name = key ?: property.name.asName() if (value == null) { meta.remove(name) } else { @@ -195,15 +197,15 @@ class MutableValueDelegate>( class MutableStringDelegate>( val meta: M, - private val key: String? = null, + private val key: Name? = null, private val default: String? = null ) : ReadWriteProperty { 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?) { - val name = key ?: property.name + val name = key ?: property.name.asName() if (value == null) { meta.remove(name) } else { @@ -214,15 +216,15 @@ class MutableStringDelegate>( class MutableBooleanDelegate>( val meta: M, - private val key: String? = null, + private val key: Name? = null, private val default: Boolean? = null ) : ReadWriteProperty { 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?) { - val name = key ?: property.name + val name = key ?: property.name.asName() if (value == null) { meta.remove(name) } else { @@ -233,15 +235,15 @@ class MutableBooleanDelegate>( class MutableNumberDelegate>( val meta: M, - private val key: String? = null, + private val key: Name? = null, private val default: Number? = null ) : ReadWriteProperty { 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?) { - val name = key ?: property.name + val name = key ?: property.name.asName() if (value == null) { meta.remove(name) } else { @@ -260,52 +262,52 @@ class MutableNumberDelegate>( class MutableSafeStringDelegate>( val meta: M, - private val key: String? = null, + private val key: Name? = null, default: () -> String ) : ReadWriteProperty { private val default: String by lazy(default) 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) { - meta.setValue(key ?: property.name, value.asValue()) + meta.setValue(key ?: property.name.asName(), value.asValue()) } } class MutableSafeBooleanDelegate>( val meta: M, - private val key: String? = null, + private val key: Name? = null, default: () -> Boolean ) : ReadWriteProperty { private val default: Boolean by lazy(default) 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) { - meta.setValue(key ?: property.name, value.asValue()) + meta.setValue(key ?: property.name.asName(), value.asValue()) } } class MutableSafeNumberDelegate>( val meta: M, - private val key: String? = null, + private val key: Name? = null, default: () -> Number ) : ReadWriteProperty { private val default: Number by lazy(default) 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) { - 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 }) @@ -317,16 +319,16 @@ class MutableSafeNumberDelegate>( class MutableSafeEnumvDelegate, E : Enum>( val meta: M, - private val key: String? = null, + private val key: Name? = null, private val default: E, private val resolver: (String) -> E ) : ReadWriteProperty { 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) { - meta.setValue(key ?: property.name, value.name.asValue()) + meta.setValue(key ?: property.name.asName(), value.name.asValue()) } } @@ -334,31 +336,31 @@ class MutableSafeEnumvDelegate, E : Enum>( class MutableNodeDelegate>( val meta: M, - private val key: String? = null + private val key: Name? = null ) : ReadWriteProperty { 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?) { - meta[key ?: property.name] = value + meta[key ?: property.name.asName()] = value } } class MutableMorphDelegate, T : Configurable>( val meta: M, - private val key: String? = null, + private val key: Name? = null, private val converter: (Meta) -> T ) : ReadWriteProperty { 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?) { if (value == null) { - meta.remove(key ?: property.name) + meta.remove(key ?: property.name.asName()) } else { - meta[key ?: property.name] = value.config + meta[key ?: property.name.asName()] = value.config } } } @@ -383,44 +385,45 @@ class ReadWriteDelegateWrapper( /** * A property delegate that uses custom key */ -fun > M.value(default: Value = Null, key: String? = null) = +fun > M.value(default: Value = Null, key: Name? = null) = MutableValueDelegate(this, key, default) -fun > M.string(default: String? = null, key: String? = null) = +fun > M.string(default: String? = null, key: Name? = null) = MutableStringDelegate(this, key, default) -fun > M.boolean(default: Boolean? = null, key: String? = null) = +fun > M.boolean(default: Boolean? = null, key: Name? = null) = MutableBooleanDelegate(this, key, default) -fun > M.number(default: Number? = null, key: String? = null) = +fun > M.number(default: Number? = null, key: Name? = null) = MutableNumberDelegate(this, key, default) -fun > M.node(key: String? = null) = MutableNodeDelegate(this, key) +fun > M.node(key: Name? = null) = + MutableNodeDelegate(this, key) @JvmName("safeString") -fun > M.string(default: String, key: String? = null) = +fun > M.string(default: String, key: Name? = null) = MutableSafeStringDelegate(this, key) { default } @JvmName("safeBoolean") -fun > M.boolean(default: Boolean, key: String? = null) = +fun > M.boolean(default: Boolean, key: Name? = null) = MutableSafeBooleanDelegate(this, key) { default } @JvmName("safeNumber") -fun > M.number(default: Number, key: String? = null) = +fun > M.number(default: Number, key: Name? = null) = MutableSafeNumberDelegate(this, key) { default } @JvmName("safeString") -fun > M.string(key: String? = null, default: () -> String) = +fun > M.string(key: Name? = null, default: () -> String) = MutableSafeStringDelegate(this, key, default) @JvmName("safeBoolean") -fun > M.boolean(key: String? = null, default: () -> Boolean) = +fun > M.boolean(key: Name? = null, default: () -> Boolean) = MutableSafeBooleanDelegate(this, key, default) @JvmName("safeNumber") -fun > M.number(key: String? = null, default: () -> Number) = +fun > M.number(key: Name? = null, default: () -> Number) = MutableSafeNumberDelegate(this, key, default) -inline fun , reified E : Enum> M.enum(default: E, key: String? = null) = +inline fun , reified E : Enum> M.enum(default: E, key: Name? = null) = MutableSafeEnumvDelegate(this, key, default) { enumValueOf(it) } \ No newline at end of file