From a3479e74f7b9d8879ad3a1a13cad6eddca33d660 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 25 Jul 2021 12:57:10 +0300 Subject: [PATCH] WIP full refactor of Meta --- .../kscience/dataforge/io/JsonMetaFormat.kt | 17 ++++---- .../space/kscience/dataforge/meta/JsonMeta.kt | 4 ++ .../space/kscience/dataforge/meta/Laminate.kt | 4 ++ .../space/kscience/dataforge/meta/Meta.kt | 5 +++ .../kscience/dataforge/meta/MutableMeta.kt | 42 +++++++++++++------ .../dataforge/meta/MutableMetaDelegate.kt | 2 +- .../kscience/dataforge/meta/ObservableMeta.kt | 19 ++++----- .../space/kscience/dataforge/meta/mapMeta.kt | 37 +++++++++------- .../space/kscience/dataforge/names/Name.kt | 5 +++ 9 files changed, 86 insertions(+), 49 deletions(-) diff --git a/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/JsonMetaFormat.kt b/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/JsonMetaFormat.kt index 8cac788e..5e293e7a 100644 --- a/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/JsonMetaFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/JsonMetaFormat.kt @@ -6,11 +6,11 @@ package space.kscience.dataforge.io import io.ktor.utils.io.core.Input import io.ktor.utils.io.core.Output import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonElement import space.kscience.dataforge.context.Context import space.kscience.dataforge.io.IOFormat.Companion.NAME_KEY import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.meta.descriptors.NodeDescriptor +import space.kscience.dataforge.meta.descriptors.MetaDescriptor import space.kscience.dataforge.meta.toJson import space.kscience.dataforge.meta.toMeta import kotlin.reflect.KType @@ -23,20 +23,19 @@ public class JsonMetaFormat(private val json: Json = DEFAULT_JSON) : MetaFormat override val type: KType get() = typeOf() - override fun writeMeta(output: Output, meta: Meta, descriptor: NodeDescriptor?) { + override fun writeMeta(output: Output, meta: Meta, descriptor: MetaDescriptor?) { val jsonObject = meta.toJson(descriptor) - output.writeUtf8String(json.encodeToString(JsonObject.serializer(), jsonObject)) + output.writeUtf8String(json.encodeToString(JsonElement.serializer(), jsonObject)) } override fun toMeta(): Meta = Meta { NAME_KEY put name.toString() } - override fun readMeta(input: Input, descriptor: NodeDescriptor?): Meta { + override fun readMeta(input: Input, descriptor: MetaDescriptor?): Meta { val str = input.readUtf8String()//readByteArray().decodeToString() val jsonElement = json.parseToJsonElement(str) - val item = jsonElement.toMeta(descriptor) - return item.node ?: Meta.EMPTY + return jsonElement.toMeta(descriptor) } public companion object : MetaFormatFactory { @@ -49,10 +48,10 @@ public class JsonMetaFormat(private val json: Json = DEFAULT_JSON) : MetaFormat private val default = JsonMetaFormat() - override fun writeMeta(output: Output, meta: Meta, descriptor: NodeDescriptor?): Unit = + override fun writeMeta(output: Output, meta: Meta, descriptor: MetaDescriptor?): Unit = default.run { writeMeta(output, meta, descriptor) } - override fun readMeta(input: Input, descriptor: NodeDescriptor?): Meta = + override fun readMeta(input: Input, descriptor: MetaDescriptor?): Meta = default.run { readMeta(input, descriptor) } } } diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/JsonMeta.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/JsonMeta.kt index 93e506d9..bdd83730 100644 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/JsonMeta.kt +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/JsonMeta.kt @@ -116,6 +116,10 @@ public class JsonMeta( } } + 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) + public companion object { /** * A key representing top-level json array of nodes, which could not be directly represented by a meta node diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/Laminate.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/Laminate.kt index b2af2e73..217efa95 100644 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/Laminate.kt +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/Laminate.kt @@ -36,6 +36,10 @@ public class Laminate(layers: List) : TypedMeta { return SealedMeta(value, items) } + 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) + public companion object { /** 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 bc318100..d8e7d0f1 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 @@ -63,6 +63,11 @@ public interface Meta : MetaRepr { } } +/** + * True if this [Meta] does not have children + */ +public val Meta.isLeaf: Boolean get() = items.isEmpty() + public operator fun Meta.get(token: NameToken): Meta? = items[token] 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 ed64e153..0a740c76 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 @@ -17,6 +17,8 @@ import kotlin.jvm.Synchronized @Serializable(MutableMetaSerializer::class) public interface MutableMeta : Meta { + override val items: Map + /** * Get or set value of this node */ @@ -241,12 +243,12 @@ private class MutableMetaImpl( children: Map = emptyMap() ) : ObservableMutableMeta { - private val children: LinkedHashMap = + private val children: LinkedHashMap = LinkedHashMap(children.mapValues { (key, meta) -> MutableMetaImpl(meta.value, meta.items).apply { adoptBy(this, key) } }) - override val items: Map get() = children + override val items: Map get() = children private val listeners = HashSet() @@ -271,21 +273,37 @@ private class MutableMetaImpl( } } - override fun attach(name: Name, node: ObservableMutableMeta) { + +// fun attach(name: Name, node: MutableMetaImpl) { +// when (name.length) { +// 0 -> error("Can't set a meta with empty name") +// 1 -> { +// val key = name.firstOrNull()!! +// children[key] = node +// adoptBy(this, key) +// changed(name) +// } +// else -> get(name.cutLast())?.attach(name.lastOrNull()!!.asName(), node) +// } +// } + + /** + * Create and attach empty node + */ + private fun createNode(name: Name): ObservableMutableMeta { + val newNode = MutableMetaImpl(null) when (name.length) { - 0 -> error("Can't set a meta with empty name") + 0 -> throw IllegalArgumentException("Can't create a node with empty name") 1 -> { - val key = name.firstOrNull()!! - children[key] = node - adoptBy(this, key) - changed(name) - } - else -> get(name.cutLast())?.attach(name.lastOrNull()!!.asName(), node) + children[name.first()] = newNode + newNode.adoptBy(this, name.first()) + } //do not notify, no value changed + else -> getOrCreate(name.first().asName()).getOrCreate(name.cutFirst()) } + return newNode } - override fun getOrCreate(name: Name): ObservableMutableMeta = - get(name) ?: MutableMetaImpl(null).also { attach(name, it) } + override fun getOrCreate(name: Name): ObservableMutableMeta = get(name) ?: createNode(name) override fun removeNode(name: Name) { when (name.length) { 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 b2fa42fc..ecb04144 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 @@ -25,7 +25,7 @@ public fun MutableMeta.item(key: Name? = null): MutableMetaDelegate = object : M /* Mutable converters */ /** - * A type converter for a mutable [TypedMetaItem] delegate + * A type converter for a [MutableMetaDelegate] */ public fun MutableMetaDelegate.convert( converter: MetaConverter, 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 3a0d3752..71c67366 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 @@ -32,11 +32,11 @@ public interface ObservableMeta : Meta { /** * A [Meta] which is both observable and mutable */ -public interface ObservableMutableMeta : ObservableMeta, MutableTypedMeta +public interface ObservableMutableMeta : ObservableMeta, MutableMeta, TypedMeta -private class ObservableMetaWrapper>( - val origin: M, -) : ObservableMutableMeta, Meta by origin { +private class ObservableMetaWrapper( + val origin: MutableMeta, +) : ObservableMutableMeta, Meta by origin { private val listeners = HashSet() @@ -54,7 +54,7 @@ private class ObservableMetaWrapper>( listeners.removeAll { it.owner === owner } } - override val items: Map> + override val items: Map get() = origin.items.mapValues { ObservableMetaWrapper(it.value) } override var value: Value? @@ -64,11 +64,6 @@ private class ObservableMetaWrapper>( changed(Name.EMPTY) } - override fun attach(name: Name, node: ObservableMutableMeta) { - origin.attach(name, node.origin) - changed(name) - } - override fun getOrCreate(name: Name): ObservableMutableMeta = get(name) ?: ObservableMetaWrapper(origin.getOrCreate(name)) @@ -91,8 +86,8 @@ private class ObservableMetaWrapper>( } } -public fun > MutableTypedMeta.asObservable(): ObservableMeta = - (this as? ObservableMeta) ?: ObservableMetaWrapper(self) +public fun MutableMeta.asObservable(): ObservableMeta = + (this as? ObservableMeta) ?: ObservableMetaWrapper(this) /** 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 558e7fb3..a2190457 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 @@ -1,6 +1,7 @@ package space.kscience.dataforge.meta import space.kscience.dataforge.meta.descriptors.MetaDescriptor +import space.kscience.dataforge.meta.descriptors.get import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.names.toName import space.kscience.dataforge.values.ListValue @@ -9,39 +10,45 @@ import space.kscience.dataforge.values.Value /** * Convert meta to map of maps */ -public fun Meta.toMap(descriptor: MetaDescriptor? = null): Map { - return items.entries.associate { (token, item) -> - token.toString() to when (item) { - is MetaItemNode -> item.node.toMap() - is MetaItemValue -> item.value.value +public fun Meta.toMap(descriptor: MetaDescriptor? = null): Map = buildMap { + items.forEach { (key, child) -> + if (child.items.isEmpty()) { + //single-value meta is considered a leaf + put(key.toString(), child.value) + } else { + //if child contains both children and value, then value will be placed into `@value` key + put(key.toString(), child.toMap(descriptor?.get(key.body))) } } + if (value != null) { + put(Meta.VALUE_KEY, value) + } } + /** - * Convert map of maps to meta. This method will recognize [TypedMetaItem], [Map] and [List] of all mentioned above as value. + * Convert map of maps to meta. This method will recognize [Meta], [Map] and [List] of all mentioned above as value. * All other values will be converted to values. */ @DFExperimental public fun Map.toMeta(descriptor: MetaDescriptor? = null): Meta = Meta { @Suppress("UNCHECKED_CAST") - fun toItem(value: Any?): MetaItem = when (value) { - is MetaItem -> value - is Meta -> MetaItemNode(value) - is Map<*, *> -> MetaItemNode((value as Map).toMeta()) - else -> MetaItemValue(Value.of(value)) + fun toMeta(value: Any?): Meta = when (value) { + is Meta -> value + is Map<*, *> -> (value as Map).toMeta() + else -> Meta(Value.of(value)) } entries.forEach { (key, value) -> if (value is List<*>) { - val items = value.map { toItem(it) } - if (items.all { it is MetaItemValue }) { + val items = value.map { toMeta(it) } + if (items.all { it.isLeaf }) { set(key, ListValue(items.map { it.value!! })) } else { - setIndexedItems(key.toName(), value.map { toItem(it) }) + setIndexedItems(key.toName(), value.map { toMeta(it) }) } } else { - set(key, toItem(value)) + set(key, toMeta(value)) } } } \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/names/Name.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/names/Name.kt index 7d7a214d..cca67f58 100644 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/names/Name.kt +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/names/Name.kt @@ -74,6 +74,11 @@ public fun Name.lastOrNull(): NameToken? = tokens.lastOrNull() */ public fun Name.firstOrNull(): NameToken? = tokens.firstOrNull() +/** + * First token or throw exception + */ +public fun Name.first(): NameToken = tokens.first() + /** * Convert a [String] to name parsing it and extracting name tokens and index syntax.