Skip to content

Commit

Permalink
Delegates for mutable and immutable meta
Browse files Browse the repository at this point in the history
  • Loading branch information
altavir committed Sep 16, 2018
1 parent 81660f3 commit f482758
Show file tree
Hide file tree
Showing 7 changed files with 295 additions and 7 deletions.
10 changes: 10 additions & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"
25 changes: 23 additions & 2 deletions src/main/kotlin/hep/dataforge/meta/Configuration.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand All @@ -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 changes: 221 additions & 0 deletions src/main/kotlin/hep/dataforge/meta/Delegates.kt
Original file line number Diff line number Diff line change
@@ -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) }
9 changes: 8 additions & 1 deletion src/main/kotlin/hep/dataforge/meta/Meta.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
2 changes: 1 addition & 1 deletion src/main/kotlin/hep/dataforge/meta/MetaBuilder.kt
Original file line number Diff line number Diff line change
@@ -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()
Expand Down
8 changes: 5 additions & 3 deletions src/main/kotlin/hep/dataforge/meta/MutableMetaNode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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>?)
}

/**
Expand Down Expand Up @@ -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 -> {
Expand All @@ -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) }))
Expand Down
27 changes: 27 additions & 0 deletions src/test/kotlin/hep/dataforge/meta/MetaDelegateTest.kt
Original file line number Diff line number Diff line change
@@ -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)
}

}

0 comments on commit f482758

Please sign in to comment.