From c1065c28856b9a414ad87539135a5de478cc4153 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 10 Aug 2021 13:34:59 +0300 Subject: [PATCH] Fix Scheme special meta implementation --- .../kscience/dataforge/meta/MutableMeta.kt | 24 +----- .../kscience/dataforge/meta/ObservableMeta.kt | 65 ++------------ .../dataforge/meta/ObservableMetaWrapper.kt | 82 ++++++++++++++++++ .../space/kscience/dataforge/meta/Scheme.kt | 85 ++++++++++++++++--- 4 files changed, 164 insertions(+), 92 deletions(-) create mode 100644 dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ObservableMetaWrapper.kt diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MutableMeta.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MutableMeta.kt index f626448f..a674aaa1 100644 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MutableMeta.kt +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MutableMeta.kt @@ -265,14 +265,7 @@ public operator fun > MutableTypedMeta.set(name: Name private class MutableMetaImpl( value: Value?, children: Map = emptyMap() -) : ObservableMutableMeta { - - private val listeners = HashSet() - - override fun invalidate(name: Name) { - listeners.forEach { it.callback(this, name) } - } - +) : AbstractObservableMeta(), ObservableMutableMeta { override var value = value @Synchronized set(value) { val oldValue = field @@ -289,17 +282,6 @@ private class MutableMetaImpl( override val items: Map get() = children - - @Synchronized - override fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit) { - listeners.add(MetaListener(owner, callback)) - } - - @Synchronized - override fun removeListener(owner: Any?) { - listeners.removeAll { it.owner === owner } - } - private fun ObservableMeta.adoptBy(parent: MutableMetaImpl, key: NameToken) { onChange(parent) { name -> parent.invalidate(key + name) @@ -377,10 +359,6 @@ private class MutableMetaImpl( invalidate(name) } } - - override fun toString(): String = Meta.toString(this) - override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta) - override fun hashCode(): Int = Meta.hashCode(this) } /** diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ObservableMeta.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ObservableMeta.kt index c5f6b4d7..73ec03b6 100644 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ObservableMeta.kt +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ObservableMeta.kt @@ -1,8 +1,6 @@ package space.kscience.dataforge.meta -import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.names.* -import space.kscience.dataforge.values.Value import kotlin.jvm.Synchronized import kotlin.reflect.KProperty1 @@ -35,11 +33,10 @@ public interface ObservableMeta : Meta { /** * A [Meta] which is both observable and mutable */ -public interface ObservableMutableMeta : ObservableMeta, MutableMeta, MutableTypedMeta{ +public interface ObservableMutableMeta : ObservableMeta, MutableMeta, MutableTypedMeta { override fun getOrCreate(name: Name): ObservableMutableMeta - - override fun getMeta(name: Name): ObservableMutableMeta?{ + override fun getMeta(name: Name): ObservableMutableMeta? { tailrec fun ObservableMutableMeta.find(name: Name): ObservableMutableMeta? = if (name.isEmpty()) { this } else { @@ -50,10 +47,7 @@ public interface ObservableMutableMeta : ObservableMeta, MutableMeta, MutableTyp } } -private class ObservableMetaWrapper( - val origin: MutableMeta, -) : ObservableMutableMeta, Meta by origin { - +internal abstract class AbstractObservableMeta : ObservableMeta { private val listeners = HashSet() override fun invalidate(name: Name) { @@ -70,58 +64,11 @@ private class ObservableMetaWrapper( listeners.removeAll { it.owner === owner } } - override val items: Map - get() = origin.items.mapValues { ObservableMetaWrapper(it.value) } - - override fun getMeta(name: Name): ObservableMetaWrapper? = origin.getMeta(name)?.let{ObservableMetaWrapper(it)} - - override var value: Value? - get() = origin.value - set(value) { - origin.value = value - invalidate(Name.EMPTY) - } - - override fun getOrCreate(name: Name): ObservableMutableMeta = - get(name) ?: ObservableMetaWrapper(origin.getOrCreate(name)) - - override fun setMeta(name: Name, node: Meta?) { - val oldMeta = get(name) - origin.setMeta(name, node) - // - // if meta is observable propagate changes from it - if (node is ObservableMeta) { - - node.onChange(this) { changeName -> - setMeta(name + changeName, node[changeName]) - } - } - if (oldMeta != node) { - invalidate(name) - } - } - - override fun toMeta(): Meta { - return origin.toMeta() - } - - @DFExperimental - override fun attach(name: Name, node: ObservableMutableMeta) { - set(name, node) - node.onChange(this) { changeName -> - setMeta(name + changeName, node[changeName]) - } - } + override fun toString(): String = Meta.toString(this) + override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta) + override fun hashCode(): Int = Meta.hashCode(this) } -/** - * Cast this [MutableMeta] to [ObservableMutableMeta] or create an observable wrapper. Only changes made to the result - * are guaranteed to be observed. - */ -public fun MutableMeta.asObservable(): ObservableMutableMeta = - (this as? ObservableMutableMeta) ?: ObservableMetaWrapper(this) - - /** * Use the value of the property in a [callBack]. * The callback is called once immediately after subscription to pass the initial value. diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ObservableMetaWrapper.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ObservableMetaWrapper.kt new file mode 100644 index 00000000..53e427ef --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ObservableMetaWrapper.kt @@ -0,0 +1,82 @@ +package space.kscience.dataforge.meta + +import space.kscience.dataforge.misc.DFExperimental +import space.kscience.dataforge.names.* +import space.kscience.dataforge.values.Value +import kotlin.jvm.Synchronized + +/** + * A class that takes [MutableMeta] provider and adds obsevability on top of that + */ +private class ObservableMetaWrapper( + val root: MutableMeta, + val absoluteName: Name, + val listeners: MutableSet +) : ObservableMutableMeta { + override val items: Map + get() = root.items.mapValues { + ObservableMetaWrapper(root, absoluteName + it.key, listeners) + } + + override fun getMeta(name: Name): ObservableMutableMeta? = + root.getMeta(name)?.let { ObservableMetaWrapper(root, this.absoluteName + name, listeners) } + + @Synchronized + override fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit) { + listeners.add( + MetaListener(Pair(owner, absoluteName)) { name -> + if (name.startsWith(absoluteName)) { + (this[absoluteName] ?: Meta.EMPTY).callback(name.removeHeadOrNull(absoluteName)!!) + } + } + ) + } + + override fun removeListener(owner: Any?) { + listeners.removeAll { it.owner === Pair(owner, absoluteName) } + } + + override fun invalidate(name: Name) { + listeners.forEach { it.callback(this, name) } + } + + override var value: Value? + get() = root.value + set(value) { + root.value = value + invalidate(Name.EMPTY) + } + + override fun getOrCreate(name: Name): ObservableMutableMeta = + ObservableMetaWrapper(root, this.absoluteName + name, listeners) + + override fun setMeta(name: Name, node: Meta?) { + val oldMeta = get(name) + root.setMeta(absoluteName + name, node) + if (oldMeta != node) { + invalidate(name) + } + } + + override fun toMeta(): Meta = root[absoluteName]?.toMeta() ?: Meta.EMPTY + + override fun toString(): String = Meta.toString(this) + override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta) + override fun hashCode(): Int = Meta.hashCode(this) + + @DFExperimental + override fun attach(name: Name, node: ObservableMutableMeta) { + set(name, node) + node.onChange(this) { changeName -> + setMeta(name + changeName, node[changeName]) + } + } +} + + +/** + * Cast this [MutableMeta] to [ObservableMutableMeta] or create an observable wrapper. Only changes made to the result + * are guaranteed to be observed. + */ +public fun MutableMeta.asObservable(): ObservableMutableMeta = + (this as? ObservableMutableMeta) ?: ObservableMetaWrapper(this, Name.EMPTY, hashSetOf()) diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/Scheme.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/Scheme.kt index f7abc1f5..0a5ce63c 100644 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/Scheme.kt +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/Scheme.kt @@ -1,8 +1,10 @@ package space.kscience.dataforge.meta import space.kscience.dataforge.meta.descriptors.* -import space.kscience.dataforge.names.Name +import space.kscience.dataforge.misc.DFExperimental +import space.kscience.dataforge.names.* import space.kscience.dataforge.values.Value +import kotlin.jvm.Synchronized /** * A base for delegate-based or descriptor-based scheme. [Scheme] has an empty constructor to simplify usage from [Specification]. @@ -10,19 +12,23 @@ import space.kscience.dataforge.values.Value */ public open class Scheme : Described, MetaRepr, MutableMetaProvider, Configurable { - private var _meta = MutableMeta() + private var targetMeta: MutableMeta = MutableMeta() - final override val meta: ObservableMutableMeta get() = _meta + private var defaultMeta: Meta? = null - internal var metaDescriptor: MetaDescriptor? = null + final override val meta: ObservableMutableMeta = SchemeMeta(Name.EMPTY) - final override val descriptor: MetaDescriptor? get() = metaDescriptor + final override var descriptor: MetaDescriptor? = null + internal set internal fun wrap( - items: MutableMeta, + newMeta: MutableMeta, preserveDefault: Boolean = false ) { - _meta = (if (preserveDefault) items.withDefault(meta.seal()) else items).asObservable() + if(preserveDefault){ + defaultMeta = targetMeta.seal() + } + targetMeta = newMeta } /** @@ -44,11 +50,70 @@ public open class Scheme : Described, MetaRepr, MutableMetaProvider, Configurabl } override fun setValue(name: Name, value: Value?) { - //TODO add validation - meta.setValue(name,value) + val valueDescriptor = descriptor?.get(name) + if (valueDescriptor?.validate(value) != false) { + meta.setValue(name, value) + } else error("Value $value is not validated by $valueDescriptor") } override fun toMeta(): Laminate = Laminate(meta, descriptor?.defaultNode) + + private val listeners = HashSet() + + private inner class SchemeMeta(val pathName: Name) : ObservableMutableMeta { + override var value: Value? + get() = targetMeta[pathName]?.value ?: defaultMeta?.get(pathName)?.value + set(value) { + val oldValue = targetMeta[pathName]?.value + targetMeta[pathName] = value + if (oldValue != value) { + invalidate(pathName) + } + } + + override val items: Map + get() { + val targetKeys = targetMeta[pathName]?.items?.keys ?: emptySet() + val defaultKeys = defaultMeta?.get(pathName)?.items?.keys ?: emptySet() + return (targetKeys + defaultKeys).associateWith { SchemeMeta(pathName + it) } + } + + override fun invalidate(name: Name) { + listeners.forEach { it.callback(this@Scheme.meta, pathName + name) } + } + + @Synchronized + override fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit) { + listeners.add(MetaListener(owner) { changedeName -> + if (changedeName.startsWith(pathName)) { + this@Scheme.meta.callback(changedeName.removeHeadOrNull(pathName)!!) + } + }) + } + + @Synchronized + override fun removeListener(owner: Any?) { + listeners.removeAll { it.owner === owner } + } + + override fun toString(): String = Meta.toString(this) + override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta) + override fun hashCode(): Int = Meta.hashCode(this) + + override fun setMeta(name: Name, node: Meta?) { + targetMeta.setMeta(name, node) + invalidate(name) + } + + override fun getOrCreate(name: Name): ObservableMutableMeta = SchemeMeta(pathName + name) + + @DFExperimental + override fun attach(name: Name, node: ObservableMutableMeta) { + TODO("Not yet implemented") + } + + + } } /** @@ -83,7 +148,7 @@ public open class SchemeSpec( override val descriptor: MetaDescriptor? get() = null override fun empty(): T = builder().also { - it.metaDescriptor = descriptor + it.descriptor = descriptor } @Suppress("OVERRIDE_BY_INLINE")