Delegates for mutable and immutable meta
This commit is contained in:
parent
81660f3ca7
commit
f482758014
@ -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"
|
@ -2,7 +2,11 @@ package hep.dataforge.meta
|
||||
|
||||
//TODO add validator to configuration
|
||||
|
||||
/**
|
||||
* Mutable meta representing object state
|
||||
*/
|
||||
class Configuration : MutableMetaNode<Configuration>() {
|
||||
|
||||
/**
|
||||
* Attach configuration node instead of creating one
|
||||
*/
|
||||
@ -20,4 +24,21 @@ class Configuration: MutableMetaNode<Configuration>() {
|
||||
}
|
||||
|
||||
override fun empty(): Configuration = Configuration()
|
||||
|
||||
/**
|
||||
* 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
|
221
src/main/kotlin/hep/dataforge/meta/Delegates.kt
Normal file
221
src/main/kotlin/hep/dataforge/meta/Delegates.kt
Normal file
@ -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<Metoid, Value?> {
|
||||
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<Metoid, String?> {
|
||||
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<Metoid, Boolean?> {
|
||||
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<Metoid, Number?> {
|
||||
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<Metoid, String> {
|
||||
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<Metoid, Boolean> {
|
||||
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<Metoid, Number> {
|
||||
override fun getValue(thisRef: Metoid, property: KProperty<*>): Number {
|
||||
return thisRef.meta[key ?: property.name]?.number ?: default
|
||||
}
|
||||
}
|
||||
|
||||
class SafeEnumDelegate<E : Enum<E>>(private val key: String? = null, private val default: E, private val resolver: (String) -> E) : ReadOnlyProperty<Metoid, E> {
|
||||
override fun getValue(thisRef: Metoid, property: KProperty<*>): E {
|
||||
return (thisRef.meta[key ?: property.name]?.string)?.let { resolver(it) } ?: default
|
||||
}
|
||||
}
|
||||
|
||||
//Child node delegate
|
||||
|
||||
class ChildDelegate<T>(private val key: String? = null, private val converter: (Meta) -> T) : ReadOnlyProperty<Metoid, T?> {
|
||||
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 <T : Metoid> 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 <reified E : Enum<E>> 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<Configurable, Value?> {
|
||||
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<Configurable, String?> {
|
||||
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<Configurable, Boolean?> {
|
||||
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<Configurable, Number?> {
|
||||
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<Configurable, String> {
|
||||
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<Configurable, Boolean> {
|
||||
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<Configurable, Number> {
|
||||
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<E : Enum<E>>(private val key: String? = null, private val default: E, private val resolver: (String) -> E) : ReadWriteProperty<Configurable, E> {
|
||||
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<T : Configurable>(private val key: String? = null, private val converter: (Configuration) -> T) : ReadWriteProperty<Configurable, T> {
|
||||
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 <T : Configurable> Configurable.child(key: String? = null, converter: (Configuration) -> T) = ChildConfigDelegate(key, converter)
|
||||
|
||||
//fun <T : Configurable> Configurable.spec(spec: Specification<T>, key: String? = null) = ChildConfigDelegate<T>(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 <reified E : Enum<E>> Configurable.enum(default: E, key: String? = null) = SafeEnumvConfigDelegate(key, default) { enumValueOf(it) }
|
@ -86,3 +86,10 @@ class SealedMeta(meta: Meta) : MetaNode<SealedMeta>() {
|
||||
* Generate sealed node from [this]. If it is already sealed return it as is
|
||||
*/
|
||||
fun Meta.seal(): SealedMeta = this as? SealedMeta ?: SealedMeta(this)
|
||||
|
||||
/**
|
||||
* Generic meta-holder object
|
||||
*/
|
||||
interface Metoid{
|
||||
val meta: Meta
|
||||
}
|
@ -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<MetaBuilder>() {
|
||||
override fun wrap(meta: Meta): MetaBuilder = meta.builder()
|
||||
|
@ -10,7 +10,7 @@ class MetaListener(val owner: Any? = null, val action: (name: Name, oldItem: Met
|
||||
|
||||
|
||||
interface MutableMeta<M : MutableMeta<M>> : Meta {
|
||||
operator fun set(name: Name, item: MetaItem<M>)
|
||||
operator fun set(name: Name, item: MetaItem<M>?)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -69,7 +69,7 @@ abstract class MutableMetaNode<M : MutableMetaNode<M>> : MetaNode<M>(), MutableM
|
||||
*/
|
||||
protected abstract fun empty(): M
|
||||
|
||||
override operator fun set(name: Name, item: MetaItem<M>) {
|
||||
override operator fun set(name: Name, item: MetaItem<M>?) {
|
||||
when (name.length) {
|
||||
0 -> error("Can't set meta item for empty name")
|
||||
1 -> {
|
||||
@ -87,6 +87,8 @@ abstract class MutableMetaNode<M : MutableMetaNode<M>> : MetaNode<M>(), 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<Meta>) = set(name, MetaItem.MultiNodeItem(metas.map { wrap(it) }))
|
||||
|
27
src/test/kotlin/hep/dataforge/meta/MetaDelegateTest.kt
Normal file
27
src/test/kotlin/hep/dataforge/meta/MetaDelegateTest.kt
Normal file
@ -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)
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user