Fix Scheme special meta implementation

This commit is contained in:
Alexander Nozik 2021-08-10 13:34:59 +03:00
parent e5f422f9ca
commit c1065c2885
4 changed files with 164 additions and 92 deletions

View File

@ -265,14 +265,7 @@ public operator fun <M : MutableTypedMeta<M>> MutableTypedMeta<M>.set(name: Name
private class MutableMetaImpl( private class MutableMetaImpl(
value: Value?, value: Value?,
children: Map<NameToken, Meta> = emptyMap() children: Map<NameToken, Meta> = emptyMap()
) : ObservableMutableMeta { ) : AbstractObservableMeta(), ObservableMutableMeta {
private val listeners = HashSet<MetaListener>()
override fun invalidate(name: Name) {
listeners.forEach { it.callback(this, name) }
}
override var value = value override var value = value
@Synchronized set(value) { @Synchronized set(value) {
val oldValue = field val oldValue = field
@ -289,17 +282,6 @@ private class MutableMetaImpl(
override val items: Map<NameToken, ObservableMutableMeta> get() = children override val items: Map<NameToken, ObservableMutableMeta> 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) { private fun ObservableMeta.adoptBy(parent: MutableMetaImpl, key: NameToken) {
onChange(parent) { name -> onChange(parent) { name ->
parent.invalidate(key + name) parent.invalidate(key + name)
@ -377,10 +359,6 @@ private class MutableMetaImpl(
invalidate(name) 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)
} }
/** /**

View File

@ -1,8 +1,6 @@
package space.kscience.dataforge.meta package space.kscience.dataforge.meta
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.* import space.kscience.dataforge.names.*
import space.kscience.dataforge.values.Value
import kotlin.jvm.Synchronized import kotlin.jvm.Synchronized
import kotlin.reflect.KProperty1 import kotlin.reflect.KProperty1
@ -38,7 +36,6 @@ public interface ObservableMeta : Meta {
public interface ObservableMutableMeta : ObservableMeta, MutableMeta, MutableTypedMeta<ObservableMutableMeta> { public interface ObservableMutableMeta : ObservableMeta, MutableMeta, MutableTypedMeta<ObservableMutableMeta> {
override fun getOrCreate(name: Name): ObservableMutableMeta 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()) { tailrec fun ObservableMutableMeta.find(name: Name): ObservableMutableMeta? = if (name.isEmpty()) {
this this
@ -50,10 +47,7 @@ public interface ObservableMutableMeta : ObservableMeta, MutableMeta, MutableTyp
} }
} }
private class ObservableMetaWrapper( internal abstract class AbstractObservableMeta : ObservableMeta {
val origin: MutableMeta,
) : ObservableMutableMeta, Meta by origin {
private val listeners = HashSet<MetaListener>() private val listeners = HashSet<MetaListener>()
override fun invalidate(name: Name) { override fun invalidate(name: Name) {
@ -70,58 +64,11 @@ private class ObservableMetaWrapper(
listeners.removeAll { it.owner === owner } listeners.removeAll { it.owner === owner }
} }
override val items: Map<NameToken, ObservableMetaWrapper> override fun toString(): String = Meta.toString(this)
get() = origin.items.mapValues { ObservableMetaWrapper(it.value) } override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta)
override fun hashCode(): Int = Meta.hashCode(this)
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])
}
}
}
/**
* 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]. * Use the value of the property in a [callBack].
* The callback is called once immediately after subscription to pass the initial value. * The callback is called once immediately after subscription to pass the initial value.

View File

@ -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<MetaListener>
) : ObservableMutableMeta {
override val items: Map<NameToken, ObservableMutableMeta>
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())

View File

@ -1,8 +1,10 @@
package space.kscience.dataforge.meta package space.kscience.dataforge.meta
import space.kscience.dataforge.meta.descriptors.* 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 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]. * 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 { 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( internal fun wrap(
items: MutableMeta, newMeta: MutableMeta,
preserveDefault: Boolean = false 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?) { override fun setValue(name: Name, value: Value?) {
//TODO add validation val valueDescriptor = descriptor?.get(name)
if (valueDescriptor?.validate(value) != false) {
meta.setValue(name, value) meta.setValue(name, value)
} else error("Value $value is not validated by $valueDescriptor")
} }
override fun toMeta(): Laminate = Laminate(meta, descriptor?.defaultNode) override fun toMeta(): Laminate = Laminate(meta, descriptor?.defaultNode)
private val listeners = HashSet<MetaListener>()
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<NameToken, ObservableMutableMeta>
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<out T : Scheme>(
override val descriptor: MetaDescriptor? get() = null override val descriptor: MetaDescriptor? get() = null
override fun empty(): T = builder().also { override fun empty(): T = builder().also {
it.metaDescriptor = descriptor it.descriptor = descriptor
} }
@Suppress("OVERRIDE_BY_INLINE") @Suppress("OVERRIDE_BY_INLINE")