Delegates for mutable and immutable meta

This commit is contained in:
Alexander Nozik 2018-09-16 20:04:54 +03:00
parent 81660f3ca7
commit f482758014
7 changed files with 295 additions and 7 deletions

View File

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

View File

@ -2,7 +2,11 @@ package hep.dataforge.meta
//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
*/
@ -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

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

View File

@ -85,4 +85,11 @@ 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)
fun Meta.seal(): SealedMeta = this as? SealedMeta ?: SealedMeta(this)
/**
* Generic meta-holder object
*/
interface Metoid{
val meta: Meta
}

View File

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

View File

@ -9,8 +9,8 @@ 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>)
interface MutableMeta<M : MutableMeta<M>> : Meta {
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) }))

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