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{
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")
}
}
}

View File

@ -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())
}

View File

@ -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 {

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>.writeBytes(obj: T): ByteArray = buildPacket { writeThis(obj) }.readBytes()
fun <T: Any> IOFormat<T>.readBytes(array: ByteArray): T = ByteReadPacket(array).readThis()
object DoubleIOFormat : IOFormat<Double> {

View File

@ -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))
}

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 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 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 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 }
// }
// }
suspend fun close() {
try {
respond(
Envelope.build {
type = EnvelopeServer.SHUTDOWN_ENVELOPE_TYPE
}
)
} catch (ex: Exception) {
logger.error { ex }
}
}
override suspend fun respond(request: Envelope): Envelope = withContext(dispatcher) {
withTimeout(timeout.toLongMilliseconds()) {
val socket = getSocket()
//val address = InetSocketAddress(host,port)
val socket = Socket(host, port)
val input = socket.getInputStream().asInput()
val output = socket.getOutputStream().asOutput()
val output = socket.getOutputStream()
format.run {
output.writeThis(request)
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@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.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) {
// 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 output = socket.getOutputStream().asOutput()
val outputStream = socket.getOutputStream()
format.run {
launch {
while (isActive && socket.isConnected) {
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)
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.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()
}
}
}

View File

@ -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
}

View File

@ -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) }

View File

@ -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)

View File

@ -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) }