From c8bd3390cb658350754f7eda7ec19743c509f8cb Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Fri, 30 Jul 2021 09:17:46 +0300 Subject: [PATCH] Cover corner cases in JsonMeta --- CHANGELOG.md | 2 + .../kscience/dataforge/context/Context.kt | 4 +- .../dataforge/properties/MetaProperty.kt | 18 +- .../dataforge/properties/schemeProperty.kt | 5 +- .../space/kscience/dataforge/data/DataSet.kt | 1 - .../kscience/dataforge/data/GroupRule.kt | 2 +- .../dataforge/io/yaml/YamlMetaFormat.kt | 70 +++--- .../kscience/dataforge/io/BinaryMetaFormat.kt | 133 ----------- .../kscience/dataforge/io/EnvelopeParts.kt | 8 +- .../space/kscience/dataforge/io/IOFormat.kt | 34 ++- .../space/kscience/dataforge/io/IOPlugin.kt | 18 +- .../space/kscience/dataforge/io/MetaFormat.kt | 6 +- .../kscience/dataforge/io/MetaFormatTest.kt | 53 ++--- .../dataforge/io/MetaSerializerTest.kt | 6 +- .../space/kscience/dataforge/io/fileIO.kt | 6 +- .../space/kscience/dataforge/meta/JsonMeta.kt | 197 +++++++++++++---- .../space/kscience/dataforge/meta/Meta.kt | 8 +- .../kscience/dataforge/meta/MetaDelegate.kt | 4 +- .../kscience/dataforge/meta/MetaSerializer.kt | 29 ++- .../kscience/dataforge/meta/MutableMeta.kt | 209 +++++++++++------- .../dataforge/meta/MutableMetaDelegate.kt | 8 +- .../kscience/dataforge/meta/ObservableMeta.kt | 18 +- .../space/kscience/dataforge/meta/Scheme.kt | 48 ++-- .../kscience/dataforge/meta/SealedMeta.kt | 4 +- .../kscience/dataforge/meta/Specification.kt | 6 +- .../dataforge/meta/descriptors/Described.kt | 18 +- .../meta/descriptors/MetaDescriptor.kt | 50 +++-- .../meta/descriptors/MetaDescriptorBuilder.kt | 124 +++++++++++ .../meta/descriptors/descriptorExtensions.kt | 18 -- .../meta/transformations/MetaConverter.kt | 105 +++------ .../transformations/MetaTransformation.kt | 33 +-- .../space/kscience/dataforge/values/Value.kt | 14 +- .../dataforge/values/valueExtensions.kt | 3 +- .../kscience/dataforge/meta/JsonMetaTest.kt | 22 +- .../dataforge/meta/MetaBuilderTest.kt | 3 +- .../dataforge/meta/MutableMetaTest.kt | 2 +- .../dataforge/meta/SpecificationTest.kt | 4 +- .../meta/descriptors/DescriptorTest.kt | 16 +- .../kscience/dataforge/meta/DynamicMeta.kt | 70 +++--- .../dataforge/meta/DynamicMetaTest.kt | 5 +- .../kscience/dataforge/workspace/Task.kt | 8 +- .../dataforge/workspace/WorkspaceBuilder.kt | 11 +- .../kscience/dataforge/workspace/fileData.kt | 2 +- 43 files changed, 767 insertions(+), 638 deletions(-) delete mode 100644 dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/BinaryMetaFormat.kt create mode 100644 dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/MetaDescriptorBuilder.kt delete mode 100644 dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/descriptorExtensions.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index f058b810..030dac71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Kotlin 1.5.10 - Build tools 0.10.0 - Relaxed type restriction on `MetaConverter`. Now nullables are available. +- **Huge API-breaking refactoring of Meta**. Meta now can hava both value and children. ### Deprecated - Direct use of `Config` @@ -17,6 +18,7 @@ ### Removed - Public PluginManager mutability - Tables and tables-exposed moved to the separate project `tables.kt` +- BinaryMetaFormat. Use CBOR encoding instead ### Fixed - Proper json array index treatment. 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 4f0c96aa..93e203ac 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 @@ -95,8 +95,8 @@ public open class Context internal constructor( override fun toMeta(): Meta = Meta { "parent" to parent?.name - "properties" put properties.layers.firstOrNull() - "plugins" put plugins.map { it.toMeta().asMetaItem() } + properties.layers.firstOrNull()?.let { set("properties", it) } + "plugins" put plugins.map { it.toMeta() } } public companion object { diff --git a/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/properties/MetaProperty.kt b/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/properties/MetaProperty.kt index e9175ff7..23601141 100644 --- a/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/properties/MetaProperty.kt +++ b/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/properties/MetaProperty.kt @@ -1,30 +1,30 @@ package space.kscience.dataforge.properties -import space.kscience.dataforge.meta.MutableMeta -import space.kscience.dataforge.meta.get +import space.kscience.dataforge.meta.* import space.kscience.dataforge.meta.transformations.MetaConverter -import space.kscience.dataforge.meta.transformations.nullableItemToObject -import space.kscience.dataforge.meta.transformations.nullableObjectToMetaItem +import space.kscience.dataforge.meta.transformations.nullableMetaToObject +import space.kscience.dataforge.meta.transformations.nullableObjectToMeta import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.names.Name +import space.kscience.dataforge.names.startsWith @DFExperimental public class MetaProperty( - public val meta: MutableMeta, + public val meta: ObservableMutableMeta, public val name: Name, public val converter: MetaConverter, ) : Property { override var value: T? - get() = converter.nullableItemToObject(meta[name]) + get() = converter.nullableMetaToObject(meta[name]) set(value) { - meta[name] = converter.nullableObjectToMetaItem(value) + meta[name] = converter.nullableObjectToMeta(value) ?: Meta.EMPTY } override fun onChange(owner: Any?, callback: (T?) -> Unit) { - meta.onChange(owner) { name, oldItem, newItem -> - if (name.startsWith(this.name) && oldItem != newItem) callback(converter.nullableItemToObject(newItem)) + meta.onChange(owner) { name -> + if (name.startsWith(this@MetaProperty.name)) callback(converter.nullableMetaToObject(get(name))) } } diff --git a/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/properties/schemeProperty.kt b/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/properties/schemeProperty.kt index 3c474b0e..452eb209 100644 --- a/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/properties/schemeProperty.kt +++ b/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/properties/schemeProperty.kt @@ -3,6 +3,7 @@ package space.kscience.dataforge.properties import space.kscience.dataforge.meta.ObservableMeta import space.kscience.dataforge.misc.DFExperimental +import space.kscience.dataforge.names.startsWith import space.kscience.dataforge.names.toName import kotlin.reflect.KMutableProperty1 @@ -16,8 +17,8 @@ public fun

P.property(property: KMutableProperty1< } override fun onChange(owner: Any?, callback: (T?) -> Unit) { - this@property.onChange(this) { name, oldItem, newItem -> - if (name.startsWith(property.name.toName()) && oldItem != newItem) { + this@property.onChange(this) { name -> + if (name.startsWith(property.name.toName())) { callback(property.get(this@property)) } } 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 305f50f8..e5ece3ee 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,7 +4,6 @@ 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-data/src/commonMain/kotlin/space/kscience/dataforge/data/GroupRule.kt b/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/data/GroupRule.kt index 64f5f6f4..a9050b3c 100644 --- a/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/data/GroupRule.kt +++ b/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/data/GroupRule.kt @@ -54,7 +54,7 @@ public interface GroupRule { val data = set.getData(name) @Suppress("NULLABLE_EXTENSION_OPERATOR_WITH_SAFE_CALL_RECEIVER") - val tagValue = data?.meta[key]?.string ?: defaultTagValue + val tagValue = data?.meta?.get(key)?.string ?: defaultTagValue map.getOrPut(tagValue) { ActiveDataTree(set.dataType) }.emit(name, data) } } diff --git a/dataforge-io/dataforge-io-yaml/src/commonMain/kotlin/space/kscience/dataforge/io/yaml/YamlMetaFormat.kt b/dataforge-io/dataforge-io-yaml/src/commonMain/kotlin/space/kscience/dataforge/io/yaml/YamlMetaFormat.kt index 2dd51ab4..96c57c58 100644 --- a/dataforge-io/dataforge-io-yaml/src/commonMain/kotlin/space/kscience/dataforge/io/yaml/YamlMetaFormat.kt +++ b/dataforge-io/dataforge-io-yaml/src/commonMain/kotlin/space/kscience/dataforge/io/yaml/YamlMetaFormat.kt @@ -10,43 +10,49 @@ import space.kscience.dataforge.io.MetaFormat import space.kscience.dataforge.io.MetaFormatFactory import space.kscience.dataforge.io.readUtf8String import space.kscience.dataforge.io.writeUtf8String -import space.kscience.dataforge.meta.* -import space.kscience.dataforge.meta.descriptors.ItemDescriptor -import space.kscience.dataforge.meta.descriptors.NodeDescriptor +import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.meta.descriptors.MetaDescriptor +import space.kscience.dataforge.meta.descriptors.get +import space.kscience.dataforge.meta.isLeaf import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.names.NameToken import space.kscience.dataforge.names.withIndex import space.kscience.dataforge.values.ListValue import space.kscience.dataforge.values.Null +import space.kscience.dataforge.values.Value import space.kscience.dataforge.values.parseValue +import kotlin.collections.component1 +import kotlin.collections.component2 +import kotlin.collections.set public fun Meta.toYaml(): YamlMap { val map: Map = items.entries.associate { (key, item) -> - key.toString() to when (item) { - is MetaItemValue -> { - item.value.value - } - is MetaItemNode -> { - item.node.toYaml() - } + key.toString() to if (item.isLeaf) { + item.value?.value + } else { + item.toYaml() } } + return YamlMap(map) } -private class YamlMeta(private val yamlMap: YamlMap, private val descriptor: NodeDescriptor? = null) : AbstractTypedMeta() { +private class YamlMeta(private val yamlMap: YamlMap, private val descriptor: MetaDescriptor? = null) : Meta { - private fun buildItems(): Map { - val map = LinkedHashMap() + override val value: Value? + get() = yamlMap.getStringOrNull(null)?.parseValue() + + private fun buildItems(): Map { + val map = LinkedHashMap() yamlMap.content.entries.forEach { (key, value) -> val stringKey = key.toString() - val itemDescriptor = descriptor?.items?.get(stringKey) + val itemDescriptor = descriptor?.get(stringKey) val token = NameToken(stringKey) when (value) { - YamlNull -> Null.asMetaItem() - is YamlLiteral -> map[token] = value.content.parseValue().asMetaItem() - is YamlMap -> map[token] = value.toMeta().asMetaItem() + YamlNull -> Meta(Null) + is YamlLiteral -> map[token] = Meta(value.content.parseValue()) + is YamlMap -> map[token] = value.toMeta() is YamlList -> if (value.all { it is YamlLiteral }) { val listValue = ListValue( value.map { @@ -54,29 +60,33 @@ private class YamlMeta(private val yamlMap: YamlMap, private val descriptor: Nod (it as YamlLiteral).content.parseValue() } ) - map[token] = MetaItemValue(listValue) + map[token] = Meta(listValue) } else value.forEachIndexed { index, yamlElement -> - val indexKey = (itemDescriptor as? NodeDescriptor)?.indexKey ?: ItemDescriptor.DEFAULT_INDEX_KEY + val indexKey = itemDescriptor?.indexKey val indexValue: String = (yamlElement as? YamlMap)?.getStringOrNull(indexKey) ?: index.toString() //In case index is non-string, the backward transformation will be broken. val tokenWithIndex = token.withIndex(indexValue) - map[tokenWithIndex] = yamlElement.toMetaItem(itemDescriptor) + map[tokenWithIndex] = yamlElement.toMeta(itemDescriptor) } } } return map } - override val items: Map get() = buildItems() + override val items: Map get() = buildItems() + + 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 fun YamlElement.toMetaItem(descriptor: ItemDescriptor? = null): MetaItem = when (this) { - YamlNull -> Null.asMetaItem() - is YamlLiteral -> content.parseValue().asMetaItem() - is YamlMap -> toMeta().asMetaItem() +public fun YamlElement.toMeta(descriptor: MetaDescriptor? = null): Meta = when (this) { + YamlNull -> Meta(Null) + is YamlLiteral -> Meta(content.parseValue()) + is YamlMap -> toMeta() //We can't return multiple items therefore we create top level node - is YamlList -> YamlMap(mapOf("@yamlArray" to this)).toMetaItem(descriptor) + is YamlList -> YamlMap(mapOf("@yamlArray" to this)).toMeta(descriptor) } public fun YamlMap.toMeta(): Meta = YamlMeta(this) @@ -88,13 +98,13 @@ public fun YamlMap.toMeta(): Meta = YamlMeta(this) @DFExperimental public class YamlMetaFormat(private val meta: Meta) : MetaFormat { - override fun writeMeta(output: Output, meta: Meta, descriptor: NodeDescriptor?) { + override fun writeMeta(output: Output, meta: Meta, descriptor: MetaDescriptor?) { val yaml = meta.toYaml() val string = Yaml.encodeToString(yaml) output.writeUtf8String(string) } - override fun readMeta(input: Input, descriptor: NodeDescriptor?): Meta { + override fun readMeta(input: Input, descriptor: MetaDescriptor?): Meta { val yaml = Yaml.decodeYamlMapFromString(input.readUtf8String()) return yaml.toMeta() } @@ -113,10 +123,10 @@ public class YamlMetaFormat(private val meta: Meta) : MetaFormat { private val default = YamlMetaFormat() - override fun writeMeta(output: Output, meta: Meta, descriptor: NodeDescriptor?): Unit = + override fun writeMeta(output: Output, meta: Meta, descriptor: MetaDescriptor?): Unit = default.writeMeta(output, meta, descriptor) - override fun readMeta(input: Input, descriptor: NodeDescriptor?): Meta = + override fun readMeta(input: Input, descriptor: MetaDescriptor?): Meta = default.readMeta(input, descriptor) } } \ No newline at end of file diff --git a/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/BinaryMetaFormat.kt b/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/BinaryMetaFormat.kt deleted file mode 100644 index dae6183a..00000000 --- a/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/BinaryMetaFormat.kt +++ /dev/null @@ -1,133 +0,0 @@ -package space.kscience.dataforge.io - -import io.ktor.utils.io.core.* -import space.kscience.dataforge.context.Context -import space.kscience.dataforge.meta.* -import space.kscience.dataforge.meta.descriptors.NodeDescriptor -import space.kscience.dataforge.values.* - -/** - * A DataForge-specific simplified binary format for meta - * TODO add description - */ -public object BinaryMetaFormat : MetaFormat, MetaFormatFactory { - override val shortName: String = "bin" - override val key: Short = 0x4249//BI - - override fun invoke(meta: Meta, context: Context): MetaFormat = this - - override fun readMeta(input: Input, descriptor: NodeDescriptor?): Meta { - return (input.readMetaItem() as MetaItemNode).node - } - - private fun Output.writeChar(char: Char) = writeByte(char.code.toByte()) - - private fun Output.writeString(str: String) { - writeInt(str.length) - writeFully(str.encodeToByteArray()) - } - - public fun Output.writeValue(value: Value): Unit = when (value.type) { - ValueType.NUMBER -> when (value.value) { - is Short -> { - writeChar('s') - writeShort(value.short) - } - is Int -> { - writeChar('i') - writeInt(value.int) - } - is Long -> { - writeChar('l') - writeLong(value.long) - } - is Float -> { - writeChar('f') - writeFloat(value.float) - } - else -> { - writeChar('d') - writeDouble(value.double) - } - } - ValueType.STRING -> { - writeChar('S') - writeString(value.string) - } - ValueType.BOOLEAN -> { - if (value.boolean) { - writeChar('+') - } else { - writeChar('-') - } - } - ValueType.NULL -> { - writeChar('N') - } - ValueType.LIST -> { - writeChar('L') - writeInt(value.list.size) - value.list.forEach { - writeValue(it) - } - } - } - - override fun writeMeta( - output: Output, - meta: Meta, - descriptor: space.kscience.dataforge.meta.descriptors.NodeDescriptor?, - ) { - output.writeChar('M') - output.writeInt(meta.items.size) - meta.items.forEach { (key, item) -> - output.writeString(key.toString()) - when (item) { - is MetaItemValue -> { - output.writeValue(item.value) - } - is MetaItemNode -> { - writeObject(output, item.node) - } - } - } - } - - private fun Input.readString(): String { - val length = readInt() - val array = readBytes(length) - return array.decodeToString() - } - - @Suppress("UNCHECKED_CAST") - public fun Input.readMetaItem(): TypedMetaItem { - return when (val keyChar = readByte().toInt().toChar()) { - 'S' -> MetaItemValue(StringValue(readString())) - 'N' -> MetaItemValue(Null) - '+' -> MetaItemValue(True) - '-' -> MetaItemValue(True) - 's' -> MetaItemValue(NumberValue(readShort())) - 'i' -> MetaItemValue(NumberValue(readInt())) - 'l' -> MetaItemValue(NumberValue(readInt())) - 'f' -> MetaItemValue(NumberValue(readFloat())) - 'd' -> MetaItemValue(NumberValue(readDouble())) - 'L' -> { - val length = readInt() - val list = (1..length).map { (readMetaItem() as MetaItemValue).value } - MetaItemValue(Value.of(list)) - } - 'M' -> { - val length = readInt() - val meta = Meta { - (1..length).forEach { _ -> - val name = readString() - val item = readMetaItem() - set(name, item) - } - } - MetaItemNode(meta) - } - else -> error("Unknown serialization key character: $keyChar") - } - } -} \ No newline at end of file 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 fece9a41..a715c66d 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 @@ -15,7 +15,7 @@ import space.kscience.dataforge.names.toName private class PartDescriptor : Scheme() { var offset by int(0) var size by int(0) - var partMeta by node("meta".toName()) + var partMeta by item("meta".toName()) companion object : SchemeSpec(::PartDescriptor) { val MULTIPART_KEY = ENVELOPE_NODE_KEY + "multipart" @@ -86,15 +86,15 @@ public fun EnvelopeBuilder.envelopes( public fun Envelope.parts(): EnvelopeParts { if (data == null) return emptyList() //TODO add zip folder reader - val parts = meta.getIndexed(PARTS_KEY).values.mapNotNull { it.node }.map { + val parts = meta.getIndexed(PARTS_KEY).values.map { PartDescriptor.read(it) } return if (parts.isEmpty()) { - listOf(EnvelopePart(data!!, meta[MULTIPART_KEY].node)) + listOf(EnvelopePart(data!!, meta[MULTIPART_KEY])) } else { parts.map { val binary = data!!.view(it.offset, it.size) - val meta = Laminate(it.partMeta, meta[MULTIPART_KEY].node) + val meta = Laminate(it.partMeta, meta[MULTIPART_KEY]) EnvelopePart(binary, meta) } } diff --git a/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/IOFormat.kt b/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/IOFormat.kt index be5f97ab..c6658013 100644 --- a/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/IOFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/IOFormat.kt @@ -6,13 +6,11 @@ import space.kscience.dataforge.context.Factory import space.kscience.dataforge.io.IOFormat.Companion.NAME_KEY import space.kscience.dataforge.io.IOFormatFactory.Companion.IO_FORMAT_TYPE import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.meta.MetaItemValue import space.kscience.dataforge.meta.MetaRepr import space.kscience.dataforge.misc.Named import space.kscience.dataforge.misc.Type import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.asName -import space.kscience.dataforge.values.Value import kotlin.reflect.KType import kotlin.reflect.typeOf @@ -117,19 +115,19 @@ public object DoubleIOFormat : IOFormat, IOFormatFactory { override fun readObject(input: Input): Double = input.readDouble() } -public object ValueIOFormat : IOFormat, IOFormatFactory { - override fun invoke(meta: Meta, context: Context): IOFormat = this - - override val name: Name = "value".asName() - - override val type: KType get() = typeOf() - - override fun writeObject(output: Output, obj: Value) { - BinaryMetaFormat.run { output.writeValue(obj) } - } - - override fun readObject(input: Input): Value { - return (BinaryMetaFormat.run { input.readMetaItem() } as? MetaItemValue)?.value - ?: error("The item is not a value") - } -} \ No newline at end of file +//public object ValueIOFormat : IOFormat, IOFormatFactory { +// override fun invoke(meta: Meta, context: Context): IOFormat = this +// +// override val name: Name = "value".asName() +// +// override val type: KType get() = typeOf() +// +// override fun writeObject(output: Output, obj: Value) { +// BinaryMetaFormat.run { output.writeValue(obj) } +// } +// +// override fun readObject(input: Input): Value { +// return (BinaryMetaFormat.run { input.readMetaItem() } as? MetaItemValue)?.value +// ?: error("The item is not a value") +// } +//} \ No newline at end of file diff --git a/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/IOPlugin.kt b/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/IOPlugin.kt index 91442603..f0f2e6a4 100644 --- a/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/IOPlugin.kt +++ b/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/IOPlugin.kt @@ -6,7 +6,9 @@ import space.kscience.dataforge.io.IOFormat.Companion.META_KEY import space.kscience.dataforge.io.IOFormat.Companion.NAME_KEY import space.kscience.dataforge.io.IOFormatFactory.Companion.IO_FORMAT_TYPE import space.kscience.dataforge.io.MetaFormatFactory.Companion.META_FORMAT_TYPE -import space.kscience.dataforge.meta.* +import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.meta.get +import space.kscience.dataforge.meta.string import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.toName import kotlin.native.concurrent.ThreadLocal @@ -19,13 +21,13 @@ public class IOPlugin(meta: Meta) : AbstractPlugin(meta) { context.gather>(IO_FORMAT_TYPE).values } - public fun resolveIOFormat(item: MetaItem, type: KClass): IOFormat? { - val key = item.string ?: item.node[NAME_KEY]?.string ?: error("Format name not defined") + public fun resolveIOFormat(item: Meta, type: KClass): IOFormat? { + val key = item.string ?: item[NAME_KEY]?.string ?: error("Format name not defined") val name = key.toName() return ioFormatFactories.find { it.name == name }?.let { @Suppress("UNCHECKED_CAST") if (it.type != type) error("Format type ${it.type} is not the same as requested type $type") - else it.invoke(item.node[META_KEY].node ?: Meta.EMPTY, context) as IOFormat + else it.invoke(item[META_KEY] ?: Meta.EMPTY, context) as IOFormat } } @@ -47,9 +49,9 @@ public class IOPlugin(meta: Meta) : AbstractPlugin(meta) { private fun resolveEnvelopeFormat(name: Name, meta: Meta = Meta.EMPTY): EnvelopeFormat? = envelopeFormatFactories.find { it.name == name }?.invoke(meta, context) - public fun resolveEnvelopeFormat(item: MetaItem): EnvelopeFormat? { - val name = item.string ?: item.node[NAME_KEY]?.string ?: error("Envelope format name not defined") - val meta = item.node[META_KEY].node ?: Meta.EMPTY + public fun resolveEnvelopeFormat(item: Meta): EnvelopeFormat? { + val name = item.string ?: item[NAME_KEY]?.string ?: error("Envelope format name not defined") + val meta = item[META_KEY] ?: Meta.EMPTY return resolveEnvelopeFormat(name.toName(), meta) } @@ -62,7 +64,7 @@ public class IOPlugin(meta: Meta) : AbstractPlugin(meta) { } public companion object : PluginFactory { - public val defaultMetaFormats: List = listOf(JsonMetaFormat, BinaryMetaFormat) + public val defaultMetaFormats: List = listOf(JsonMetaFormat) public val defaultEnvelopeFormats: List = listOf(TaggedEnvelopeFormat, TaglessEnvelopeFormat) diff --git a/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/MetaFormat.kt b/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/MetaFormat.kt index 75435649..2155eb9a 100644 --- a/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/MetaFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/MetaFormat.kt @@ -7,7 +7,7 @@ import io.ktor.utils.io.core.use import space.kscience.dataforge.context.Context import space.kscience.dataforge.io.MetaFormatFactory.Companion.META_FORMAT_TYPE import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.meta.descriptors.NodeDescriptor +import space.kscience.dataforge.meta.descriptors.MetaDescriptor import space.kscience.dataforge.misc.Type import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.asName @@ -30,10 +30,10 @@ public interface MetaFormat : IOFormat { public fun writeMeta( output: Output, meta: Meta, - descriptor: NodeDescriptor? = null, + descriptor: MetaDescriptor? = null, ) - public fun readMeta(input: Input, descriptor: NodeDescriptor? = null): Meta + public fun readMeta(input: Input, descriptor: MetaDescriptor? = null): Meta } @Type(META_FORMAT_TYPE) diff --git a/dataforge-io/src/commonTest/kotlin/space/kscience/dataforge/io/MetaFormatTest.kt b/dataforge-io/src/commonTest/kotlin/space/kscience/dataforge/io/MetaFormatTest.kt index 1e8d7ed7..d142e746 100644 --- a/dataforge-io/src/commonTest/kotlin/space/kscience/dataforge/io/MetaFormatTest.kt +++ b/dataforge-io/src/commonTest/kotlin/space/kscience/dataforge/io/MetaFormatTest.kt @@ -2,11 +2,12 @@ package space.kscience.dataforge.io import kotlinx.serialization.json.* import space.kscience.dataforge.meta.* -import space.kscience.dataforge.meta.JsonMeta.Companion.JSON_ARRAY_KEY import space.kscience.dataforge.values.ListValue +import space.kscience.dataforge.values.double import kotlin.test.Test import kotlin.test.assertEquals + fun Meta.toByteArray(format: MetaFormat = JsonMetaFormat) = buildByteArray { format.writeObject(this@buildByteArray, this@toByteArray) } @@ -16,20 +17,6 @@ fun MetaFormat.fromByteArray(packet: ByteArray): Meta { } class MetaFormatTest { - @Test - fun testBinaryMetaFormat() { - val meta = Meta { - "a" put 22 - "node" put { - "b" put "DDD" - "c" put 11.1 - "array" put doubleArrayOf(1.0, 2.0, 3.0) - } - } - val bytes = meta.toByteArray(BinaryMetaFormat) - val result = BinaryMetaFormat.fromByteArray(bytes) - assertEquals(meta, result) - } @Test fun testJsonMetaFormat() { @@ -50,36 +37,36 @@ class MetaFormatTest { if (meta[it] != result[it]) error("${meta[it]} != ${result[it]}") } - assertEquals(meta, result) + assertEquals(meta, result) } @Test fun testJsonToMeta() { val json = buildJsonArray { //top level array - add(buildJsonArray { - add(JsonPrimitive(88)) - add(buildJsonObject { + addJsonArray { + add(88) + addJsonObject { put("c", "aasdad") put("d", true) - }) - }) + } + } add("value") - add(buildJsonArray { - add(JsonPrimitive(1.0)) - add(JsonPrimitive(2.0)) - add(JsonPrimitive(3.0)) - }) + addJsonArray { + add(1.0) + add(2.0) + add(3.0) + } } - val meta = json.toMeta().node!! + val meta = json.toMeta() - assertEquals(true, meta["$JSON_ARRAY_KEY[0].$JSON_ARRAY_KEY[1].d"].boolean) - assertEquals("value", meta["$JSON_ARRAY_KEY[1]"].string) - assertEquals(listOf(1.0, 2.0, 3.0), meta["$JSON_ARRAY_KEY[2"].value?.list?.map { it.number.toDouble() }) + assertEquals(true, meta["${Meta.JSON_ARRAY_KEY}[0].${Meta.JSON_ARRAY_KEY}[1].d"].boolean) + assertEquals("value", meta["${Meta.JSON_ARRAY_KEY}[1]"].string) + assertEquals(listOf(1.0, 2.0, 3.0), meta["${Meta.JSON_ARRAY_KEY}[2]"]?.value?.list?.map { it.double }) } @Test - fun testJsonStringToMeta(){ + fun testJsonStringToMeta() { val jsonString = """ { "comments": [ @@ -97,8 +84,8 @@ class MetaFormatTest { } """.trimIndent() val json = Json.parseToJsonElement(jsonString) - val meta = json.toMeta().node!! - assertEquals(ListValue.EMPTY, meta["comments"].value) + val meta = json.toMeta() + assertEquals(ListValue.EMPTY, meta["comments"]?.value) } } \ No newline at end of file diff --git a/dataforge-io/src/commonTest/kotlin/space/kscience/dataforge/io/MetaSerializerTest.kt b/dataforge-io/src/commonTest/kotlin/space/kscience/dataforge/io/MetaSerializerTest.kt index 5abd6a5e..332a647c 100644 --- a/dataforge-io/src/commonTest/kotlin/space/kscience/dataforge/io/MetaSerializerTest.kt +++ b/dataforge-io/src/commonTest/kotlin/space/kscience/dataforge/io/MetaSerializerTest.kt @@ -5,7 +5,7 @@ import kotlinx.serialization.cbor.Cbor import kotlinx.serialization.json.Json import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.MetaSerializer -import space.kscience.dataforge.meta.TypedMetaItem +import space.kscience.dataforge.meta.seal import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.toName import kotlin.test.Test @@ -28,7 +28,7 @@ class MetaSerializerTest { fun testMetaSerialization() { val string = JSON_PRETTY.encodeToString(MetaSerializer, meta) println(string) - val restored = JSON_PLAIN.decodeFromString(MetaSerializer, string) + val restored = JSON_PLAIN.decodeFromString(MetaSerializer, string).seal() assertEquals(meta, restored) } @@ -52,7 +52,7 @@ class MetaSerializerTest { @OptIn(ExperimentalSerializationApi::class) @Test fun testMetaItemDescriptor() { - val descriptor = TypedMetaItem.serializer(MetaSerializer).descriptor.getElementDescriptor(0) + val descriptor = MetaSerializer.descriptor.getElementDescriptor(0) println(descriptor) } } \ No newline at end of file diff --git a/dataforge-io/src/jvmMain/kotlin/space/kscience/dataforge/io/fileIO.kt b/dataforge-io/src/jvmMain/kotlin/space/kscience/dataforge/io/fileIO.kt index 454c81fe..05296047 100644 --- a/dataforge-io/src/jvmMain/kotlin/space/kscience/dataforge/io/fileIO.kt +++ b/dataforge-io/src/jvmMain/kotlin/space/kscience/dataforge/io/fileIO.kt @@ -3,7 +3,7 @@ package space.kscience.dataforge.io import io.ktor.utils.io.core.* import io.ktor.utils.io.streams.asOutput 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.isEmpty import space.kscience.dataforge.misc.DFExperimental import java.nio.file.Files @@ -97,7 +97,7 @@ public inline fun IOPlugin.resolveIOFormat(): IOFormat? { public fun IOPlugin.readMetaFile( path: Path, formatOverride: MetaFormat? = null, - descriptor: NodeDescriptor? = null, + descriptor: MetaDescriptor? = null, ): Meta { if (!Files.exists(path)) error("Meta file $path does not exist") @@ -125,7 +125,7 @@ public fun IOPlugin.writeMetaFile( path: Path, meta: Meta, metaFormat: MetaFormatFactory = JsonMetaFormat, - descriptor: NodeDescriptor? = null, + descriptor: MetaDescriptor? = null, ) { val actualPath = if (Files.isDirectory(path)) { path.resolve("@" + metaFormat.name.toString()) 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 bdd83730..9269a327 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 @@ -4,9 +4,13 @@ package space.kscience.dataforge.meta import kotlinx.serialization.json.* import space.kscience.dataforge.meta.descriptors.MetaDescriptor +import space.kscience.dataforge.meta.descriptors.get import space.kscience.dataforge.names.NameToken import space.kscience.dataforge.values.* +private const val jsonArrayKey: String = "@jsonArray" + +public val Meta.Companion.JSON_ARRAY_KEY: String get() = jsonArrayKey /** * @param descriptor reserved for custom serialization in future @@ -15,7 +19,7 @@ public fun Value.toJson(descriptor: MetaDescriptor? = null): JsonElement = when ValueType.NUMBER -> JsonPrimitive(numberOrNull) ValueType.STRING -> JsonPrimitive(string) ValueType.BOOLEAN -> JsonPrimitive(boolean) - ValueType.LIST -> JsonArray(list.map { it.toJson() }) + ValueType.LIST -> JsonArray(list.map { it.toJson(descriptor) }) ValueType.NULL -> JsonNull } @@ -46,7 +50,7 @@ private fun Meta.toJsonWithIndex(descriptor: MetaDescriptor?, index: String?): J //Add index if needed if (index != null) { - pairs += Meta.INDEX_KEY to JsonPrimitive(index) + pairs += (descriptor?.indexKey ?: Meta.INDEX_KEY) to JsonPrimitive(index) } //Add value if needed @@ -59,8 +63,9 @@ private fun Meta.toJsonWithIndex(descriptor: MetaDescriptor?, index: String?): J public fun Meta.toJson(descriptor: MetaDescriptor? = null): JsonElement = toJsonWithIndex(descriptor, null) -public fun JsonObject.toMeta(descriptor: MetaDescriptor? = null): JsonMeta = JsonMeta(this, descriptor) - +/** + * Convert a Json primitive to a [Value] + */ public fun JsonPrimitive.toValue(descriptor: MetaDescriptor?): Value { return when (this) { JsonNull -> Null @@ -68,62 +73,160 @@ public fun JsonPrimitive.toValue(descriptor: MetaDescriptor?): Value { if (isString) { StringValue(content) } else { + //consider using LazyParse content.parseValue() } } } } -public fun JsonElement.toMeta(descriptor: MetaDescriptor? = null): TypedMeta = JsonMeta(this, descriptor) +/** + * Turn this [JsonArray] into a [ListValue] with recursion or return null if it contains objects + */ +public fun JsonElement.toValueOrNull(descriptor: MetaDescriptor?): Value? = when (this) { + is JsonPrimitive -> toValue(descriptor) + is JsonObject -> get(Meta.VALUE_KEY)?.toValueOrNull(descriptor) + is JsonArray -> { + if(isEmpty()) ListValue.EMPTY else { + val values = map { it.toValueOrNull(descriptor) } + values.map { it ?: return null }.asValue() + } + } +} /** - * A meta wrapping json object + * Fill a mutable map with children produced from [element] with given top level [key] */ -public class JsonMeta( - private val json: JsonElement, - private val descriptor: MetaDescriptor? = null -) : TypedMeta { - - private val indexName by lazy { descriptor?.indexKey ?: Meta.INDEX_KEY } - - override val value: Value? by lazy { - when (json) { - is JsonPrimitive -> json.toValue(descriptor) - is JsonObject -> json[Meta.VALUE_KEY]?.let { JsonMeta(it).value } - is JsonArray -> if (json.all { it is JsonPrimitive }) { - //convert array of primitives to ListValue - json.map { (it as JsonPrimitive).toValue(descriptor) }.asValue() +private fun MutableMap.addJsonElement( + key: String, + element: JsonElement, + descriptor: MetaDescriptor? +) { + when (element) { + is JsonPrimitive -> put(NameToken(key), Meta(element.toValue(descriptor))) + is JsonArray -> { + val value = element.toValueOrNull(descriptor) + if (value != null) { + put(NameToken(key), Meta(value)) } else { - null + val indexKey = descriptor?.indexKey ?: Meta.INDEX_KEY + element.forEachIndexed { serial, childElement -> + val index = (childElement as? JsonObject)?.get(indexKey)?.jsonPrimitive?.content + ?: serial.toString() + val child: SealedMeta = when (childElement) { + is JsonObject -> childElement.toMeta(descriptor) + is JsonArray -> { + val childValue = childElement.toValueOrNull(null) + if (childValue == null) { + SealedMeta(null, + hashMapOf().apply { + addJsonElement(Meta.JSON_ARRAY_KEY, childElement, null) + } + ) + } else { + Meta(childValue) + } + } + is JsonPrimitive -> Meta(childElement.toValue(null)) + } + put(NameToken(key, index), child) + } } } - } - - override val items: Map by lazy { - when (json) { - is JsonPrimitive -> emptyMap() - is JsonObject -> json.entries.associate { (name, child) -> - val index = (child as? JsonObject)?.get(indexName)?.jsonPrimitive?.content - val token = NameToken(name, index) - token to JsonMeta(child, descriptor?.children?.get(name)) - } - is JsonArray -> json.mapIndexed { index, child -> - //Use explicit index or or order for index - val tokenIndex = (child as? JsonObject)?.get(indexName)?.jsonPrimitive?.content ?: index.toString() - val token = NameToken(JSON_ARRAY_KEY, tokenIndex) - token to JsonMeta(child) - }.toMap() + is JsonObject -> { + val indexKey = descriptor?.indexKey ?: Meta.INDEX_KEY + val index = element[indexKey]?.jsonPrimitive?.content + put(NameToken(key, index), element.toMeta(descriptor)) } } +} - 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 - */ - public const val JSON_ARRAY_KEY: String = "@jsonArray" +public fun JsonObject.toMeta(descriptor: MetaDescriptor? = null): SealedMeta { + val map = LinkedHashMap() + forEach { (key, element) -> + if (key != Meta.VALUE_KEY) { + map.addJsonElement(key, element, descriptor?.get(key)) + } } -} \ No newline at end of file + return SealedMeta(get(Meta.VALUE_KEY)?.toValueOrNull(descriptor), map) +} + +public fun JsonElement.toMeta(descriptor: MetaDescriptor? = null): SealedMeta = when (this) { + is JsonPrimitive -> Meta(toValue(descriptor)) + is JsonObject -> toMeta(descriptor) + is JsonArray -> SealedMeta(null, + linkedMapOf().apply { + addJsonElement(Meta.JSON_ARRAY_KEY, this@toMeta, null) + } + ) +} + +// +///** +// * A meta wrapping json object +// */ +//public class JsonMeta( +// private val json: JsonElement, +// private val descriptor: MetaDescriptor? = null +//) : TypedMeta { +// +// private val indexName by lazy { descriptor?.indexKey ?: Meta.INDEX_KEY } +// +// override val value: Value? by lazy { +// json.toValueOrNull(descriptor) +// } +// +// private fun MutableMap.appendArray(json: JsonArray, key: String) { +// json.forEachIndexed { index, child -> +// if (child is JsonArray) { +// appendArray(child, key) +// } else { +// //Use explicit index or order for index +// val tokenIndex = (child as? JsonObject) +// ?.get(indexName) +// ?.jsonPrimitive?.content +// ?: index.toString() +// val token = NameToken(key, tokenIndex) +// this[token] = JsonMeta(child) +// } +// } +// } +// +// override val items: Map by lazy { +// val map = HashMap() +// when (json) { +// is JsonObject -> json.forEach { (name, child) -> +// //skip value key +// if (name != Meta.VALUE_KEY) { +// if (child is JsonArray && child.any { it is JsonObject }) { +// map.appendArray(child, name) +// } else { +// +// val index = (child as? JsonObject)?.get(indexName)?.jsonPrimitive?.content +// val token = NameToken(name, index) +// map[token] = JsonMeta(child, descriptor?.get(name)) +// } +// } +// } +// is JsonArray -> { +// //return children only if it is not value +// if (value == null) map.appendArray(json, JSON_ARRAY_KEY) +// } +// else -> { +// //do nothing +// } +// } +// map +// } +// +// 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 +// */ +// public const val JSON_ARRAY_KEY: String = "@jsonArray" +// } +//} \ No newline at end of file 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 d8e7d0f1..e53ccc69 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 @@ -49,7 +49,13 @@ public interface Meta : MetaRepr { } public fun equals(meta1: Meta?, meta2: Meta?): Boolean { - return meta1?.value == meta2?.value && meta1?.items == meta2?.items + if (meta1 == null && meta2 == null) return true + if (meta1 == null || meta2 == null) return false + if (meta1.value != meta2.value) return false + if (meta1.items.keys != meta2.items.keys) return false + return meta1.items.keys.all { + equals(meta1[it], meta2[it]) + } } private val json = Json { 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 673dd2bf..b0cdc3bb 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 @@ -23,7 +23,7 @@ public fun Meta.item(key: Name? = null): MetaDelegate = ReadOnlyProperty { _, pr public fun MetaDelegate.convert( converter: MetaConverter, ): ReadOnlyProperty = ReadOnlyProperty { thisRef, property -> - this@convert.getValue(thisRef, property)?.let(converter::itemToObject) + this@convert.getValue(thisRef, property)?.let(converter::metaToObject) } /* @@ -33,7 +33,7 @@ public fun MetaDelegate.convert( converter: MetaConverter, default: () -> R, ): ReadOnlyProperty = ReadOnlyProperty { thisRef, property -> - this@convert.getValue(thisRef, property)?.let(converter::itemToObject) ?: default() + this@convert.getValue(thisRef, property)?.let(converter::metaToObject) ?: default() } /** diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MetaSerializer.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MetaSerializer.kt index b447faa5..e7daf2fb 100644 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MetaSerializer.kt +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MetaSerializer.kt @@ -1,30 +1,35 @@ package space.kscience.dataforge.meta import kotlinx.serialization.KSerializer -import kotlinx.serialization.builtins.MapSerializer import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.JsonDecoder import kotlinx.serialization.json.JsonElement -import space.kscience.dataforge.names.NameToken -import space.kscience.dataforge.names.NameTokenSerializer +import kotlinx.serialization.json.JsonEncoder /** - * Serialized for meta + * Serialized for [Meta] */ public object MetaSerializer : KSerializer { + private val genericMetaSerializer = SealedMeta.serializer() - private val itemsSerializer: KSerializer> = MapSerializer( - NameTokenSerializer, - MetaSerializer - ) + private val jsonSerializer = JsonElement.serializer() - override val descriptor: SerialDescriptor = JsonElement.serializer().descriptor + override val descriptor: SerialDescriptor = jsonSerializer.descriptor - override fun deserialize(decoder: Decoder): Meta = JsonElement.serializer().deserialize(decoder).toMeta() + override fun deserialize(decoder: Decoder): Meta = if (decoder is JsonDecoder) { + jsonSerializer.deserialize(decoder).toMeta() + } else { + genericMetaSerializer.deserialize(decoder) + } override fun serialize(encoder: Encoder, value: Meta) { - JsonElement.serializer().serialize(encoder, value.toJson()) + if (encoder is JsonEncoder) { + jsonSerializer.serialize(encoder, value.toJson()) + } else { + genericMetaSerializer.serialize(encoder, value.seal()) + } } } @@ -32,8 +37,8 @@ public object MetaSerializer : KSerializer { * A serializer for [MutableMeta] */ public object MutableMetaSerializer : KSerializer { - override val descriptor: SerialDescriptor = MetaSerializer.descriptor + override val descriptor: SerialDescriptor = MetaSerializer.descriptor override fun deserialize(decoder: Decoder): MutableMeta { val meta = decoder.decodeSerializableValue(MetaSerializer) 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 0a740c76..0f820717 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 @@ -1,20 +1,27 @@ package space.kscience.dataforge.meta import kotlinx.serialization.Serializable -import space.kscience.dataforge.misc.DFBuilder import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.names.* import space.kscience.dataforge.values.EnumValue import space.kscience.dataforge.values.Value import space.kscience.dataforge.values.asValue +import kotlin.js.JsName import kotlin.jvm.Synchronized +/** + * Mark a meta builder + */ +@DslMarker +public annotation class MetaBuilder + /** * Mutable variant of [Meta] * TODO documentation */ @Serializable(MutableMetaSerializer::class) +@MetaBuilder public interface MutableMeta : Meta { override val items: Map @@ -30,9 +37,9 @@ public interface MutableMeta : Meta { public operator fun set(name: Name, meta: Meta) /** - * Remove a note at a given [name] if it is present + * Remove a node at a given [name] if it is present */ - public fun removeNode(name: Name) + public fun remove(name: Name) /** * Get existing node or create a new one @@ -109,11 +116,17 @@ public interface MutableMeta : Meta { toName() put repr.toMeta() } + public infix fun String.put(iterable: Iterable) { + setIndexed(toName(), iterable) + } + public infix fun String.put(builder: MutableMeta.() -> Unit) { set(toName(), MutableMeta(builder)) } } +public fun MutableMeta.getOrCreate(string: String): MutableMeta = getOrCreate(string.toName()) + @Serializable(MutableMetaSerializer::class) public interface MutableTypedMeta> : TypedMeta, MutableMeta { /** @@ -125,34 +138,59 @@ public interface MutableTypedMeta> : TypedMeta, Mutab override fun getOrCreate(name: Name): M } -public operator fun MutableMeta.set(key: String, item: Meta?): Unit = - set(key.toName(), item) +public fun > M.getOrCreate(string: String): M = getOrCreate(string.toName()) +public fun MutableMeta.remove(name: String){ + remove(name.toName()) +} -public fun MutableMeta.remove(name: Name): Unit = set(name, null) +// node setters + +public operator fun MutableMeta.set(name: NameToken, value: Meta): Unit = set(name.asName(), value) +public operator fun MutableMeta.set(name: Name, meta: Meta?): Unit { + if (meta == null) { + remove(name) + } else { + set(name, meta) + } +} + +public operator fun MutableMeta.set(key: String, meta: Meta?): Unit { + set(key.toName(), meta) +} + +//value setters + +public operator fun MutableMeta.set(name: NameToken, value: Value?): Unit = set(name.asName(), value) +public operator fun MutableMeta.set(key: String, value: Value?): Unit = set(key.toName(), value) + +public operator fun MutableMeta.set(name: Name, value: String): Unit = set(name, value.asValue()) +public operator fun MutableMeta.set(name: NameToken, value: String): Unit = set(name.asName(), value.asValue()) +public operator fun MutableMeta.set(key: String, value: String): Unit = set(key.toName(), value.asValue()) + +public operator fun MutableMeta.set(name: Name, value: Boolean): Unit = set(name, value.asValue()) +public operator fun MutableMeta.set(name: NameToken, value: Boolean): Unit = set(name.asName(), value.asValue()) +public operator fun MutableMeta.set(key: String, value: Boolean): Unit = set(key.toName(), value.asValue()) + +public operator fun MutableMeta.set(name: Name, value: Number): Unit = set(name, value.asValue()) +public operator fun MutableMeta.set(name: NameToken, value: Number): Unit = set(name.asName(), value.asValue()) +public operator fun MutableMeta.set(key: String, value: Number): Unit = set(key.toName(), value.asValue()) + +public operator fun MutableMeta.set(name: Name, value: List): Unit = set(name, value.asValue()) +public operator fun MutableMeta.set(name: NameToken, value: List): Unit = set(name.asName(), value.asValue()) +public operator fun MutableMeta.set(key: String, value: List): Unit = set(key.toName(), value.asValue()) + +//public fun MutableMeta.set(key: String, index: String, value: Value?): Unit = +// set(key.toName().withIndex(index), value) -public fun MutableMeta.remove(name: String): Unit = remove(name.toName()) /** * Universal unsafe set method */ -public operator fun MutableMeta.set(name: Name, value: Any?) { - when (value) { - null -> remove(name) - else -> set(name, Value.of(value)) - } +public operator fun MutableMeta.set(name: Name, value: Value?) { + getOrCreate(name).value = Value.of(value) } -public operator fun MutableMeta.set(name: NameToken, value: Any?): Unit = - set(name.asName(), value) - -public operator fun MutableMeta.set(key: String, value: Any?): Unit = - set(key.toName(), value) - -public operator fun MutableMeta.set(key: String, index: String, value: Any?): Unit = - set(key.toName().withIndex(index), value) - - /* Same name siblings generation */ public fun MutableMeta.setIndexedItems( @@ -177,10 +215,10 @@ public fun MutableMeta.setIndexed( setIndexedItems(name, metas) { item, index -> indexFactory(item, index) } } -public operator fun > MutableTypedMeta.set(name: Name, metas: Iterable): Unit = +public operator fun MutableMeta.set(name: Name, metas: Iterable): Unit = setIndexed(name, metas) -public operator fun > MutableTypedMeta.set(name: String, metas: Iterable): Unit = +public operator fun MutableMeta.set(name: String, metas: Iterable): Unit = setIndexed(name.toName(), metas) @@ -190,7 +228,7 @@ public operator fun > MutableTypedMeta.set(name: Stri * * node updates node and replaces anything but node * * node list updates node list if number of nodes in the list is the same and replaces anything otherwise */ -public fun > MutableTypedMeta.update(meta: Meta) { +public fun MutableMeta.update(meta: Meta) { meta.valueSequence().forEach { (name, value) -> set(name, value) } @@ -217,38 +255,24 @@ public operator fun > MutableTypedMeta.set(name: Name } } -///** -// * Create a mutable item provider that uses given provider for default values if those are not found in this provider. -// * Changes are propagated only to this provider. -// */ -//public fun > M.withDefault(default: Meta?): MutableTypedMeta<*> = -// if (default == null || default.isEmpty()) { -// //Optimize for use with empty default -// this -// } else object : MutableTypedMeta { -// override fun set(name: Name, item: MetaItem?) { -// this@withDefault.set(name, item) -// } -// -// override fun getItem(name: Name): MetaItem? = this@withDefault.getItem(name) ?: default.getItem(name) -// } - /** * A general implementation of mutable [Meta] which implements both [MutableTypedMeta] and [ObservableMeta]. * The implementation uses blocking synchronization on mutation on JVM */ -@DFBuilder private class MutableMetaImpl( - override var value: Value?, + value: Value?, children: Map = emptyMap() ) : ObservableMutableMeta { - private val children: LinkedHashMap = + override var value = value + @Synchronized set + + 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() @@ -267,60 +291,56 @@ private class MutableMetaImpl( } - private fun adoptBy(parent: MutableMetaImpl, key: NameToken) { + private fun ObservableMeta.adoptBy(parent: MutableMetaImpl, key: NameToken) { onChange(parent) { name -> parent.changed(key + name) } } - -// 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) -// } -// } + override fun attach(name: Name, node: ObservableMutableMeta) { + when (name.length) { + 0 -> error("Can't set a meta with empty name") + 1 -> { + replaceItem(name.first(), get(name), node) + } + 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 -> throw IllegalArgumentException("Can't create a node with empty name") - 1 -> { - 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 + private fun createNode(name: Name): ObservableMutableMeta = when (name.length) { + 0 -> throw IllegalArgumentException("Can't create a node with empty name") + 1 -> { + val newNode = MutableMetaImpl(null) + children[name.first()] = newNode + newNode.adoptBy(this, name.first()) + newNode + } //do not notify, no value changed + else -> getOrCreate(name.first().asName()).getOrCreate(name.cutFirst()) } override fun getOrCreate(name: Name): ObservableMutableMeta = get(name) ?: createNode(name) - override fun removeNode(name: 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())?.removeNode(name.lastOrNull()!!.asName()) + else -> get(name.cutLast())?.remove(name.lastOrNull()!!.asName()) } } + @Synchronized private fun replaceItem( key: NameToken, oldItem: ObservableMutableMeta?, - newItem: MutableMetaImpl? + newItem: ObservableMutableMeta? ) { if (oldItem != newItem) { if (newItem == null) { - children.remove(key) + //remove child and remove stale listener + children.remove(key)?.removeListener(this) } else { newItem.adoptBy(this, key) children[key] = newItem @@ -364,7 +384,7 @@ private class MutableMetaImpl( * Append the node with a same-name-sibling, automatically generating numerical index */ @DFExperimental -public fun MutableMeta.append(name: Name, value: Any?) { +public fun MutableMeta.append(name: Name, value: Value?) { require(!name.isEmpty()) { "Name could not be empty for append operation" } val newIndex = name.lastOrNull()!!.index if (newIndex != null) { @@ -376,7 +396,7 @@ public fun MutableMeta.append(name: Name, value: Any?) { } @DFExperimental -public fun MutableMeta.append(name: String, value: Any?): Unit = append(name.toName(), value) +public fun MutableMeta.append(name: String, value: Value?): Unit = append(name.toName(), value) ///** // * Apply existing node with given [builder] or create a new element with it. @@ -398,12 +418,15 @@ public fun Meta.toMutableMeta(): ObservableMutableMeta = MutableMetaImpl(value, public fun Meta.asMutableMeta(): MutableMeta = (this as? MutableMeta) ?: toMutableMeta() +@JsName("newMutableMeta") +public fun MutableMeta(): ObservableMutableMeta = MutableMetaImpl(null) + /** * Build a [MutableMeta] using given transformation */ @Suppress("FunctionName") -public fun MutableMeta(builder: MutableMeta.() -> Unit = {}): ObservableMutableMeta = - MutableMetaImpl(null).apply(builder) +public inline fun MutableMeta(builder: MutableMeta.() -> Unit = {}): ObservableMutableMeta = + MutableMeta().apply(builder) /** @@ -411,4 +434,32 @@ public fun MutableMeta(builder: MutableMeta.() -> Unit = {}): ObservableMutableM * The listeners of the original Config are not retained. */ public inline fun Meta.copy(block: MutableMeta.() -> Unit = {}): Meta = - toMutableMeta().apply(block) \ No newline at end of file + toMutableMeta().apply(block) + + +private class MutableMetaWithDefault(val source: MutableMeta, val default: Meta, val name: Name) : + MutableMeta by source { + override val items: Map + get() = (source.items.keys + default.items.keys).associateWith { + MutableMetaWithDefault(source, default, name + it) + } + + override var value: Value? + get() = source[name]?.value ?: default[name]?.value + set(value) { + source[name] = value + } + + 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) +} + +/** + * Create a mutable item provider that uses given provider for default values if those are not found in this provider. + * Changes are propagated only to this provider. + */ +public fun MutableMeta.withDefault(default: Meta?): MutableMeta = if (default == null || default.isEmpty()) { + //Optimize for use with empty default + this +} else MutableMetaWithDefault(this, default, Name.EMPTY) 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 ecb04144..3ff71289 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 @@ -32,10 +32,10 @@ public fun MutableMetaDelegate.convert( ): ReadWriteProperty = object : ReadWriteProperty { override fun getValue(thisRef: Any?, property: KProperty<*>): R? = - this@convert.getValue(thisRef, property)?.let(converter::itemToObject) + this@convert.getValue(thisRef, property)?.let(converter::metaToObject) override fun setValue(thisRef: Any?, property: KProperty<*>, value: R?) { - val item = value?.let(converter::objectToMetaItem) + val item = value?.let(converter::objectToMeta) this@convert.setValue(thisRef, property, item) } } @@ -46,10 +46,10 @@ public fun MutableMetaDelegate.convert( ): ReadWriteProperty = object : ReadWriteProperty { override fun getValue(thisRef: Any?, property: KProperty<*>): R = - this@convert.getValue(thisRef, property)?.let(converter::itemToObject) ?: default() + this@convert.getValue(thisRef, property)?.let(converter::metaToObject) ?: default() override fun setValue(thisRef: Any?, property: KProperty<*>, value: R) { - val item = value.let(converter::objectToMetaItem) + val item = value.let(converter::objectToMeta) this@convert.setValue(thisRef, property, item) } } 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 71c67366..5ea37757 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,7 +32,7 @@ public interface ObservableMeta : Meta { /** * A [Meta] which is both observable and mutable */ -public interface ObservableMutableMeta : ObservableMeta, MutableMeta, TypedMeta +public interface ObservableMutableMeta : ObservableMeta, MutableMeta, MutableTypedMeta private class ObservableMetaWrapper( val origin: MutableMeta, @@ -68,8 +68,8 @@ private class ObservableMetaWrapper( get(name) ?: ObservableMetaWrapper(origin.getOrCreate(name)) - override fun removeNode(name: Name) { - origin.removeNode(name) + override fun remove(name: Name) { + origin.remove(name) changed(name) } @@ -84,10 +84,18 @@ private class ObservableMetaWrapper( override fun toMeta(): Meta { return origin.toMeta() } + + override fun attach(name: Name, node: ObservableMutableMeta) { + TODO("Not yet implemented") + } } -public fun MutableMeta.asObservable(): ObservableMeta = - (this as? ObservableMeta) ?: ObservableMetaWrapper(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) /** 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 574b57b0..55fb2360 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,39 +2,43 @@ package space.kscience.dataforge.meta import space.kscience.dataforge.meta.descriptors.* import space.kscience.dataforge.names.Name +import space.kscience.dataforge.names.NameToken +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]. * Default item provider and [NodeDescriptor] are optional */ -public open class Scheme internal constructor( +public open class Scheme( source: MutableMeta = MutableMeta() -) : Described, ObservableMutableMeta, Meta by source { +) : Described, MutableMeta, ObservableMeta, Meta by source { private var source = source.asObservable() final override var descriptor: MetaDescriptor? = null internal set + override var value: Value? + get() = source.value + set(value) { + source.value = value + } + + override val items: Map get() = source.items internal fun wrap( items: MutableMeta, preserveDefault: Boolean = false ) { - this.source = if (preserveDefault) items.withDefault(this.source) else items + this.source = (if (preserveDefault) items.withDefault(this.source) else items).asObservable() } -// -// /** -// * Get a property with default -// */ -// override fun getItem(name: Name): MetaItem? = source[name] ?: descriptor?.get(name)?.defaultValue /** - * Check if property with given [name] could be assigned to [item] + * Check if property with given [name] could be assigned to [meta] */ - public open fun validate(name: Name, item: Meta?): Boolean { + public open fun validate(name: Name, meta: Meta?): Boolean { val descriptor = descriptor?.get(name) - return descriptor?.validateItem(item) ?: true + return descriptor?.validate(meta) ?: true } /** @@ -51,11 +55,25 @@ public open class Scheme internal constructor( } } - override fun toMeta(): Laminate = Laminate(source, descriptor?.defaultMeta) + override fun toMeta(): Laminate = Laminate(source, descriptor?.defaultNode) + + override fun remove(name: Name) { + source.remove(name) + } + + override fun getOrCreate(name: Name): MutableMeta = source.getOrCreate(name) + + override fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit) { + source.onChange(owner ?: this, callback) + } + + override fun removeListener(owner: Any?) { + source.removeListener(owner ?: this) + } } /** - * Relocate scheme target onto given [MutableTypedMeta]. Old provider does not get updates anymore. + * Relocate scheme target onto given [MutableMeta]. Old provider does not get updates anymore. * Current state of the scheme used as a default. */ public fun T.retarget(provider: MutableMeta): T = apply { @@ -74,8 +92,8 @@ public open class SchemeSpec( private val builder: () -> T, ) : Specification, Described { - override fun read(items: Meta): T = empty().also { - it.wrap(MutableMeta().withDefault(items)) + override fun read(source: Meta): T = empty().also { + it.wrap(MutableMeta().withDefault(source)) } override fun write(target: MutableMeta): T = empty().also { 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 4d07ab4d..6d2ea9fc 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 @@ -1,5 +1,6 @@ package space.kscience.dataforge.meta +import kotlinx.serialization.Serializable import space.kscience.dataforge.names.NameToken import space.kscience.dataforge.values.Value @@ -8,6 +9,7 @@ import space.kscience.dataforge.values.Value * * If the argument is possibly mutable node, it is copied on creation */ +@Serializable public class SealedMeta internal constructor( override val value: Value?, override val items: Map @@ -28,6 +30,6 @@ public fun Meta.seal(): SealedMeta = this as? SealedMeta ?: SealedMeta(value, it public fun Meta(value: Value): SealedMeta = SealedMeta(value, emptyMap()) @Suppress("FunctionName") -public fun Meta(builder: MutableMeta.() -> Unit): SealedMeta = +public inline fun Meta(builder: MutableMeta.() -> Unit): SealedMeta = MutableMeta(builder).seal() diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/Specification.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/Specification.kt index f207c0cf..9c035fc9 100644 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/Specification.kt +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/Specification.kt @@ -33,15 +33,15 @@ public interface ReadOnlySpecification { */ public interface Specification : ReadOnlySpecification { /** - * Wrap [MutableTypedMeta], using it as inner storage (changes to [Specification] are reflected on [MutableTypedMeta] + * Wrap [MutableMeta], using it as inner storage (changes to [Specification] are reflected on [MutableMeta] */ public fun write(target: MutableMeta): T } /** - * Update a [MutableTypedMeta] using given specification + * Update a [MutableMeta] using given specification */ -public fun , T : Any> M.update( +public fun MutableMeta.update( spec: Specification, action: T.() -> Unit ): T = spec.write(this).apply(action) diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/Described.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/Described.kt index b9f72f75..b21363ab 100644 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/Described.kt +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/Described.kt @@ -5,20 +5,4 @@ package space.kscience.dataforge.meta.descriptors */ public interface Described { public val descriptor: MetaDescriptor? - - public companion object { - //public const val DESCRIPTOR_NODE: String = "@descriptor" - } -} - -///** -// * If meta node supplies explicit descriptor, return it, otherwise try to use descriptor node from meta itself -// */ -//val MetaRepr.descriptor: NodeDescriptor? -// get() { -// return if (this is Described) { -// descriptor -// } else { -// toMeta()[DESCRIPTOR_NODE].node?.let { NodeDescriptor.wrap(it) } -// } -// } \ No newline at end of file +} \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/MetaDescriptor.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/MetaDescriptor.kt index a3f523e5..d7d640ee 100644 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/MetaDescriptor.kt +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/MetaDescriptor.kt @@ -2,6 +2,8 @@ package space.kscience.dataforge.meta.descriptors import kotlinx.serialization.Serializable import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.meta.get +import space.kscience.dataforge.meta.set import space.kscience.dataforge.names.* import space.kscience.dataforge.values.Value import space.kscience.dataforge.values.ValueType @@ -12,7 +14,7 @@ import space.kscience.dataforge.values.ValueType * @param children child descriptors for this node * @param multiple True if same name siblings with this name are allowed * @param required True if the item is required - * @param type list of allowed types for [Meta.value], null if all values are allowed + * @param type list of allowed types for [Meta.value], null if all values are allowed. If the type is [ValueType.NULL], no value is allowed for this node. * @param indexKey An index field by which this node is identified in case of same name siblings construct * @param defaultValue the default [Meta.value] for the node * @param attributes additional attributes of this descriptor. For example validation and widget parameters @@ -27,7 +29,13 @@ public data class MetaDescriptor( public val indexKey: String = Meta.INDEX_KEY, public val defaultValue: Value? = null, public val attributes: Meta = Meta.EMPTY, -) +) { + public companion object { + internal const val ALLOWED_VALUES_KEY = "allowedValues" + } +} + +public val MetaDescriptor.allowedValues: List? get() = attributes[MetaDescriptor.ALLOWED_VALUES_KEY]?.value?.list public operator fun MetaDescriptor.get(name: Name): MetaDescriptor? = when (name.length) { 0 -> this @@ -37,24 +45,22 @@ public operator fun MetaDescriptor.get(name: Name): MetaDescriptor? = when (name public operator fun MetaDescriptor.get(name: String): MetaDescriptor? = get(name.toName()) -public class MetaDescriptorBuilder { - public var info: String? = null - public var children: MutableMap = hashMapOf() - public var multiple: Boolean = false - public var required: Boolean = false - public var type: List? = null - public var indexKey: String = Meta.INDEX_KEY - public var default: Value? = null - public var attributes: Meta = Meta.EMPTY +/** + * A node constructed of default values for this descriptor and its children + */ +public val MetaDescriptor.defaultNode: Meta + get() = Meta { + defaultValue?.let { defaultValue -> + this.value = defaultValue + } + children.forEach { (key, descriptor) -> + set(key, descriptor.defaultNode) + } + } - internal fun build(): MetaDescriptor = MetaDescriptor( - info = info, - children = children, - multiple = multiple, - required = required, - type = type, - indexKey = indexKey, - defaultValue = default, - attributes = attributes - ) -} \ No newline at end of file +/** + * Check if given item suits the descriptor + */ +public fun MetaDescriptor.validate(item: Meta?): Boolean = (item != null || !required) && + allowedValues?.let { item?.value in it } ?: true && + children.all { (key, childDescriptor) -> childDescriptor.validate(item?.get(key)) } \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/MetaDescriptorBuilder.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/MetaDescriptorBuilder.kt new file mode 100644 index 00000000..0c25793c --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/MetaDescriptorBuilder.kt @@ -0,0 +1,124 @@ +package space.kscience.dataforge.meta.descriptors + +import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.meta.MutableMeta +import space.kscience.dataforge.meta.get +import space.kscience.dataforge.meta.set +import space.kscience.dataforge.names.* +import space.kscience.dataforge.values.Value +import space.kscience.dataforge.values.ValueType +import space.kscience.dataforge.values.asValue + +public class MetaDescriptorBuilder { + public var info: String? = null + public var children: MutableMap = hashMapOf() + public var multiple: Boolean = false + public var required: Boolean = false + + public var type: List? = null + + public fun type(primaryType: ValueType, vararg otherTypes: ValueType) { + type = listOf(primaryType, *otherTypes) + } + + public var indexKey: String = Meta.INDEX_KEY + public var default: Value? = null + + public fun default(value: Any?) { + default = Value.of(value) + } + + public var attributes: MutableMeta = MutableMeta() + + public fun item(name: Name, block: MetaDescriptorBuilder.() -> Unit) { + when (name.length) { + 0 -> apply(block) + 1 -> { + val target = MetaDescriptorBuilder().apply(block) + children[name.first().body] = target + } + else -> { + children.getOrPut(name.first().body) { MetaDescriptorBuilder() }.item(name.cutFirst(), block) + } + } + } + + public var allowedValues: List + get() = attributes[MetaDescriptor.ALLOWED_VALUES_KEY]?.value?.list ?: emptyList() + set(value) { + attributes[MetaDescriptor.ALLOWED_VALUES_KEY] = value + } + + + public fun allowedValues(vararg values: Any) { + allowedValues = values.map { Value.of(it) } + } + + internal fun build(): MetaDescriptor = MetaDescriptor( + info = info, + children = children.mapValues { it.value.build() }, + multiple = multiple, + required = required, + type = type, + indexKey = indexKey, + defaultValue = default, + attributes = attributes + ) +} + +public fun MetaDescriptorBuilder.item(name: String, block: MetaDescriptorBuilder.() -> Unit) { + item(name.toName(), block) +} + +public fun MetaDescriptor(block: MetaDescriptorBuilder.() -> Unit): MetaDescriptor = + MetaDescriptorBuilder().apply(block).build() + +/** + * Create and configure child value descriptor + */ +public fun MetaDescriptorBuilder.value( + name: Name, + type: ValueType, + vararg additionalTypes: ValueType, + block: MetaDescriptorBuilder.() -> Unit +) { + item(name) { + type(type, *additionalTypes) + block() + } +} + +public fun MetaDescriptorBuilder.value( + name: String, + type: ValueType, + vararg additionalTypes: ValueType, + block: MetaDescriptorBuilder.() -> Unit +) { + value(name.toName(), type, additionalTypes = additionalTypes, block) +} + +/** + * Create and configure child value descriptor + */ +public fun MetaDescriptorBuilder.node(name: Name, block: MetaDescriptorBuilder.() -> Unit) { + item(name) { + type(ValueType.NULL) + block() + } +} + +public fun MetaDescriptorBuilder.node(name: String, block: MetaDescriptorBuilder.() -> Unit) { + node(name.toName(), block) +} + +public inline fun > MetaDescriptorBuilder.enum( + key: Name, + default: E?, + crossinline modifier: MetaDescriptorBuilder.() -> Unit = {}, +): Unit = value(key,ValueType.STRING) { + default?.let { + this.default = default.asValue() + } + allowedValues = enumValues().map { it.asValue() } + modifier() +} \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/descriptorExtensions.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/descriptorExtensions.kt deleted file mode 100644 index e8846e7b..00000000 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/descriptorExtensions.kt +++ /dev/null @@ -1,18 +0,0 @@ -package space.kscience.dataforge.meta.descriptors - -import space.kscience.dataforge.names.Name -import space.kscience.dataforge.values.ValueType -import space.kscience.dataforge.values.asValue - -public inline fun > MetaDescriptorBuilder.enum( - key: Name, - default: E?, - crossinline modifier: MetaDescriptor.() -> Unit = {}, -): Unit = value(key) { - type(ValueType.STRING) - default?.let { - default(default) - } - allowedValues = enumValues().map { it.asValue() } - modifier() -} \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/transformations/MetaConverter.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/transformations/MetaConverter.kt index 3945205c..52de57bc 100644 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/transformations/MetaConverter.kt +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/transformations/MetaConverter.kt @@ -1,124 +1,89 @@ package space.kscience.dataforge.meta.transformations import space.kscience.dataforge.meta.* -import space.kscience.dataforge.values.* +import space.kscience.dataforge.values.Value +import space.kscience.dataforge.values.asValue /** - * A converter of generic object to and from [TypedMetaItem] + * A converter of generic object to and from [Meta] */ public interface MetaConverter { - public fun itemToObject(item: MetaItem): T - public fun objectToMetaItem(obj: T): MetaItem + public fun metaToObject(meta: Meta): T? + public fun objectToMeta(obj: T): Meta public companion object { - public val item: MetaConverter = object : MetaConverter { - override fun itemToObject(item: MetaItem): MetaItem = item - override fun objectToMetaItem(obj: MetaItem): MetaItem = obj - } - public val meta: MetaConverter = object : MetaConverter { - override fun itemToObject(item: MetaItem): Meta = when (item) { - is MetaItemNode -> item.node - is MetaItemValue -> item.value.toMeta() - } - - override fun objectToMetaItem(obj: Meta): MetaItem = MetaItemNode(obj) + override fun metaToObject(meta: Meta): Meta = meta + override fun objectToMeta(obj: Meta): Meta = obj } public val value: MetaConverter = object : MetaConverter { - override fun itemToObject(item: MetaItem): Value = when (item) { - is MetaItemNode -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value") - is MetaItemValue -> item.value - } - - override fun objectToMetaItem(obj: Value): MetaItem = MetaItemValue(obj) + override fun metaToObject(meta: Meta): Value? = meta.value + override fun objectToMeta(obj: Value): Meta = Meta(obj) } public val string: MetaConverter = object : MetaConverter { - override fun itemToObject(item: MetaItem): String = when (item) { - is MetaItemNode -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value") - is MetaItemValue -> item.value - }.string - - override fun objectToMetaItem(obj: String): MetaItem = MetaItemValue(obj.asValue()) + override fun metaToObject(meta: Meta): String? = meta.string + override fun objectToMeta(obj: String): Meta = Meta(obj.asValue()) } public val boolean: MetaConverter = object : MetaConverter { - override fun itemToObject(item: MetaItem): Boolean = when (item) { - is MetaItemNode -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value") - is MetaItemValue -> item.value - }.boolean - - override fun objectToMetaItem(obj: Boolean): MetaItem = MetaItemValue(obj.asValue()) + override fun metaToObject(meta: Meta): Boolean? = meta.boolean + override fun objectToMeta(obj: Boolean): Meta = Meta(obj.asValue()) } public val number: MetaConverter = object : MetaConverter { - override fun itemToObject(item: MetaItem): Number = when (item) { - is MetaItemNode -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value") - is MetaItemValue -> item.value - }.number - - override fun objectToMetaItem(obj: Number): MetaItem = MetaItemValue(obj.asValue()) + override fun metaToObject(meta: Meta): Number? = meta.number + override fun objectToMeta(obj: Number): Meta = Meta(obj.asValue()) } public val double: MetaConverter = object : MetaConverter { - override fun itemToObject(item: MetaItem): Double = when (item) { - is MetaItemNode -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value") - is MetaItemValue -> item.value - }.double + override fun metaToObject(meta: Meta): Double? = meta.double - override fun objectToMetaItem(obj: Double): MetaItem = MetaItemValue(obj.asValue()) + override fun objectToMeta(obj: Double): Meta = Meta(obj.asValue()) } public val float: MetaConverter = object : MetaConverter { - override fun itemToObject(item: MetaItem): Float = when (item) { - is MetaItemNode -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value") - is MetaItemValue -> item.value - }.float + override fun metaToObject(meta: Meta): Float? = meta.float - override fun objectToMetaItem(obj: Float): MetaItem = MetaItemValue(obj.asValue()) + override fun objectToMeta(obj: Float): Meta = Meta(obj.asValue()) } public val int: MetaConverter = object : MetaConverter { - override fun itemToObject(item: MetaItem): Int = when (item) { - is MetaItemNode -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value") - is MetaItemValue -> item.value - }.int + override fun metaToObject(meta: Meta): Int? = meta.int - override fun objectToMetaItem(obj: Int): MetaItem = MetaItemValue(obj.asValue()) + override fun objectToMeta(obj: Int): Meta = Meta(obj.asValue()) } public val long: MetaConverter = object : MetaConverter { - override fun itemToObject(item: MetaItem): Long = when (item) { - is MetaItemNode -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value") - is MetaItemValue -> item.value - }.long + override fun metaToObject(meta: Meta): Long? = meta.long - override fun objectToMetaItem(obj: Long): MetaItem = MetaItemValue(obj.asValue()) + override fun objectToMeta(obj: Long): Meta = Meta(obj.asValue()) } public inline fun > enum(): MetaConverter = object : MetaConverter { @Suppress("USELESS_CAST") - override fun itemToObject(item: MetaItem): E = item.enum() as? E ?: error("The Item is not a Enum") + override fun metaToObject(meta: Meta): E = meta.enum() as? E ?: error("The Item is not a Enum") - override fun objectToMetaItem(obj: E): MetaItem = MetaItemValue(obj.asValue()) + override fun objectToMeta(obj: E): Meta = Meta(obj.asValue()) } - public fun valueList(writer: (T) -> Value = { Value.of(it)}, reader: (Value) -> T): MetaConverter> = + public fun valueList( + writer: (T) -> Value = { Value.of(it) }, + reader: (Value) -> T + ): MetaConverter> = object : MetaConverter> { - override fun itemToObject(item: MetaItem): List = - item.value?.list?.map(reader) ?: error("The item is not a value list") + override fun metaToObject(meta: Meta): List = + meta.value?.list?.map(reader) ?: error("The item is not a value list") - override fun objectToMetaItem(obj: List): MetaItem = - MetaItemValue(obj.map(writer).asValue()) + override fun objectToMeta(obj: List): Meta = Meta(obj.map(writer).asValue()) } } } -public fun MetaConverter.nullableItemToObject(item: MetaItem?): T? = item?.let { itemToObject(it) } -public fun MetaConverter.nullableObjectToMetaItem(obj: T?): MetaItem? = obj?.let { objectToMetaItem(it) } +public fun MetaConverter.nullableMetaToObject(item: Meta?): T? = item?.let { metaToObject(it) } +public fun MetaConverter.nullableObjectToMeta(obj: T?): Meta? = obj?.let { objectToMeta(it) } -public fun MetaConverter.metaToObject(meta: Meta): T = itemToObject(MetaItemNode(meta)) -public fun MetaConverter.valueToObject(value: Value): T = itemToObject(MetaItemValue(value)) +public fun MetaConverter.valueToObject(value: Value): T? = metaToObject(Meta(value)) diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/transformations/MetaTransformation.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/transformations/MetaTransformation.kt index e80b64bb..138deb73 100644 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/transformations/MetaTransformation.kt +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/transformations/MetaTransformation.kt @@ -13,7 +13,7 @@ public interface TransformationRule { /** * Check if this transformation should be applied to a node with given name and value */ - public fun matches(name: Name, item: MetaItem?): Boolean + public fun matches(name: Name, item: Meta?): Boolean /** * Select all items to be transformed. Item could be a value as well as node @@ -26,7 +26,7 @@ public interface TransformationRule { /** * Apply transformation for a single item (Node or Value) to the target */ - public fun transformItem(name: Name, item: MetaItem?, target: MutableTypedMeta): Unit + public fun transformItem(name: Name, item: Meta?, target: MutableMeta): Unit } /** @@ -34,14 +34,14 @@ public interface TransformationRule { */ public data class KeepTransformationRule(val selector: (Name) -> Boolean) : TransformationRule { - override fun matches(name: Name, item: MetaItem?): Boolean { + override fun matches(name: Name, item: Meta?): Boolean { return selector(name) } override fun selectItems(meta: Meta): Sequence = meta.nodeSequence().map { it.first }.filter(selector) - override fun transformItem(name: Name, item: MetaItem?, target: MutableTypedMeta) { + override fun transformItem(name: Name, item: Meta?, target: MutableMeta) { if (selector(name)) target.set(name, item) } } @@ -51,15 +51,15 @@ public data class KeepTransformationRule(val selector: (Name) -> Boolean) : */ public data class SingleItemTransformationRule( val from: Name, - val transform: MutableTypedMeta.(Name, MetaItem?) -> Unit, + val transform: MutableMeta.(Name, Meta?) -> Unit, ) : TransformationRule { - override fun matches(name: Name, item: MetaItem?): Boolean { + override fun matches(name: Name, item: Meta?): Boolean { return name == from } override fun selectItems(meta: Meta): Sequence = sequenceOf(from) - override fun transformItem(name: Name, item: MetaItem?, target: MutableTypedMeta) { + override fun transformItem(name: Name, item: Meta?, target: MutableMeta) { if (name == this.from) { target.transform(name, item) } @@ -68,13 +68,13 @@ public data class SingleItemTransformationRule( public data class RegexItemTransformationRule( val from: Regex, - val transform: MutableTypedMeta.(name: Name, MatchResult, MetaItem?) -> Unit, + val transform: MutableMeta.(name: Name, MatchResult, Meta?) -> Unit, ) : TransformationRule { - override fun matches(name: Name, item: MetaItem?): Boolean { + override fun matches(name: Name, item: Meta?): Boolean { return from.matches(name.toString()) } - override fun transformItem(name: Name, item: MetaItem?, target: MutableTypedMeta) { + override fun transformItem(name: Name, item: Meta?, target: MutableMeta) { val match = from.matchEntire(name.toString()) if (match != null) { target.transform(name, match, item) @@ -105,7 +105,7 @@ public value class MetaTransformation(private val transformations: Collection rule.selectItems(source).forEach { name -> rule.transformItem(name, source[name], this) @@ -131,8 +131,9 @@ public value class MetaTransformation(private val transformations: Collection + public fun bind(source: ObservableMeta, target: MutableMeta) { + source.onChange(target) { name -> + val newItem = source[name] transformations.forEach { t -> if (t.matches(name, newItem)) { t.transformItem(name, newItem, target) @@ -172,15 +173,15 @@ public class MetaTransformationBuilder { */ public fun keep(regex: String) { transformations.add( - RegexItemTransformationRule(regex.toRegex()) { name, _, metaItem -> - set(name, metaItem) + RegexItemTransformationRule(regex.toRegex()) { name, _, Meta -> + set(name, Meta) }) } /** * Move an item from [from] to [to], optionally applying [operation] it defined */ - public fun move(from: Name, to: Name, operation: (MetaItem?) -> Any? = { it }) { + public fun move(from: Name, to: Name, operation: (Meta?) -> Meta? = { it }) { transformations.add( SingleItemTransformationRule(from) { _, item -> set(to, operation(item)) 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 3c708a2d..d470f366 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 @@ -1,6 +1,7 @@ package space.kscience.dataforge.values import kotlinx.serialization.Serializable +import kotlin.jvm.JvmInline /** @@ -153,16 +154,10 @@ public class NumberValue(public val number: Number) : Value { override fun hashCode(): Int = numberOrNull.hashCode() } -public class StringValue(public val string: String) : Value { +@JvmInline +public value class StringValue(public val string: String) : Value { override val value: Any get() = string override val type: ValueType get() = ValueType.STRING - - override fun equals(other: Any?): Boolean { - return this.string == (other as? Value)?.string - } - - override fun hashCode(): Int = string.hashCode() - override fun toString(): String = string } @@ -204,6 +199,9 @@ public class ListValue(override val list: List) : Value, Iterable } } +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) public fun Boolean.asValue(): Value = if (this) True else False 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 e1adfb45..7ccc8f43 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 @@ -1,7 +1,6 @@ package space.kscience.dataforge.values import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.meta.MutableMeta /** * Check if value is null @@ -35,4 +34,4 @@ public val Value.doubleArray: DoubleArray } -public fun Value.toMeta(): MutableMeta = Meta { Meta.VALUE_KEY put this } \ No newline at end of file +public fun Value.toMeta(): Meta = Meta(this) \ No newline at end of file diff --git a/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/JsonMetaTest.kt b/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/JsonMetaTest.kt index 6863a1a0..fba9b596 100644 --- a/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/JsonMetaTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/JsonMetaTest.kt @@ -1,7 +1,8 @@ package space.kscience.dataforge.meta import kotlinx.serialization.json.* -import space.kscience.dataforge.meta.descriptors.NodeDescriptor +import space.kscience.dataforge.meta.descriptors.MetaDescriptor +import space.kscience.dataforge.meta.descriptors.item import kotlin.test.Test import kotlin.test.assertEquals @@ -30,8 +31,8 @@ class JsonMetaTest { }) } - val descriptor = NodeDescriptor{ - node("nodeArray"){ + val descriptor = MetaDescriptor { + item("nodeArray") { indexKey = "index" } } @@ -43,8 +44,17 @@ class JsonMetaTest { //println(meta) val reconstructed = meta.toJson(descriptor) println(reconstructed) - assertEquals(2, - reconstructed["nodeArray"]?.jsonArray?.get(1)?.jsonObject?.get("index")?.jsonPrimitive?.int) - assertEquals(json,reconstructed) + assertEquals( + 2, + reconstructed + .jsonObject["nodeArray"] + ?.jsonArray + ?.get(1) + ?.jsonObject + ?.get("index") + ?.jsonPrimitive + ?.int + ) + assertEquals(json, reconstructed) } } \ No newline at end of file diff --git a/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/MetaBuilderTest.kt b/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/MetaBuilderTest.kt index 50914147..3667110f 100644 --- a/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/MetaBuilderTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/MetaBuilderTest.kt @@ -1,5 +1,6 @@ package space.kscience.dataforge.meta +import space.kscience.dataforge.values.Value import space.kscience.dataforge.values.asValue import kotlin.test.Test import kotlin.test.assertEquals @@ -10,7 +11,7 @@ class MetaBuilderTest { fun testBuilder() { val meta = Meta { "a" put 22 - "b" put listOf(1, 2, 3) + "b" put Value.of(listOf(1, 2, 3)) this["c"] = "myValue".asValue() "node" put { "e" put 12.2 diff --git a/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/MutableMetaTest.kt b/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/MutableMetaTest.kt index 7e4caa1c..534d907e 100644 --- a/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/MutableMetaTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/MutableMetaTest.kt @@ -6,7 +6,7 @@ import kotlin.test.assertEquals class MutableMetaTest{ @Test fun testRemove(){ - val meta = Meta { + val meta = MutableMeta { "aNode" put { "innerNode" put { "innerValue" put true diff --git a/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/SpecificationTest.kt b/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/SpecificationTest.kt index 4fade703..a8033cd3 100644 --- a/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/SpecificationTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/SpecificationTest.kt @@ -50,7 +50,7 @@ class SpecificationTest { @Test fun testChildModification() { val config = MutableMeta() - val child = config.getChild("child") + val child = config.getOrCreate("child") val scheme = TestScheme.write(child) scheme.a = 22 scheme.b = "test" @@ -61,7 +61,7 @@ class SpecificationTest { @Test fun testChildUpdate() { val config = MutableMeta() - val child = config.getChild("child") + val child = config.getOrCreate("child") child.update(TestScheme) { a = 22 b = "test" diff --git a/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/descriptors/DescriptorTest.kt b/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/descriptors/DescriptorTest.kt index c9ff52cb..b05d6e4e 100644 --- a/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/descriptors/DescriptorTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/descriptors/DescriptorTest.kt @@ -9,16 +9,14 @@ import kotlin.test.assertNotNull class DescriptorTest { - val descriptor = NodeDescriptor { + val descriptor = MetaDescriptor { node("aNode") { info = "A root demo node" - value("b") { + value("b", ValueType.NUMBER) { info = "b number value" - type(ValueType.NUMBER) } node("otherNode") { - value("otherValue") { - type(ValueType.BOOLEAN) + value("otherValue", ValueType.BOOLEAN) { default(false) info = "default value" } @@ -30,13 +28,13 @@ class DescriptorTest { fun testAllowedValues() { val child = descriptor["aNode.b"] assertNotNull(child) - val allowed = descriptor.nodes["aNode"]?.values?.get("b")?.allowedValues - assertEquals(emptyList(), allowed) + val allowed = descriptor["aNode"]?.get("b")?.allowedValues + assertEquals(null, allowed) } @Test - fun testDefaultMetaNode(){ - val meta = descriptor.defaultMeta + fun testDefaultMetaNode() { + val meta = descriptor.defaultNode assertEquals(false, meta["aNode.otherNode.otherValue"].boolean) } } \ No newline at end of file diff --git a/dataforge-meta/src/jsMain/kotlin/space/kscience/dataforge/meta/DynamicMeta.kt b/dataforge-meta/src/jsMain/kotlin/space/kscience/dataforge/meta/DynamicMeta.kt index 1bf587cd..b11c85d6 100644 --- a/dataforge-meta/src/jsMain/kotlin/space/kscience/dataforge/meta/DynamicMeta.kt +++ b/dataforge-meta/src/jsMain/kotlin/space/kscience/dataforge/meta/DynamicMeta.kt @@ -1,8 +1,8 @@ package space.kscience.dataforge.meta import space.kscience.dataforge.names.NameToken -import space.kscience.dataforge.values.Null import space.kscience.dataforge.values.Value +import space.kscience.dataforge.values.asValue import space.kscience.dataforge.values.isList @@ -22,11 +22,6 @@ public fun Value.toDynamic(): dynamic { public fun Meta.toDynamic(): dynamic { if (this is DynamicMeta) return this.obj - fun MetaItem.toDynamic(): dynamic = when (this) { - is MetaItemValue -> this.value.toDynamic() - is MetaItemNode -> this.node.toDynamic() - } - val res = js("{}") this.items.entries.groupBy { it.key.body }.forEach { (key, value) -> val list = value.map { it.value } @@ -38,46 +33,51 @@ public fun Meta.toDynamic(): dynamic { return res } -public class DynamicMeta(internal val obj: dynamic) : AbstractTypedMeta() { +public class DynamicMeta(internal val obj: dynamic) : Meta { private fun keys(): Array = js("Object").keys(obj) private fun isArray(@Suppress("UNUSED_PARAMETER") obj: dynamic): Boolean = - js("Array.isArray(obj)") as Boolean + js("Array").isArray(obj) as Boolean private fun isPrimitive(obj: dynamic): Boolean = (jsTypeOf(obj) != "object") - @Suppress("UNCHECKED_CAST", "USELESS_CAST") - private fun asItem(obj: dynamic): TypedMetaItem? { - return when { - obj == null -> MetaItemValue(Null) - isArray(obj) && (obj as Array).all { isPrimitive(it) } -> MetaItemValue(Value.of(obj as Array)) - else -> when (jsTypeOf(obj)) { - "boolean" -> MetaItemValue(Value.of(obj as Boolean)) - "number" -> MetaItemValue(Value.of(obj as Number)) - "string" -> MetaItemValue(Value.of(obj as String)) - "object" -> MetaItemNode(DynamicMeta(obj)) - else -> null - } + @Suppress("USELESS_CAST") + override val value: Value? + get() = if (isArray(obj) && (obj as Array).all { isPrimitive(it) }) Value.of(obj as Array) + else when (jsTypeOf(obj)) { + "boolean" -> (obj as Boolean).asValue() + "number" -> (obj as Number).asValue() + "string" -> (obj as String).asValue() + else -> null } - } - override val items: Map> - get() = keys().flatMap>> { key -> + override val items: Map + get() = keys().flatMap> { key -> val value = obj[key] ?: return@flatMap emptyList() - if (isArray(value)) { - val array = value as Array - return@flatMap if (array.all { isPrimitive(it) }) { - listOf(NameToken(key) to MetaItemValue(Value.of(array))) - } else { - array.mapIndexedNotNull { index, it -> - val item = asItem(it) ?: return@mapIndexedNotNull null - NameToken(key, index.toString()) to item + when { + isArray(value) -> { + val array = value as Array + if (array.all { isPrimitive(it) }) { + emptyList() + } else { + array.mapIndexedNotNull { index, it -> + val item = DynamicMeta(it) + NameToken(key, index.toString()) to item + } } } - } else { - val item = asItem(value) ?: return@flatMap emptyList() - listOf(NameToken(key) to item) + isPrimitive(obj) -> { + emptyList() + } + else -> { + val item = DynamicMeta(value) + listOf(NameToken(key) to item) + } } - }.associate { it } + }.toMap() + + 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) } \ No newline at end of file diff --git a/dataforge-meta/src/jsTest/kotlin/space/kscience/dataforge/meta/DynamicMetaTest.kt b/dataforge-meta/src/jsTest/kotlin/space/kscience/dataforge/meta/DynamicMetaTest.kt index 2c9c3e23..49576949 100644 --- a/dataforge-meta/src/jsTest/kotlin/space/kscience/dataforge/meta/DynamicMetaTest.kt +++ b/dataforge-meta/src/jsTest/kotlin/space/kscience/dataforge/meta/DynamicMetaTest.kt @@ -1,5 +1,6 @@ package space.kscience.dataforge.meta +import space.kscience.dataforge.values.ListValue import space.kscience.dataforge.values.int import kotlin.test.Test import kotlin.test.assertEquals @@ -21,7 +22,7 @@ class DynamicMetaTest { val meta = DynamicMeta(d) println(meta) assertEquals(true, meta["ob.booleanNode"].boolean) - assertEquals(2, meta["array"].value?.list?.get(1)?.int) + assertEquals(2, meta["array"]?.value?.list?.get(1)?.int) assertEquals(4, meta.items.size) } @@ -29,7 +30,7 @@ class DynamicMetaTest { fun testMetaToDynamic(){ val meta = Meta { "a" put 22 - "array" put listOf(1, 2, 3) + "array" put ListValue(1, 2, 3) "b" put "myString" "ob" put { "childNode" put 18 diff --git a/dataforge-workspace/src/commonMain/kotlin/space/kscience/dataforge/workspace/Task.kt b/dataforge-workspace/src/commonMain/kotlin/space/kscience/dataforge/workspace/Task.kt index ac14c473..965f859e 100644 --- a/dataforge-workspace/src/commonMain/kotlin/space/kscience/dataforge/workspace/Task.kt +++ b/dataforge-workspace/src/commonMain/kotlin/space/kscience/dataforge/workspace/Task.kt @@ -6,7 +6,7 @@ import space.kscience.dataforge.data.DataTree import space.kscience.dataforge.data.GoalExecutionRestriction import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.descriptors.Described -import space.kscience.dataforge.meta.descriptors.ItemDescriptor +import space.kscience.dataforge.meta.descriptors.MetaDescriptor import space.kscience.dataforge.misc.DFInternal import space.kscience.dataforge.misc.Type import space.kscience.dataforge.names.Name @@ -47,11 +47,11 @@ public class TaskResultBuilder( @DFInternal public fun Task( resultType: KType, - descriptor: ItemDescriptor? = null, + descriptor: MetaDescriptor? = null, builder: suspend TaskResultBuilder.() -> Unit, ): Task = object : Task { - override val descriptor: ItemDescriptor? = descriptor + override val descriptor: MetaDescriptor? = descriptor override suspend fun execute( workspace: Workspace, @@ -69,6 +69,6 @@ public fun Task( @OptIn(DFInternal::class) @Suppress("FunctionName") public inline fun Task( - descriptor: ItemDescriptor? = null, + descriptor: MetaDescriptor? = null, noinline builder: suspend TaskResultBuilder.() -> Unit, ): Task = Task(typeOf(), descriptor, builder) \ No newline at end of file diff --git a/dataforge-workspace/src/commonMain/kotlin/space/kscience/dataforge/workspace/WorkspaceBuilder.kt b/dataforge-workspace/src/commonMain/kotlin/space/kscience/dataforge/workspace/WorkspaceBuilder.kt index 1fa4ba5e..d2c0e48d 100644 --- a/dataforge-workspace/src/commonMain/kotlin/space/kscience/dataforge/workspace/WorkspaceBuilder.kt +++ b/dataforge-workspace/src/commonMain/kotlin/space/kscience/dataforge/workspace/WorkspaceBuilder.kt @@ -9,7 +9,8 @@ import space.kscience.dataforge.data.DataSetBuilder import space.kscience.dataforge.data.DataTree import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.MutableMeta -import space.kscience.dataforge.meta.descriptors.NodeDescriptor +import space.kscience.dataforge.meta.descriptors.MetaDescriptor +import space.kscience.dataforge.meta.descriptors.MetaDescriptorBuilder import space.kscience.dataforge.misc.DFBuilder import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.names.Name @@ -26,16 +27,16 @@ public interface TaskContainer { public inline fun TaskContainer.registerTask( name: String, - noinline descriptorBuilder: NodeDescriptor.() -> Unit = {}, + noinline descriptorBuilder: MetaDescriptorBuilder.() -> Unit = {}, noinline builder: suspend TaskResultBuilder.() -> Unit, -): Unit = registerTask(name.toName(), Task(NodeDescriptor(descriptorBuilder), builder)) +): Unit = registerTask(name.toName(), Task(MetaDescriptor(descriptorBuilder), builder)) public inline fun TaskContainer.task( - noinline descriptorBuilder: NodeDescriptor.() -> Unit = {}, + noinline descriptorBuilder: MetaDescriptorBuilder.() -> Unit = {}, noinline builder: suspend TaskResultBuilder.() -> Unit, ): PropertyDelegateProvider>> = PropertyDelegateProvider { _, property -> val taskName = property.name.toName() - val task = Task(NodeDescriptor(descriptorBuilder), builder) + val task = Task(MetaDescriptor(descriptorBuilder), builder) registerTask(taskName, task) ReadOnlyProperty { _, _ -> TaskReference(taskName, task) } } diff --git a/dataforge-workspace/src/jvmMain/kotlin/space/kscience/dataforge/workspace/fileData.kt b/dataforge-workspace/src/jvmMain/kotlin/space/kscience/dataforge/workspace/fileData.kt index 210d56ef..90a69976 100644 --- a/dataforge-workspace/src/jvmMain/kotlin/space/kscience/dataforge/workspace/fileData.kt +++ b/dataforge-workspace/src/jvmMain/kotlin/space/kscience/dataforge/workspace/fileData.kt @@ -86,7 +86,7 @@ public suspend fun DataSetBuilder.file( //otherwise, read as directory plugin.run { val data = readDataDirectory(path, formatResolver) - val name = data.getMeta()[Envelope.ENVELOPE_NAME_KEY].string + val name = data.getMeta()?.get(Envelope.ENVELOPE_NAME_KEY).string ?: path.fileName.toString().replace(".df", "") emit(name, data) }