From 7aec2f35473e82e6138a89cfc1801ded9be0f699 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 1 Aug 2021 14:28:11 +0300 Subject: [PATCH] Cleanup delegates. Fix a lot of bugs --- .../kscience/dataforge/context/Context.kt | 2 +- .../space/kscience/dataforge/data/DataSet.kt | 1 + .../kscience/dataforge/io/EnvelopeParts.kt | 2 +- .../space/kscience/dataforge/meta/Meta.kt | 16 +- .../kscience/dataforge/meta/MetaDelegate.kt | 102 ++++------- .../kscience/dataforge/meta/MutableMeta.kt | 167 +++++++++-------- .../dataforge/meta/MutableMetaDelegate.kt | 171 ++++++++---------- .../kscience/dataforge/meta/ObservableMeta.kt | 22 +-- .../space/kscience/dataforge/meta/Scheme.kt | 8 +- .../kscience/dataforge/meta/SealedMeta.kt | 9 +- .../space/kscience/dataforge/meta/mapMeta.kt | 4 +- .../space/kscience/dataforge/values/Value.kt | 6 +- .../dataforge/values/valueExtensions.kt | 7 + .../dataforge/meta/MetaDelegateTest.kt | 3 +- .../space/kscience/dataforge/meta/MetaTest.kt | 2 +- 15 files changed, 251 insertions(+), 271 deletions(-) diff --git a/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/Context.kt b/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/Context.kt index 93e203ac..97efc76a 100644 --- a/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/Context.kt +++ b/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/Context.kt @@ -96,7 +96,7 @@ public open class Context internal constructor( override fun toMeta(): Meta = Meta { "parent" to parent?.name properties.layers.firstOrNull()?.let { set("properties", it) } - "plugins" put plugins.map { it.toMeta() } + "plugins" putIndexed plugins.map { it.toMeta() } } public companion object { diff --git a/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/data/DataSet.kt b/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/data/DataSet.kt index e5ece3ee..305f50f8 100644 --- a/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/data/DataSet.kt +++ b/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/data/DataSet.kt @@ -4,6 +4,7 @@ import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import space.kscience.dataforge.data.Data.Companion.TYPE_OF_NOTHING import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.meta.set import space.kscience.dataforge.names.* import kotlin.reflect.KType diff --git a/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/EnvelopeParts.kt b/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/EnvelopeParts.kt index 2d6376e3..641247c3 100644 --- a/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/EnvelopeParts.kt +++ b/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/EnvelopeParts.kt @@ -14,7 +14,7 @@ import space.kscience.dataforge.names.plus private class PartDescriptor : Scheme() { var offset by int(0) var size by int(0) - var partMeta by item("meta".asName()) + var partMeta by node("meta".asName()) companion object : SchemeSpec(::PartDescriptor) { val MULTIPART_KEY = ENVELOPE_NODE_KEY + "multipart" diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/Meta.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/Meta.kt index 75c290cf..45723682 100644 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/Meta.kt +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/Meta.kt @@ -34,7 +34,15 @@ public interface Meta : MetaRepr, MetaProvider { public val value: Value? public val items: Map - override fun getMeta(name: Name): Meta? = find(name) + override fun getMeta(name: Name): Meta?{ + tailrec fun Meta.find(name: Name): Meta? = if (name.isEmpty()) { + this + } else { + items[name.firstOrNull()!!]?.find(name.cutFirst()) + } + + return find(name) + } override fun toMeta(): Meta = this @@ -75,12 +83,6 @@ public interface Meta : MetaRepr, MetaProvider { public fun toString(meta: Meta): String = json.encodeToString(MetaSerializer, meta) public val EMPTY: Meta = SealedMeta(null, emptyMap()) - - private tailrec fun Meta.find(name: Name): Meta? = if (name.isEmpty()) { - this - } else { - items[name.firstOrNull()!!]?.find(name.cutFirst()) - } } } diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MetaDelegate.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MetaDelegate.kt index b0905e88..92e0c3d4 100644 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MetaDelegate.kt +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MetaDelegate.kt @@ -3,109 +3,83 @@ package space.kscience.dataforge.meta import space.kscience.dataforge.meta.transformations.MetaConverter import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.asName -import space.kscience.dataforge.values.Value +import space.kscience.dataforge.values.* import kotlin.properties.ReadOnlyProperty /* Meta delegates */ -public typealias MetaDelegate = ReadOnlyProperty - -public fun MetaProvider.item(key: Name? = null): MetaDelegate = ReadOnlyProperty { _, property -> +public fun MetaProvider.node(key: Name? = null): ReadOnlyProperty = ReadOnlyProperty { _, property -> getMeta(key ?: property.name.asName()) } -//TODO add caching for sealed nodes - - -/** - * Apply a converter to this delegate creating a delegate with a custom type - */ -public fun MetaDelegate.convert( - converter: MetaConverter, -): ReadOnlyProperty = ReadOnlyProperty { thisRef, property -> - this@convert.getValue(thisRef, property)?.let(converter::metaToObject) +public fun MetaProvider.node( + key: Name? = null, + converter: MetaConverter +): ReadOnlyProperty = ReadOnlyProperty { _, property -> + getMeta(key ?: property.name.asName())?.let { converter.metaToObject(it) } } -/* - * - */ -public fun MetaDelegate.convert( - converter: MetaConverter, - default: () -> R, -): ReadOnlyProperty = ReadOnlyProperty { thisRef, property -> - this@convert.getValue(thisRef, property)?.let(converter::metaToObject) ?: default() -} - -/** - * A converter with a custom reader transformation - */ -public fun MetaDelegate.convert( - reader: (Meta?) -> R, -): ReadOnlyProperty = ReadOnlyProperty { thisRef, property -> - this@convert.getValue(thisRef, property).let(reader) -} - -/* Read-only delegates for [Meta] */ - /** * A property delegate that uses custom key */ -public fun MetaProvider.value(key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.value) +public fun MetaProvider.value(key: Name? = null): ReadOnlyProperty = ReadOnlyProperty { _, property -> + getMeta(key ?: property.name.asName())?.value +} -public fun MetaProvider.string(key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.string) +public fun MetaProvider.value( + key: Name? = null, + reader: (Value?) -> R +): ReadOnlyProperty = ReadOnlyProperty { _, property -> + reader(getMeta(key ?: property.name.asName())?.value) +} -public fun MetaProvider.boolean(key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.boolean) +//TODO add caching for sealed nodes -public fun MetaProvider.number(key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.number) +/* Read-only delegates for [Meta] */ -public fun MetaProvider.double(key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.double) +public fun MetaProvider.string(key: Name? = null): ReadOnlyProperty = value(key) { it?.string } -public fun MetaProvider.float(key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.float) +public fun MetaProvider.boolean(key: Name? = null): ReadOnlyProperty = value(key) { it?.boolean } -public fun MetaProvider.int(key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.int) +public fun MetaProvider.number(key: Name? = null): ReadOnlyProperty = value(key) { it?.numberOrNull } -public fun MetaProvider.long(key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.long) +public fun MetaProvider.double(key: Name? = null): ReadOnlyProperty = value(key) { it?.double } -public fun MetaProvider.node(key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.meta) +public fun MetaProvider.float(key: Name? = null): ReadOnlyProperty = value(key) { it?.float } + +public fun MetaProvider.int(key: Name? = null): ReadOnlyProperty = value(key) { it?.int } + +public fun MetaProvider.long(key: Name? = null): ReadOnlyProperty = value(key) { it?.long } public fun MetaProvider.string(default: String, key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.string) { default } + value(key) { it?.string ?: default } public fun MetaProvider.boolean(default: Boolean, key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.boolean) { default } + value(key) { it?.boolean ?: default } public fun MetaProvider.number(default: Number, key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.number) { default } + value(key) { it?.numberOrNull ?: default } public fun MetaProvider.double(default: Double, key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.double) { default } + value(key) { it?.double ?: default } public fun MetaProvider.float(default: Float, key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.float) { default } + value(key) { it?.float ?: default } public fun MetaProvider.int(default: Int, key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.int) { default } + value(key) { it?.int ?: default } public fun MetaProvider.long(default: Long, key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.long) { default } + value(key) { it?.long ?: default } public inline fun > MetaProvider.enum(default: E, key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.enum()) { default } + value(key) { it?.enum() ?: default } public fun MetaProvider.string(key: Name? = null, default: () -> String): ReadOnlyProperty = - item(key).convert(MetaConverter.string, default) + value(key) { it?.string ?: default() } public fun MetaProvider.boolean(key: Name? = null, default: () -> Boolean): ReadOnlyProperty = - item(key).convert(MetaConverter.boolean, default) + value(key) { it?.boolean ?: default() } public fun MetaProvider.number(key: Name? = null, default: () -> Number): ReadOnlyProperty = - item(key).convert(MetaConverter.number, default) + value(key) { it?.numberOrNull ?: default() } 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 10b06d74..6aaa5327 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 @@ -15,8 +15,13 @@ import kotlin.jvm.Synchronized @DslMarker public annotation class MetaBuilder +/** + * A generic interface that gives access to getting and setting meta notes and values + */ public interface MutableMetaProvider : MetaProvider { + override fun getMeta(name: Name): MutableMeta? public fun setMeta(name: Name, node: Meta?) + public fun setValue(name: Name, value: Value?) } /** @@ -34,23 +39,19 @@ public interface MutableMeta : Meta, MutableMetaProvider { */ override var value: Value? - /** - * Set or replace node at given [name] - */ - public operator fun set(name: Name, meta: Meta) - - override fun setMeta(name: Name, node: Meta?) { - if (node == null) { - remove(name) + override fun getMeta(name: Name): MutableMeta? { + tailrec fun MutableMeta.find(name: Name): MutableMeta? = if (name.isEmpty()) { + this } else { - set(name, node) + items[name.firstOrNull()!!]?.find(name.cutFirst()) } + + return find(name) } - /** - * Remove a node at a given [name] if it is present - */ - public fun remove(name: Name) + override fun setValue(name: Name, value: Value?) { + getOrCreate(name).value = value + } /** * Get existing node or create a new one @@ -60,82 +61,92 @@ public interface MutableMeta : Meta, MutableMetaProvider { //TODO to be moved to extensions with multi-receivers public infix fun Name.put(value: Value?) { - set(this, value) + setValue(this, value) } public infix fun Name.put(string: String) { - set(this, string.asValue()) + setValue(this, string.asValue()) } public infix fun Name.put(number: Number) { - set(this, number.asValue()) + setValue(this, number.asValue()) } public infix fun Name.put(boolean: Boolean) { - set(this, boolean.asValue()) + setValue(this, boolean.asValue()) } public infix fun Name.put(enum: Enum<*>) { - set(this, EnumValue(enum)) + setValue(this, EnumValue(enum)) } - public infix fun Name.put(iterable: Iterable) { + public infix fun Name.putIndexed(iterable: Iterable) { setIndexed(this, iterable) } public infix fun Name.put(meta: Meta) { - set(this, meta) + setMeta(this, meta) } public infix fun Name.put(repr: MetaRepr) { - put(repr.toMeta()) + setMeta(this, repr.toMeta()) } public infix fun Name.put(mutableMeta: MutableMeta.() -> Unit) { - set(this, Meta(mutableMeta)) + setMeta(this, Meta(mutableMeta)) } public infix fun String.put(meta: Meta) { - Name.parse(this) put meta + setMeta(Name.parse(this), meta) } public infix fun String.put(value: Value?) { - set(Name.parse(this), value) + setValue(Name.parse(this), value) } public infix fun String.put(string: String) { - set(Name.parse(this), string.asValue()) + setValue(Name.parse(this), string.asValue()) } public infix fun String.put(number: Number) { - set(Name.parse(this), number.asValue()) + setValue(Name.parse(this), number.asValue()) } public infix fun String.put(boolean: Boolean) { - set(Name.parse(this), boolean.asValue()) + setValue(Name.parse(this), boolean.asValue()) } public infix fun String.put(enum: Enum<*>) { - set(Name.parse(this), EnumValue(enum)) + setValue(Name.parse(this), EnumValue(enum)) } public infix fun String.put(array: DoubleArray) { - set(Name.parse(this), array.asValue()) + setValue(Name.parse(this), array.asValue()) } public infix fun String.put(repr: MetaRepr) { - Name.parse(this) put repr.toMeta() + setMeta(Name.parse(this), repr.toMeta()) } - public infix fun String.put(iterable: Iterable) { + public infix fun String.putIndexed(iterable: Iterable) { setIndexed(Name.parse(this), iterable) } public infix fun String.put(builder: MutableMeta.() -> Unit) { - set(Name.parse(this), MutableMeta(builder)) + setMeta(Name.parse(this), MutableMeta(builder)) } } +/** + * Set or replace node at given [name] + */ +public operator fun MutableMeta.set(name: Name, meta: Meta): Unit = setMeta(name, meta) + +/** + * Set or replace value at given [name] + */ +public operator fun MutableMeta.set(name: Name, value: Value?): Unit = setValue(name, value) + public fun MutableMeta.getOrCreate(key: String): MutableMeta = getOrCreate(Name.parse(key)) @Serializable(MutableMetaSerializer::class) @@ -151,8 +162,12 @@ public interface MutableTypedMeta> : TypedMeta, Mutab public fun > M.getOrCreate(key: String): M = getOrCreate(Name.parse(key)) -public fun MutableMeta.remove(key: String) { - remove(Name.parse(key)) +public fun MutableMetaProvider.remove(name: Name) { + setMeta(name, null) +} + +public fun MutableMetaProvider.remove(key: String) { + setMeta(Name.parse(key), null) } // node setters @@ -185,35 +200,21 @@ public operator fun MutableMeta.set(key: String, value: List): Unit = set // set(key.toName().withIndex(index), value) -/** - * Universal unsafe set method - */ -public operator fun MutableMeta.set(name: Name, value: Value?) { - getOrCreate(name).value = Value.of(value) -} - /* Same name siblings generation */ -public fun MutableMeta.setIndexedItems( - name: Name, - items: Iterable, - indexFactory: (Meta, index: Int) -> String = { _, index -> index.toString() }, -) { - val tokens = name.tokens.toMutableList() - val last = tokens.last() - items.forEachIndexed { index, meta -> - val indexedToken = NameToken(last.body, (last.index ?: "") + indexFactory(meta, index)) - tokens[tokens.lastIndex] = indexedToken - set(Name(tokens), meta) - } -} public fun MutableMeta.setIndexed( name: Name, metas: Iterable, indexFactory: (Meta, index: Int) -> String = { _, index -> index.toString() }, ) { - setIndexedItems(name, metas) { item, index -> indexFactory(item, index) } + val tokens = name.tokens.toMutableList() + val last = tokens.last() + metas.forEachIndexed { index, meta -> + val indexedToken = NameToken(last.body, (last.index ?: "") + indexFactory(meta, index)) + tokens[tokens.lastIndex] = indexedToken + set(Name(tokens), meta) + } } public operator fun MutableMeta.set(name: Name, metas: Iterable): Unit = @@ -265,8 +266,20 @@ private class MutableMetaImpl( children: Map = emptyMap() ) : ObservableMutableMeta { + private val listeners = HashSet() + + private fun changed(name: Name) { + listeners.forEach { it.callback(this, name) } + } + override var value = value - @Synchronized set + @Synchronized set(value) { + val oldValue = field + field = value + if (oldValue != value) { + changed(Name.EMPTY) + } + } private val children: LinkedHashMap = LinkedHashMap(children.mapValues { (key, meta) -> @@ -275,11 +288,6 @@ private class MutableMetaImpl( override val items: Map get() = children - private val listeners = HashSet() - - private fun changed(name: Name) { - listeners.forEach { it.callback(this, name) } - } @Synchronized override fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit) { @@ -322,15 +330,8 @@ private class MutableMetaImpl( else -> getOrCreate(name.first().asName()).getOrCreate(name.cutFirst()) } - override fun getOrCreate(name: Name): ObservableMutableMeta = get(name) ?: createNode(name) - - override fun remove(name: Name) { - when (name.length) { - 0 -> error("Can't remove self") - 1 -> if (children.remove(name.firstOrNull()!!) != null) changed(name) - else -> get(name.cutLast())?.remove(name.lastOrNull()!!.asName()) - } - } + override fun getOrCreate(name: Name): ObservableMutableMeta = + if (name.isEmpty()) this else get(name) ?: createNode(name) @Synchronized private fun replaceItem( @@ -354,14 +355,14 @@ private class MutableMetaImpl( MutableMetaImpl(meta.value, meta.items.mapValuesTo(LinkedHashMap()) { wrapItem(it.value) }) - override fun set(name: Name, meta: Meta) { + override fun setMeta(name: Name, node: Meta?) { val oldItem: ObservableMutableMeta? = get(name) - if (oldItem != meta) { + if (oldItem != node) { when (name.length) { 0 -> error("Can't set a meta with empty name") 1 -> { val token = name.firstOrNull()!! - replaceItem(token, oldItem, wrapItem(meta)) + replaceItem(token, oldItem, node?.let { wrapItem(node) }) } else -> { val token = name.firstOrNull()!! @@ -369,7 +370,7 @@ private class MutableMetaImpl( if (items[token] == null) { replaceItem(token, null, MutableMetaImpl(null)) } - items[token]?.set(name.cutFirst(), meta) + items[token]?.setMeta(name.cutFirst(), node) } } changed(name) @@ -421,6 +422,7 @@ public fun Meta.toMutableMeta(): ObservableMutableMeta = MutableMetaImpl(value, public fun Meta.asMutableMeta(): MutableMeta = (this as? MutableMeta) ?: toMutableMeta() +@Suppress("FunctionName") @JsName("newMutableMeta") public fun MutableMeta(): ObservableMutableMeta = MutableMetaImpl(null) @@ -441,20 +443,25 @@ public inline fun Meta.copy(block: MutableMeta.() -> Unit = {}): Meta = private class MutableMetaWithDefault( - val source: MutableMeta, val default: Meta, val name: Name + val source: MutableMeta, val default: Meta, val rootName: Name ) : MutableMeta by source { override val items: Map - get() = (source.items.keys + default.items.keys).associateWith { - MutableMetaWithDefault(source, default, name + it) + get() { + val sourceKeys: Collection = source[rootName]?.items?.keys ?: emptyList() + val defaultKeys: Collection = default[rootName]?.items?.keys ?: emptyList() + //merging keys for primary and default node + return (sourceKeys + defaultKeys).associateWith { + MutableMetaWithDefault(source, default, rootName + it) + } } override var value: Value? - get() = source[name]?.value ?: default[name]?.value + get() = source[rootName]?.value ?: default[rootName]?.value set(value) { - source[name] = value + source[rootName] = value } - override fun getMeta(name: Name): Meta? = source.getMeta(name) ?: default.getMeta(name) + override fun getMeta(name: Name): MutableMeta = MutableMetaWithDefault(source, default, rootName + name) override fun toString(): String = Meta.toString(this) override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta) diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MutableMetaDelegate.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MutableMetaDelegate.kt index 44cba048..d9677515 100644 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MutableMetaDelegate.kt +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MutableMetaDelegate.kt @@ -9,138 +9,115 @@ import kotlin.reflect.KProperty /* Read-write delegates */ -public typealias MutableMetaDelegate = ReadWriteProperty +public fun MutableMetaProvider.node(key: Name? = null): ReadWriteProperty = + object : ReadWriteProperty { + override fun getValue(thisRef: Any?, property: KProperty<*>): Meta? { + return getMeta(key ?: property.name.asName()) + } -public fun MutableMetaProvider.item(key: Name? = null): MutableMetaDelegate = object : MutableMetaDelegate { - override fun getValue(thisRef: Any?, property: KProperty<*>): Meta? { - return getMeta(key ?: property.name.asName()) + override fun setValue(thisRef: Any?, property: KProperty<*>, value: Meta?) { + val name = key ?: property.name.asName() + setMeta(name, value) + } } - override fun setValue(thisRef: Any?, property: KProperty<*>, value: Meta?) { - val name = key ?: property.name.asName() - setMeta(name, value) +public fun MutableMetaProvider.node(key: Name? = null, converter: MetaConverter): ReadWriteProperty = + object : ReadWriteProperty { + override fun getValue(thisRef: Any?, property: KProperty<*>): T? { + return getMeta(key ?: property.name.asName())?.let { converter.metaToObject(it) } + } + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) { + val name = key ?: property.name.asName() + setMeta(name, value?.let { converter.objectToMeta(it) }) + } + } + +public fun MutableMetaProvider.value(key: Name? = null): ReadWriteProperty = + object : ReadWriteProperty { + override fun getValue(thisRef: Any?, property: KProperty<*>): Value? = + getMeta(key ?: property.name.asName())?.value + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: Value?) { + setValue(key ?: property.name.asName(), value) + } + } + +public fun MutableMetaProvider.value( + key: Name? = null, + writer: (T) -> Value? = { Value.of(it) }, + reader: (Value?) -> T +): ReadWriteProperty = object : ReadWriteProperty { + override fun getValue(thisRef: Any?, property: KProperty<*>): T = + reader(getMeta(key ?: property.name.asName())?.value) + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { + setValue(key ?: property.name.asName(), writer(value)) } } -/* Mutable converters */ - -/** - * A type converter for a [MutableMetaDelegate] - */ -public fun MutableMetaDelegate.convert( - converter: MetaConverter, -): ReadWriteProperty = object : ReadWriteProperty { - - override fun getValue(thisRef: Any?, property: KProperty<*>): R? = - this@convert.getValue(thisRef, property)?.let(converter::metaToObject) - - override fun setValue(thisRef: Any?, property: KProperty<*>, value: R?) { - val item = value?.let(converter::objectToMeta) - this@convert.setValue(thisRef, property, item) - } -} - -public fun MutableMetaDelegate.convert( - converter: MetaConverter, - default: () -> R, -): ReadWriteProperty = object : ReadWriteProperty { - - override fun getValue(thisRef: Any?, property: KProperty<*>): R = - this@convert.getValue(thisRef, property)?.let(converter::metaToObject) ?: default() - - override fun setValue(thisRef: Any?, property: KProperty<*>, value: R) { - val item = value.let(converter::objectToMeta) - this@convert.setValue(thisRef, property, item) - } -} - -public fun MutableMetaDelegate.convert( - reader: (Meta?) -> R, - writer: (R) -> Meta?, -): ReadWriteProperty = object : ReadWriteProperty { - - override fun getValue(thisRef: Any?, property: KProperty<*>): R = - this@convert.getValue(thisRef, property).let(reader) - - override fun setValue(thisRef: Any?, property: KProperty<*>, value: R) { - val item = value?.let(writer) - this@convert.setValue(thisRef, property, item) - } -} - - /* Read-write delegates for [MutableItemProvider] */ -/** - * A property delegate that uses custom key - */ -public fun MutableMetaProvider.value(key: Name? = null): ReadWriteProperty = - item(key).convert(MetaConverter.value) - public fun MutableMetaProvider.string(key: Name? = null): ReadWriteProperty = - item(key).convert(MetaConverter.string) + value(key) { it?.string } public fun MutableMetaProvider.boolean(key: Name? = null): ReadWriteProperty = - item(key).convert(MetaConverter.boolean) + value(key) { it?.boolean } public fun MutableMetaProvider.number(key: Name? = null): ReadWriteProperty = - item(key).convert(MetaConverter.number) + value(key) { it?.number } public fun MutableMetaProvider.string(default: String, key: Name? = null): ReadWriteProperty = - item(key).convert(MetaConverter.string) { default } + value(key) { it?.string ?: default } public fun MutableMetaProvider.boolean(default: Boolean, key: Name? = null): ReadWriteProperty = - item(key).convert(MetaConverter.boolean) { default } + value(key) { it?.boolean ?: default } public fun MutableMetaProvider.number(default: Number, key: Name? = null): ReadWriteProperty = - item(key).convert(MetaConverter.number) { default } - -public fun MutableMetaProvider.value(key: Name? = null, default: () -> Value): ReadWriteProperty = - item(key).convert(MetaConverter.value, default) + value(key) { it?.number ?: default } public fun MutableMetaProvider.string(key: Name? = null, default: () -> String): ReadWriteProperty = - item(key).convert(MetaConverter.string, default) + value(key) { it?.string ?: default() } public fun MutableMetaProvider.boolean(key: Name? = null, default: () -> Boolean): ReadWriteProperty = - item(key).convert(MetaConverter.boolean, default) + value(key) { it?.boolean ?: default() } public fun MutableMetaProvider.number(key: Name? = null, default: () -> Number): ReadWriteProperty = - item(key).convert(MetaConverter.number, default) + value(key) { it?.number ?: default() } public inline fun > MutableMetaProvider.enum( default: E, key: Name? = null, -): ReadWriteProperty = - item(key).convert(MetaConverter.enum()) { default } +): ReadWriteProperty = value(key) { value -> value?.string?.let { enumValueOf(it) } ?: default } /* Number delegates */ public fun MutableMetaProvider.int(key: Name? = null): ReadWriteProperty = - item(key).convert(MetaConverter.int) + value(key) { it?.int } public fun MutableMetaProvider.double(key: Name? = null): ReadWriteProperty = - item(key).convert(MetaConverter.double) + value(key) { it?.double } public fun MutableMetaProvider.long(key: Name? = null): ReadWriteProperty = - item(key).convert(MetaConverter.long) + value(key) { it?.long } public fun MutableMetaProvider.float(key: Name? = null): ReadWriteProperty = - item(key).convert(MetaConverter.float) + value(key) { it?.float } /* Safe number delegates*/ public fun MutableMetaProvider.int(default: Int, key: Name? = null): ReadWriteProperty = - item(key).convert(MetaConverter.int) { default } + value(key) { it?.int ?: default } public fun MutableMetaProvider.double(default: Double, key: Name? = null): ReadWriteProperty = - item(key).convert(MetaConverter.double) { default } + value(key) { it?.double ?: default } public fun MutableMetaProvider.long(default: Long, key: Name? = null): ReadWriteProperty = - item(key).convert(MetaConverter.long) { default } + value(key) { it?.long ?: default } public fun MutableMetaProvider.float(default: Float, key: Name? = null): ReadWriteProperty = - item(key).convert(MetaConverter.float) { default } + value(key) { it?.float ?: default } /* Extra delegates for special cases */ @@ -148,24 +125,27 @@ public fun MutableMetaProvider.float(default: Float, key: Name? = null): ReadWri public fun MutableMetaProvider.stringList( vararg default: String, key: Name? = null, -): ReadWriteProperty> = item(key).convert( +): ReadWriteProperty> = value( + key, + writer = { list -> list.map { str -> str.asValue() }.asValue() }, reader = { it?.stringList ?: listOf(*default) }, - writer = { Meta(it.map { str -> str.asValue() }.asValue()) } ) public fun MutableMetaProvider.stringList( key: Name? = null, -): ReadWriteProperty?> = item(key).convert( +): ReadWriteProperty?> = value( + key, + writer = { it -> it?.map { str -> str.asValue() }?.asValue() }, reader = { it?.stringList }, - writer = { it?.map { str -> str.asValue() }?.asValue()?.let { Meta(it) } } ) public fun MutableMetaProvider.numberList( vararg default: Number, key: Name? = null, -): ReadWriteProperty> = item(key).convert( - reader = { it?.value?.list?.map { value -> value.numberOrNull ?: Double.NaN } ?: listOf(*default) }, - writer = { Meta(it.map { num -> num.asValue() }.asValue()) } +): ReadWriteProperty> = value( + key, + writer = { it.map { num -> num.asValue() }.asValue() }, + reader = { it?.list?.map { value -> value.numberOrNull ?: Double.NaN } ?: listOf(*default) }, ) /* A special delegate for double arrays */ @@ -174,13 +154,18 @@ public fun MutableMetaProvider.numberList( public fun MutableMetaProvider.doubleArray( vararg default: Double, key: Name? = null, -): ReadWriteProperty = item(key).convert( - reader = { it?.value?.doubleArray ?: doubleArrayOf(*default) }, - writer = { Meta(DoubleArrayValue(it)) } +): ReadWriteProperty = value( + key, + writer = { DoubleArrayValue(it) }, + reader = { it?.doubleArray ?: doubleArrayOf(*default) }, ) public fun MutableMetaProvider.listValue( key: Name? = null, writer: (T) -> Value = { Value.of(it) }, reader: (Value) -> T, -): ReadWriteProperty?> = item(key).convert(MetaConverter.valueList(writer, reader)) +): ReadWriteProperty?> = value( + key, + writer = { it?.map(writer)?.asValue() }, + reader = { it?.list?.map(reader) } +) 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 fa9dddf4..371d8e92 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 @@ -54,7 +54,7 @@ private class ObservableMetaWrapper( override val items: Map get() = origin.items.mapValues { ObservableMetaWrapper(it.value) } - override fun getMeta(name: Name): Meta? = origin.getMeta(name) + override fun getMeta(name: Name): MutableMeta? = origin.getMeta(name) override var value: Value? get() = origin.value @@ -66,22 +66,18 @@ private class ObservableMetaWrapper( override fun getOrCreate(name: Name): ObservableMutableMeta = get(name) ?: ObservableMetaWrapper(origin.getOrCreate(name)) - - override fun remove(name: Name) { - origin.remove(name) - changed(name) - } - - override fun set(name: Name, meta: Meta) { + override fun setMeta(name: Name, node: Meta?) { val oldMeta = get(name) - origin[name] = meta + origin.setMeta(name, node) + // // if meta is observable propagate changes from it - if(meta is ObservableMeta){ - meta.onChange(this) { changeName -> - setMeta(name + changeName, meta[changeName]) + if(node is ObservableMeta){ + + node.onChange(this) { changeName -> + setMeta(name + changeName, node[changeName]) } } - if (oldMeta != meta) { + if (oldMeta != node) { changed(name) } } 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 ae412fc7..3cf40a76 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 @@ -2,6 +2,7 @@ package space.kscience.dataforge.meta import space.kscience.dataforge.meta.descriptors.* import space.kscience.dataforge.names.Name +import space.kscience.dataforge.values.Value /** * A base for delegate-based or descriptor-based scheme. [Scheme] has an empty constructor to simplify usage from [Specification]. @@ -30,7 +31,7 @@ public open class Scheme : Described, MetaRepr, MutableMetaProvider, Configurabl return descriptor?.validate(meta) ?: true } - override fun getMeta(name: Name): Meta? = meta.getMeta(name) + override fun getMeta(name: Name): MutableMeta? = meta.getMeta(name) override fun setMeta(name: Name, node: Meta?) { if (validate(name, meta)) { @@ -40,6 +41,11 @@ public open class Scheme : Described, MetaRepr, MutableMetaProvider, Configurabl } } + override fun setValue(name: Name, value: Value?) { + //TODO add validation + meta.setValue(name,value) + } + override fun toMeta(): Laminate = Laminate(meta, descriptor?.defaultNode) } diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/SealedMeta.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/SealedMeta.kt index 6d2ea9fc..597d331f 100644 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/SealedMeta.kt +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/SealedMeta.kt @@ -22,9 +22,12 @@ public class SealedMeta internal constructor( /** * Generate sealed node from [this]. If it is already sealed return it as is */ -public fun Meta.seal(): SealedMeta = this as? SealedMeta ?: SealedMeta(value, items.mapValues { entry -> - entry.value.seal() -}) +public fun Meta.seal(): SealedMeta = this as? SealedMeta ?: SealedMeta( + value, + items.mapValues { entry -> + entry.value.seal() + } +) @Suppress("FunctionName") public fun Meta(value: Value): SealedMeta = SealedMeta(value, emptyMap()) diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/mapMeta.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/mapMeta.kt index 6074cb8a..0c129932 100644 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/mapMeta.kt +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/mapMeta.kt @@ -31,7 +31,7 @@ public fun Meta.toMap(descriptor: MetaDescriptor? = null): Map = b * All other values will be converted to [Value]. */ @DFExperimental -public fun Map.toMeta(descriptor: MetaDescriptor? = null): Meta = Meta { +public fun Map.toMeta(@Suppress("UNUSED_PARAMETER") descriptor: MetaDescriptor? = null): Meta = Meta { @Suppress("UNCHECKED_CAST") fun toMeta(value: Any?): Meta = when (value) { is Meta -> value @@ -45,7 +45,7 @@ public fun Map.toMeta(descriptor: MetaDescriptor? = null): Meta = if (items.all { it.isLeaf }) { set(key, ListValue(items.map { it.value!! })) } else { - setIndexedItems(Name.parse(key), value.map { toMeta(it) }) + setIndexed(Name.parse(key), value.map { toMeta(it) }) } } else { set(key, toMeta(value)) diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/values/Value.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/values/Value.kt index d470f366..e2a83b9c 100644 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/values/Value.kt +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/values/Value.kt @@ -194,13 +194,13 @@ public class ListValue(override val list: List) : Value, Iterable return list.hashCode() } - public companion object{ + public companion object { public val EMPTY: ListValue = ListValue(emptyList()) } } -public fun ListValue(vararg numbers: Number): ListValue = ListValue(numbers.map{it.asValue()}) -public fun ListValue(vararg strings: String): ListValue = ListValue(strings.map{it.asValue()}) +public fun ListValue(vararg numbers: Number): ListValue = ListValue(numbers.map { it.asValue() }) +public fun ListValue(vararg strings: String): ListValue = ListValue(strings.map { it.asValue() }) public fun Number.asValue(): Value = NumberValue(this) diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/values/valueExtensions.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/values/valueExtensions.kt index 7ccc8f43..68755825 100644 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/values/valueExtensions.kt +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/values/valueExtensions.kt @@ -24,6 +24,13 @@ public val Value.float: Float get() = number.toFloat() public val Value.short: Short get() = number.toShort() public val Value.long: Long get() = number.toLong() +public inline fun > Value.enum(): E = if (this is EnumValue<*>) { + value as E +} else { + enumValueOf(string) +} + + public val Value.stringList: List get() = list.map { it.string } public val Value.doubleArray: DoubleArray diff --git a/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/MetaDelegateTest.kt b/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/MetaDelegateTest.kt index 3456494f..7a2dbc22 100644 --- a/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/MetaDelegateTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/MetaDelegateTest.kt @@ -1,6 +1,5 @@ package space.kscience.dataforge.meta -import space.kscience.dataforge.values.asValue import kotlin.test.Test import kotlin.test.assertEquals @@ -30,7 +29,7 @@ class MetaDelegateTest { fun delegateTest() { val testObject = TestScheme.empty() - testObject.meta["myValue"] = "theString".asValue() + testObject.meta["myValue"] = "theString" testObject.enumValue = TestEnum.NO testObject.inner = InnerScheme { innerValue = "ddd" } diff --git a/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/MetaTest.kt b/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/MetaTest.kt index f3971b66..f88bc307 100644 --- a/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/MetaTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/MetaTest.kt @@ -40,7 +40,7 @@ class MetaTest { "b" put { "c" put "ddd" } - "list" put (0..4).map { + "list" putIndexed (0..4).map { Meta { "value" put it }