From f482758014c454b67554f4481da55b9c01d14b60 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 16 Sep 2018 20:04:54 +0300 Subject: [PATCH] Delegates for mutable and immutable meta --- settings.gradle | 10 + .../hep/dataforge/meta/Configuration.kt | 25 +- .../kotlin/hep/dataforge/meta/Delegates.kt | 221 ++++++++++++++++++ src/main/kotlin/hep/dataforge/meta/Meta.kt | 9 +- .../kotlin/hep/dataforge/meta/MetaBuilder.kt | 2 +- .../hep/dataforge/meta/MutableMetaNode.kt | 8 +- .../hep/dataforge/meta/MetaDelegateTest.kt | 27 +++ 7 files changed, 295 insertions(+), 7 deletions(-) create mode 100644 src/main/kotlin/hep/dataforge/meta/Delegates.kt create mode 100644 src/test/kotlin/hep/dataforge/meta/MetaDelegateTest.kt diff --git a/settings.gradle b/settings.gradle index fa5906c2..8b0f8f86 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,8 +4,18 @@ pluginManagement { if (requested.id.id == "kotlin-platform-common") { useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:${requested.version}") } + + if (requested.id.id == "kotlin-platform-jvm") { + useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:${requested.version}") + } + + if (requested.id.id == "kotlin-platform-js") { + useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:${requested.version}") + } } } } rootProject.name = 'dataforge-meta' +include ":dataforge-meta-jvm" +include ":dataforge-meta-js" \ No newline at end of file diff --git a/src/main/kotlin/hep/dataforge/meta/Configuration.kt b/src/main/kotlin/hep/dataforge/meta/Configuration.kt index b9e9ea4d..560f185d 100644 --- a/src/main/kotlin/hep/dataforge/meta/Configuration.kt +++ b/src/main/kotlin/hep/dataforge/meta/Configuration.kt @@ -2,7 +2,11 @@ package hep.dataforge.meta //TODO add validator to configuration -class Configuration: MutableMetaNode() { +/** + * Mutable meta representing object state + */ +class Configuration : MutableMetaNode() { + /** * Attach configuration node instead of creating one */ @@ -20,4 +24,21 @@ class Configuration: MutableMetaNode() { } override fun empty(): Configuration = Configuration() -} \ No newline at end of file + + /** + * Universal set method + */ + operator fun set(key: String, value: Any?) { + when (value) { + null -> remove(key) + is Meta -> set(key, value) + else -> set(key, Value.of(value)) + } + } +} + +interface Configurable { + val config: Configuration +} + +open class SimpleConfigurable(override val config:Configuration): Configurable \ No newline at end of file diff --git a/src/main/kotlin/hep/dataforge/meta/Delegates.kt b/src/main/kotlin/hep/dataforge/meta/Delegates.kt new file mode 100644 index 00000000..d16431eb --- /dev/null +++ b/src/main/kotlin/hep/dataforge/meta/Delegates.kt @@ -0,0 +1,221 @@ +package hep.dataforge.meta + +import kotlin.jvm.JvmName +import kotlin.properties.ReadOnlyProperty +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + +/* Meta delegates */ + +class ValueDelegate(private val key: String? = null, private val default: Value? = null) : ReadOnlyProperty { + override fun getValue(thisRef: Metoid, property: KProperty<*>): Value? { + return thisRef.meta[key ?: property.name]?.value ?: default + } +} + +class StringDelegate(private val key: String? = null, private val default: String? = null) : ReadOnlyProperty { + override fun getValue(thisRef: Metoid, property: KProperty<*>): String? { + return thisRef.meta[key ?: property.name]?.string ?: default + } +} + +class BooleanDelegate(private val key: String? = null, private val default: Boolean? = null) : ReadOnlyProperty { + override fun getValue(thisRef: Metoid, property: KProperty<*>): Boolean? { + return thisRef.meta[key ?: property.name]?.boolean ?: default + } +} + +class NumberDelegate(private val key: String? = null, private val default: Number? = null) : ReadOnlyProperty { + override fun getValue(thisRef: Metoid, property: KProperty<*>): Number? { + return thisRef.meta[key ?: property.name]?.number ?: default + } +} + +//Delegates with non-null values + +class SafeStringDelegate(private val key: String? = null, private val default: String) : ReadOnlyProperty { + override fun getValue(thisRef: Metoid, property: KProperty<*>): String { + return thisRef.meta[key ?: property.name]?.string ?: default + } +} + +class SafeBooleanDelegate(private val key: String? = null, private val default: Boolean) : ReadOnlyProperty { + override fun getValue(thisRef: Metoid, property: KProperty<*>): Boolean { + return thisRef.meta[key ?: property.name]?.boolean ?: default + } +} + +class SafeNumberDelegate(private val key: String? = null, private val default: Number) : ReadOnlyProperty { + override fun getValue(thisRef: Metoid, property: KProperty<*>): Number { + return thisRef.meta[key ?: property.name]?.number ?: default + } +} + +class SafeEnumDelegate>(private val key: String? = null, private val default: E, private val resolver: (String) -> E) : ReadOnlyProperty { + override fun getValue(thisRef: Metoid, property: KProperty<*>): E { + return (thisRef.meta[key ?: property.name]?.string)?.let { resolver(it) } ?: default + } +} + +//Child node delegate + +class ChildDelegate(private val key: String? = null, private val converter: (Meta) -> T) : ReadOnlyProperty { + override fun getValue(thisRef: Metoid, property: KProperty<*>): T? { + return thisRef.meta[key ?: property.name]?.node?.let { converter(it)} + } +} + +//Read-only delegates + +/** + * A property delegate that uses custom key + */ +fun Metoid.value(default: Value = Null, key: String? = null) = ValueDelegate(key, default) + +fun Metoid.string(default: String? = null, key: String? = null) = StringDelegate(key, default) + +fun Metoid.boolean(default: Boolean? = null, key: String? = null) = BooleanDelegate(key, default) + +fun Metoid.number(default: Number? = null, key: String? = null) = NumberDelegate(key, default) + +fun Metoid.child(key: String? = null) = ChildDelegate(key) { it } + +fun Metoid.child(key: String? = null, converter: (Meta) -> T) = ChildDelegate(key, converter) + +@JvmName("safeString") +fun Metoid.string(default: String, key: String? = null) = SafeStringDelegate(key, default) + +@JvmName("safeBoolean") +fun Metoid.boolean(default: Boolean, key: String? = null) = SafeBooleanDelegate(key, default) + +@JvmName("safeNumber") +fun Metoid.number(default: Number, key: String? = null) = SafeNumberDelegate(key, default) + +inline fun > Metoid.enum(default: E, key: String? = null) = SafeEnumDelegate(key, default) { enumValueOf(it) } + +/* Configuration delegates */ + +class ValueConfigDelegate(private val key: String? = null, private val default: Value? = null) : ReadWriteProperty { + override fun getValue(thisRef: Configurable, property: KProperty<*>): Value? { + return thisRef.config[key ?: property.name]?.value ?: default + } + + override fun setValue(thisRef: Configurable, property: KProperty<*>, value: Value?) { + thisRef.config[key ?: property.name] = value + } +} + +class StringConfigDelegate(private val key: String? = null, private val default: String? = null) : ReadWriteProperty { + override fun getValue(thisRef: Configurable, property: KProperty<*>): String? { + return thisRef.config[key ?: property.name]?.string ?: default + } + + override fun setValue(thisRef: Configurable, property: KProperty<*>, value: String?) { + thisRef.config[key ?: property.name] = value + } +} + +class BooleanConfigDelegate(private val key: String? = null, private val default: Boolean? = null) : ReadWriteProperty { + override fun getValue(thisRef: Configurable, property: KProperty<*>): Boolean? { + return thisRef.config[key ?: property.name]?.boolean ?: default + } + + override fun setValue(thisRef: Configurable, property: KProperty<*>, value: Boolean?) { + thisRef.config[key ?: property.name] = value + } +} + +class NumberConfigDelegate(private val key: String? = null, private val default: Number? = null) : ReadWriteProperty { + override fun getValue(thisRef: Configurable, property: KProperty<*>): Number? { + return thisRef.config[key ?: property.name]?.number ?: default + } + + override fun setValue(thisRef: Configurable, property: KProperty<*>, value: Number?) { + thisRef.config[key ?: property.name] = value + } +} + +//Delegates with non-null values + +class SafeStringConfigDelegate(private val key: String? = null, private val default: String) : ReadWriteProperty { + override fun getValue(thisRef: Configurable, property: KProperty<*>): String { + return thisRef.config[key ?: property.name]?.string ?: default + } + + override fun setValue(thisRef: Configurable, property: KProperty<*>, value: String) { + thisRef.config[key ?: property.name] = value + } +} + +class SafeBooleanConfigDelegate(private val key: String? = null, private val default: Boolean) : ReadWriteProperty { + override fun getValue(thisRef: Configurable, property: KProperty<*>): Boolean { + return thisRef.config[key ?: property.name]?.boolean ?: default + } + + override fun setValue(thisRef: Configurable, property: KProperty<*>, value: Boolean) { + thisRef.config[key ?: property.name] = value + } +} + +class SafeNumberConfigDelegate(private val key: String? = null, private val default: Number) : ReadWriteProperty { + override fun getValue(thisRef: Configurable, property: KProperty<*>): Number { + return thisRef.config[key ?: property.name]?.number ?: default + } + + override fun setValue(thisRef: Configurable, property: KProperty<*>, value: Number) { + thisRef.config[key ?: property.name] = value + } +} + +class SafeEnumvConfigDelegate>(private val key: String? = null, private val default: E, private val resolver: (String) -> E) : ReadWriteProperty { + override fun getValue(thisRef: Configurable, property: KProperty<*>): E { + return (thisRef.config[key ?: property.name]?.string)?.let { resolver(it) } ?: default + } + + override fun setValue(thisRef: Configurable, property: KProperty<*>, value: E) { + thisRef.config[key ?: property.name] = value.name + } +} + +//Child node delegate + +class ChildConfigDelegate(private val key: String? = null, private val converter: (Configuration) -> T) : ReadWriteProperty { + override fun getValue(thisRef: Configurable, property: KProperty<*>): T { + return converter(thisRef.config.get(key ?: property.name)?.node ?: Configuration()) + } + + override fun setValue(thisRef: Configurable, property: KProperty<*>, value: T) { + thisRef.config[key ?: property.name] = value.config + } + +} + +//Read-write delegates + +/** + * A property delegate that uses custom key + */ +fun Configurable.value(default: Value = Null, key: String? = null) = ValueConfigDelegate(key, default) + +fun Configurable.string(default: String? = null, key: String? = null) = StringConfigDelegate(key, default) + +fun Configurable.boolean(default: Boolean? = null, key: String? = null) = BooleanConfigDelegate(key, default) + +fun Configurable.number(default: Number? = null, key: String? = null) = NumberConfigDelegate(key, default) + +fun Configurable.child(key: String? = null) = ChildConfigDelegate(key) { SimpleConfigurable(it) } + +fun Configurable.child(key: String? = null, converter: (Configuration) -> T) = ChildConfigDelegate(key, converter) + +//fun Configurable.spec(spec: Specification, key: String? = null) = ChildConfigDelegate(key) { spec.wrap(this) } + +@JvmName("safeString") +fun Configurable.string(default: String, key: String? = null) = SafeStringConfigDelegate(key, default) + +@JvmName("safeBoolean") +fun Configurable.boolean(default: Boolean, key: String? = null) = SafeBooleanConfigDelegate(key, default) + +@JvmName("safeNumber") +fun Configurable.number(default: Number, key: String? = null) = SafeNumberConfigDelegate(key, default) + +inline fun > Configurable.enum(default: E, key: String? = null) = SafeEnumvConfigDelegate(key, default) { enumValueOf(it) } \ No newline at end of file diff --git a/src/main/kotlin/hep/dataforge/meta/Meta.kt b/src/main/kotlin/hep/dataforge/meta/Meta.kt index 0a662e0b..2409ea62 100644 --- a/src/main/kotlin/hep/dataforge/meta/Meta.kt +++ b/src/main/kotlin/hep/dataforge/meta/Meta.kt @@ -85,4 +85,11 @@ class SealedMeta(meta: Meta) : MetaNode() { /** * Generate sealed node from [this]. If it is already sealed return it as is */ -fun Meta.seal(): SealedMeta = this as? SealedMeta ?: SealedMeta(this) \ No newline at end of file +fun Meta.seal(): SealedMeta = this as? SealedMeta ?: SealedMeta(this) + +/** + * Generic meta-holder object + */ +interface Metoid{ + val meta: Meta +} \ No newline at end of file diff --git a/src/main/kotlin/hep/dataforge/meta/MetaBuilder.kt b/src/main/kotlin/hep/dataforge/meta/MetaBuilder.kt index c4aa7592..f6ef7bca 100644 --- a/src/main/kotlin/hep/dataforge/meta/MetaBuilder.kt +++ b/src/main/kotlin/hep/dataforge/meta/MetaBuilder.kt @@ -1,7 +1,7 @@ package hep.dataforge.meta /** - * DSL builder for meta + * DSL builder for meta. Is not intended to store mutable state */ class MetaBuilder : MutableMetaNode() { override fun wrap(meta: Meta): MetaBuilder = meta.builder() diff --git a/src/main/kotlin/hep/dataforge/meta/MutableMetaNode.kt b/src/main/kotlin/hep/dataforge/meta/MutableMetaNode.kt index c74d54dc..b05351a5 100644 --- a/src/main/kotlin/hep/dataforge/meta/MutableMetaNode.kt +++ b/src/main/kotlin/hep/dataforge/meta/MutableMetaNode.kt @@ -9,8 +9,8 @@ class MetaListener(val owner: Any? = null, val action: (name: Name, oldItem: Met } -interface MutableMeta>: Meta{ - operator fun set(name: Name, item: MetaItem) +interface MutableMeta> : Meta { + operator fun set(name: Name, item: MetaItem?) } /** @@ -69,7 +69,7 @@ abstract class MutableMetaNode> : MetaNode(), MutableM */ protected abstract fun empty(): M - override operator fun set(name: Name, item: MetaItem) { + override operator fun set(name: Name, item: MetaItem?) { when (name.length) { 0 -> error("Can't set meta item for empty name") 1 -> { @@ -87,6 +87,8 @@ abstract class MutableMetaNode> : MetaNode(), MutableM } } + fun remove(name: String) = set(name.toName(), null) + operator fun set(name: Name, value: Value) = set(name, MetaItem.ValueItem(value)) operator fun set(name: Name, meta: Meta) = set(name, MetaItem.SingleNodeItem(wrap(meta))) operator fun set(name: Name, metas: List) = set(name, MetaItem.MultiNodeItem(metas.map { wrap(it) })) diff --git a/src/test/kotlin/hep/dataforge/meta/MetaDelegateTest.kt b/src/test/kotlin/hep/dataforge/meta/MetaDelegateTest.kt new file mode 100644 index 00000000..0a4495a4 --- /dev/null +++ b/src/test/kotlin/hep/dataforge/meta/MetaDelegateTest.kt @@ -0,0 +1,27 @@ +package hep.dataforge.meta + +import kotlin.test.Test +import kotlin.test.assertEquals + + +class MetaDelegateTest { + enum class TestEnum { + YES, + NO + } + + @Test + fun delegateTest() { + val testObject = object : SimpleConfigurable(Configuration()) { + var myValue by string() + var safeValue by number(2.2) + var enumValue by enum(TestEnum.YES) + } + testObject.config["myValue"] = "theString" + testObject.enumValue = TestEnum.NO + assertEquals("theString", testObject.myValue) + assertEquals(TestEnum.NO, testObject.enumValue) + assertEquals(2.2, testObject.safeValue) + } + +} \ No newline at end of file