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") {
|
if (requested.id.id == "kotlin-platform-common") {
|
||||||
useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:${requested.version}")
|
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'
|
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
|
//TODO add validator to configuration
|
||||||
|
|
||||||
class Configuration: MutableMetaNode<Configuration>() {
|
/**
|
||||||
|
* Mutable meta representing object state
|
||||||
|
*/
|
||||||
|
class Configuration : MutableMetaNode<Configuration>() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attach configuration node instead of creating one
|
* Attach configuration node instead of creating one
|
||||||
*/
|
*/
|
||||||
@ -20,4 +24,21 @@ class Configuration: MutableMetaNode<Configuration>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun empty(): Configuration = 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) }
|
@ -85,4 +85,11 @@ class SealedMeta(meta: Meta) : MetaNode<SealedMeta>() {
|
|||||||
/**
|
/**
|
||||||
* Generate sealed node from [this]. If it is already sealed return it as is
|
* Generate sealed node from [this]. If it is already sealed return it as is
|
||||||
*/
|
*/
|
||||||
fun Meta.seal(): SealedMeta = this as? SealedMeta ?: SealedMeta(this)
|
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
|
package hep.dataforge.meta
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DSL builder for meta
|
* DSL builder for meta. Is not intended to store mutable state
|
||||||
*/
|
*/
|
||||||
class MetaBuilder : MutableMetaNode<MetaBuilder>() {
|
class MetaBuilder : MutableMetaNode<MetaBuilder>() {
|
||||||
override fun wrap(meta: Meta): MetaBuilder = meta.builder()
|
override fun wrap(meta: Meta): MetaBuilder = meta.builder()
|
||||||
|
@ -9,8 +9,8 @@ class MetaListener(val owner: Any? = null, val action: (name: Name, oldItem: Met
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
interface MutableMeta<M : MutableMeta<M>>: Meta{
|
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
|
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) {
|
when (name.length) {
|
||||||
0 -> error("Can't set meta item for empty name")
|
0 -> error("Can't set meta item for empty name")
|
||||||
1 -> {
|
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, 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, meta: Meta) = set(name, MetaItem.SingleNodeItem(wrap(meta)))
|
||||||
operator fun set(name: Name, metas: List<Meta>) = set(name, MetaItem.MultiNodeItem(metas.map { wrap(it) }))
|
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