Envelope client-sever EAP
This commit is contained in:
parent
10b8385324
commit
c44e004495
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,8 +39,8 @@ interface RandomAccessBinary : Binary {
|
||||
override fun <R> 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 <R> 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 <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
|
||||
*/
|
||||
@ -87,13 +78,9 @@ fun <T : Any> Binary.readWith(format: IOFormat<T>): T = format.run {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
fun <T : Any> IOFormat<T>.writeBinary(obj: T): Binary {
|
||||
val packet = buildPacket {
|
||||
writeThis(this@writeWith)
|
||||
writeThis(obj)
|
||||
}
|
||||
return@run ArrayBinary(packet.readBytes())
|
||||
return ArrayBinary(packet.readBytes())
|
||||
}
|
@ -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 {
|
||||
|
@ -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>.writeBytes(obj: T): ByteArray = buildPacket { writeThis(obj) }.readBytes()
|
||||
fun <T: Any> IOFormat<T>.readBytes(array: ByteArray): T = ByteReadPacket(array).readThis()
|
||||
|
||||
|
||||
object DoubleIOFormat : IOFormat<Double> {
|
||||
|
@ -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))
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package hep.dataforge.meta
|
||||
|
||||
import hep.dataforge.names.Name
|
||||
|
||||
/**
|
||||
* 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(
|
||||
spec: Specification<C>,
|
||||
key: String? = null
|
||||
key: Name? = null
|
||||
): MutableMorphDelegate<Config, C> = MutableMorphDelegate(config, key) { spec.wrap(it) }
|
@ -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<Config> =
|
||||
fun Configurable.value(default: Any = Null, key: Name? = null): MutableValueDelegate<Config> =
|
||||
MutableValueDelegate(config, key, Value.of(default))
|
||||
|
||||
fun <T> Configurable.value(
|
||||
default: T? = null,
|
||||
key: String? = null,
|
||||
key: Name? = null,
|
||||
writer: (T) -> Value = { Value.of(it) },
|
||||
reader: (Value?) -> T
|
||||
): ReadWriteDelegateWrapper<Value?, T> =
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
/* 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 <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) }
|
||||
|
||||
/* 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) }
|
||||
|
||||
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) }
|
||||
|
||||
/*
|
||||
* 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() }
|
||||
|
||||
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() }
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
(it as? DoubleArrayValue)?.value
|
||||
?: it?.list?.map { value -> value.number.toDouble() }?.toDoubleArray()
|
||||
?: 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)
|
||||
|
@ -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 <T : Metoid> Metoid.child(key: String? = null, converter: (Meta) -> T) = Chi
|
||||
|
||||
class MutableValueDelegate<M : MutableMeta<M>>(
|
||||
val meta: M,
|
||||
private val key: String? = null,
|
||||
private val key: Name? = null,
|
||||
private val default: Value? = null
|
||||
) : ReadWriteProperty<Any?, 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?) {
|
||||
val name = key ?: property.name
|
||||
val name = key ?: property.name.asName()
|
||||
if (value == null) {
|
||||
meta.remove(name)
|
||||
} else {
|
||||
@ -195,15 +197,15 @@ class MutableValueDelegate<M : MutableMeta<M>>(
|
||||
|
||||
class MutableStringDelegate<M : MutableMeta<M>>(
|
||||
val meta: M,
|
||||
private val key: String? = null,
|
||||
private val key: Name? = null,
|
||||
private val default: String? = null
|
||||
) : ReadWriteProperty<Any?, 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?) {
|
||||
val name = key ?: property.name
|
||||
val name = key ?: property.name.asName()
|
||||
if (value == null) {
|
||||
meta.remove(name)
|
||||
} else {
|
||||
@ -214,15 +216,15 @@ class MutableStringDelegate<M : MutableMeta<M>>(
|
||||
|
||||
class MutableBooleanDelegate<M : MutableMeta<M>>(
|
||||
val meta: M,
|
||||
private val key: String? = null,
|
||||
private val key: Name? = null,
|
||||
private val default: Boolean? = null
|
||||
) : ReadWriteProperty<Any?, 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?) {
|
||||
val name = key ?: property.name
|
||||
val name = key ?: property.name.asName()
|
||||
if (value == null) {
|
||||
meta.remove(name)
|
||||
} else {
|
||||
@ -233,15 +235,15 @@ class MutableBooleanDelegate<M : MutableMeta<M>>(
|
||||
|
||||
class MutableNumberDelegate<M : MutableMeta<M>>(
|
||||
val meta: M,
|
||||
private val key: String? = null,
|
||||
private val key: Name? = null,
|
||||
private val default: Number? = null
|
||||
) : ReadWriteProperty<Any?, 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?) {
|
||||
val name = key ?: property.name
|
||||
val name = key ?: property.name.asName()
|
||||
if (value == null) {
|
||||
meta.remove(name)
|
||||
} else {
|
||||
@ -260,52 +262,52 @@ class MutableNumberDelegate<M : MutableMeta<M>>(
|
||||
|
||||
class MutableSafeStringDelegate<M : MutableMeta<M>>(
|
||||
val meta: M,
|
||||
private val key: String? = null,
|
||||
private val key: Name? = null,
|
||||
default: () -> String
|
||||
) : ReadWriteProperty<Any?, String> {
|
||||
|
||||
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<M : MutableMeta<M>>(
|
||||
val meta: M,
|
||||
private val key: String? = null,
|
||||
private val key: Name? = null,
|
||||
default: () -> Boolean
|
||||
) : ReadWriteProperty<Any?, Boolean> {
|
||||
|
||||
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<M : MutableMeta<M>>(
|
||||
val meta: M,
|
||||
private val key: String? = null,
|
||||
private val key: Name? = null,
|
||||
default: () -> Number
|
||||
) : ReadWriteProperty<Any?, Number> {
|
||||
|
||||
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<M : MutableMeta<M>>(
|
||||
|
||||
class MutableSafeEnumvDelegate<M : MutableMeta<M>, E : Enum<E>>(
|
||||
val meta: M,
|
||||
private val key: String? = null,
|
||||
private val key: Name? = null,
|
||||
private val default: E,
|
||||
private val resolver: (String) -> E
|
||||
) : ReadWriteProperty<Any?, 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) {
|
||||
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>>(
|
||||
val meta: M,
|
||||
private val key: String? = null
|
||||
private val key: Name? = null
|
||||
) : ReadWriteProperty<Any?, 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?) {
|
||||
meta[key ?: property.name] = value
|
||||
meta[key ?: property.name.asName()] = value
|
||||
}
|
||||
}
|
||||
|
||||
class MutableMorphDelegate<M : MutableMeta<M>, T : Configurable>(
|
||||
val meta: M,
|
||||
private val key: String? = null,
|
||||
private val key: Name? = null,
|
||||
private val converter: (Meta) -> T
|
||||
) : ReadWriteProperty<Any?, 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?) {
|
||||
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<T, R>(
|
||||
/**
|
||||
* 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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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")
|
||||
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 }
|
||||
|
||||
@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 }
|
||||
|
||||
@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 }
|
||||
|
||||
@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)
|
||||
|
||||
@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)
|
||||
|
||||
@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)
|
||||
|
||||
|
||||
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) }
|
Loading…
Reference in New Issue
Block a user