From 9f5b010847c238a2423e9e3437da595c93c1dfd6 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sat, 24 Jul 2021 19:58:19 +0300 Subject: [PATCH] WIP full refactor of Meta --- build.gradle.kts | 6 +- .../kscience/dataforge/context/Context.kt | 11 +- .../dataforge/context/ContextBuilder.kt | 14 +- .../dataforge/properties/MetaProperty.kt | 7 +- .../dataforge/properties/schemeProperty.kt | 5 +- .../space/kscience/dataforge/provider/Path.kt | 6 +- .../kscience/dataforge/provider/PathTest.kt | 14 + .../kscience/dataforge/actions/MapAction.kt | 4 +- .../dataforge/actions/ReduceAction.kt | 4 +- .../kscience/dataforge/actions/SplitAction.kt | 4 +- .../kscience/dataforge/data/DataSetBuilder.kt | 6 +- .../kscience/dataforge/data/dataSetMeta.kt | 4 +- .../kscience/dataforge/data/dataTransform.kt | 6 +- .../dataforge/io/yaml/YamlMetaFormat.kt | 2 +- .../kscience/dataforge/io/BinaryMetaFormat.kt | 2 +- .../kscience/dataforge/io/EnvelopeBuilder.kt | 4 +- .../kscience/dataforge/io/JsonMetaFormat.kt | 5 +- .../kscience/dataforge/io/MetaFormatTest.kt | 5 +- .../kscience/dataforge/meta/Configurable.kt | 10 +- .../kscience/dataforge/meta/ItemDelegate.kt | 111 ----- .../kscience/dataforge/meta/ItemProvider.kt | 88 ---- .../space/kscience/dataforge/meta/JsonMeta.kt | 187 +++----- .../space/kscience/dataforge/meta/Laminate.kt | 51 +- .../space/kscience/dataforge/meta/Meta.kt | 216 +++++++-- .../kscience/dataforge/meta/MetaBuilder.kt | 146 ------ .../kscience/dataforge/meta/MetaDelegate.kt | 111 +++++ .../space/kscience/dataforge/meta/MetaItem.kt | 96 ---- .../kscience/dataforge/meta/MetaSerializer.kt | 86 +--- .../dataforge/meta/MutableItemProvider.kt | 153 ------ .../kscience/dataforge/meta/MutableMeta.kt | 415 +++++++++++++--- ...ItemDelegate.kt => MutableMetaDelegate.kt} | 84 ++-- .../dataforge/meta/ObservableItemProvider.kt | 159 ------- .../kscience/dataforge/meta/ObservableMeta.kt | 116 +++++ .../space/kscience/dataforge/meta/Scheme.kt | 79 ++-- .../kscience/dataforge/meta/SealedMeta.kt | 26 +- .../kscience/dataforge/meta/Specification.kt | 55 +-- .../kscience/dataforge/meta/TypedMeta.kt | 56 --- .../dataforge/meta/descriptors/Described.kt | 2 +- .../meta/descriptors/ItemDescriptor.kt | 253 +++++----- .../meta/descriptors/MetaDescriptor.kt | 60 +++ .../meta/descriptors/NodeDescriptor.kt | 444 +++++++++--------- .../meta/descriptors/ValueDescriptor.kt | 278 +++++------ .../meta/descriptors/descriptorExtensions.kt | 4 +- .../space/kscience/dataforge/meta/mapMeta.kt | 6 +- .../transformations/MetaTransformation.kt | 20 +- .../dataforge/values/valueExtensions.kt | 4 +- .../kscience/dataforge/meta/ConfigTest.kt | 2 +- .../dataforge/meta/MutableMetaTest.kt | 2 +- .../kscience/dataforge/meta/SchemeTest.kt | 4 +- .../dataforge/meta/SpecificationTest.kt | 4 +- .../kscience/dataforge/meta/DynamicMeta.kt | 2 +- .../kscience/dataforge/workspace/Workspace.kt | 4 +- .../dataforge/workspace/WorkspaceBuilder.kt | 6 +- .../workspace/SimpleWorkspaceTest.kt | 2 +- settings.gradle.kts | 2 +- 55 files changed, 1608 insertions(+), 1845 deletions(-) create mode 100644 dataforge-context/src/commonTest/kotlin/space/kscience/dataforge/provider/PathTest.kt delete mode 100644 dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ItemDelegate.kt delete mode 100644 dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ItemProvider.kt delete mode 100644 dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MetaBuilder.kt create mode 100644 dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MetaDelegate.kt delete mode 100644 dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MetaItem.kt delete mode 100644 dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MutableItemProvider.kt rename dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/{MutableItemDelegate.kt => MutableMetaDelegate.kt} (56%) delete mode 100644 dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ObservableItemProvider.kt create mode 100644 dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ObservableMeta.kt delete mode 100644 dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/TypedMeta.kt create mode 100644 dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/MetaDescriptor.kt diff --git a/build.gradle.kts b/build.gradle.kts index 46d44b9d..67575df7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ plugins { allprojects { group = "space.kscience" - version = "0.5.0-dev-2" + version = "0.5.0-dev-3" } subprojects { @@ -15,10 +15,6 @@ readme { readmeTemplate = file("docs/templates/README-TEMPLATE.md") } -changelog{ - version = project.version.toString() -} - ksciencePublish { github("dataforge-core") space("https://maven.pkg.jetbrains.space/mipt-npm/p/sci/maven") 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 b7e393cf..4f0c96aa 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 @@ -3,10 +3,7 @@ package space.kscience.dataforge.context import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob -import space.kscience.dataforge.meta.Laminate -import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.meta.MetaRepr -import space.kscience.dataforge.meta.itemSequence +import space.kscience.dataforge.meta.* import space.kscience.dataforge.misc.Named import space.kscience.dataforge.names.Name import space.kscience.dataforge.provider.Provider @@ -50,13 +47,13 @@ public open class Context internal constructor( public fun content(target: String, inherit: Boolean): Map { return if (inherit) { when (target) { - PROPERTY_TARGET -> properties.itemSequence().toMap() + PROPERTY_TARGET -> properties.nodeSequence().toMap() Plugin.TARGET -> plugins.list(true).associateBy { it.name } else -> emptyMap() } } else { when (target) { - PROPERTY_TARGET -> properties.layers.firstOrNull()?.itemSequence()?.toMap() ?: emptyMap() + PROPERTY_TARGET -> properties.layers.firstOrNull()?.nodeSequence()?.toMap() ?: emptyMap() Plugin.TARGET -> plugins.list(false).associateBy { it.name } else -> emptyMap() } @@ -99,7 +96,7 @@ 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() } + "plugins" put plugins.map { it.toMeta().asMetaItem() } } public companion object { diff --git a/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/ContextBuilder.kt b/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/ContextBuilder.kt index 52db8dae..016eb139 100644 --- a/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/ContextBuilder.kt +++ b/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/context/ContextBuilder.kt @@ -1,7 +1,7 @@ package space.kscience.dataforge.context import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.meta.MetaBuilder +import space.kscience.dataforge.meta.MutableMeta import space.kscience.dataforge.meta.seal import space.kscience.dataforge.meta.toMutableMeta import space.kscience.dataforge.misc.DFBuilder @@ -25,7 +25,7 @@ public class ContextBuilder internal constructor( internal val factories = HashMap, Meta>() internal var meta = meta.toMutableMeta() - public fun properties(action: MetaBuilder.() -> Unit) { + public fun properties(action: MutableMeta.() -> Unit) { meta.action() } @@ -38,20 +38,20 @@ public class ContextBuilder internal constructor( parent.gatherInSequence>(PluginFactory.TYPE).values .find { it.tag.matches(tag) } ?: error("Can't resolve plugin factory for $tag") - public fun plugin(tag: PluginTag, metaBuilder: MetaBuilder.() -> Unit = {}) { + public fun plugin(tag: PluginTag, mutableMeta: MutableMeta.() -> Unit = {}) { val factory = findPluginFactory(tag) - factories[factory] = Meta(metaBuilder) + factories[factory] = Meta(mutableMeta) } public fun plugin(factory: PluginFactory<*>, meta: Meta) { factories[factory] = meta } - public fun plugin(factory: PluginFactory<*>, metaBuilder: MetaBuilder.() -> Unit = {}) { - factories[factory] = Meta(metaBuilder) + public fun plugin(factory: PluginFactory<*>, mutableMeta: MutableMeta.() -> Unit = {}) { + factories[factory] = Meta(mutableMeta) } - public fun plugin(name: String, group: String = "", version: String = "", action: MetaBuilder.() -> Unit = {}) { + public fun plugin(name: String, group: String = "", version: String = "", action: MutableMeta.() -> Unit = {}) { plugin(PluginTag(name, group, version), action) } 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 9e6964c3..e9175ff7 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,18 +1,17 @@ package space.kscience.dataforge.properties -import space.kscience.dataforge.meta.ObservableMeta + +import space.kscience.dataforge.meta.MutableMeta import space.kscience.dataforge.meta.get -import space.kscience.dataforge.meta.set 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.misc.DFExperimental import space.kscience.dataforge.names.Name -import space.kscience.dataforge.names.startsWith @DFExperimental public class MetaProperty( - public val meta: ObservableMeta, + public val meta: MutableMeta, public val name: Name, public val converter: MetaConverter, ) : Property { 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 63695bb4..3c474b0e 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 @@ -1,14 +1,13 @@ package space.kscience.dataforge.properties -import space.kscience.dataforge.meta.ObservableItemProvider +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 @DFExperimental -public fun

P.property(property: KMutableProperty1): Property = +public fun

P.property(property: KMutableProperty1): Property = object : Property { override var value: T? get() = property.get(this@property) diff --git a/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/provider/Path.kt b/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/provider/Path.kt index 2a16e53f..ef4b4664 100644 --- a/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/provider/Path.kt +++ b/dataforge-context/src/commonMain/kotlin/space/kscience/dataforge/provider/Path.kt @@ -33,11 +33,7 @@ public value class Path(public val tokens: List) : Iterable(public var name: Name, public var meta: MetaBuilder, public val actionMeta: Meta) { +public class MapActionBuilder(public var name: Name, public var meta: MutableMeta, public val actionMeta: Meta) { public lateinit var result: suspend ActionEnv.(T) -> R /** diff --git a/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/actions/ReduceAction.kt b/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/actions/ReduceAction.kt index 2607804c..bffb5b67 100644 --- a/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/actions/ReduceAction.kt +++ b/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/actions/ReduceAction.kt @@ -6,7 +6,7 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.fold import space.kscience.dataforge.data.* import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.meta.MetaBuilder +import space.kscience.dataforge.meta.MutableMeta import space.kscience.dataforge.misc.DFBuilder import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DFInternal @@ -18,7 +18,7 @@ import kotlin.reflect.typeOf public class JoinGroup(public var name: String, internal val set: DataSet) { - public var meta: MetaBuilder = MetaBuilder() + public var meta: MutableMeta = MutableMeta() public lateinit var result: suspend ActionEnv.(Map) -> R diff --git a/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/actions/SplitAction.kt b/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/actions/SplitAction.kt index 50286a2a..fc22eb95 100644 --- a/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/actions/SplitAction.kt +++ b/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/actions/SplitAction.kt @@ -7,7 +7,7 @@ import kotlinx.coroutines.launch import space.kscience.dataforge.data.* import space.kscience.dataforge.meta.Laminate import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.meta.MetaBuilder +import space.kscience.dataforge.meta.MutableMeta import space.kscience.dataforge.meta.toMutableMeta import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DFInternal @@ -20,7 +20,7 @@ import kotlin.reflect.typeOf public class SplitBuilder(public val name: Name, public val meta: Meta) { - public class FragmentRule(public val name: Name, public var meta: MetaBuilder) { + public class FragmentRule(public val name: Name, public var meta: MutableMeta) { public lateinit var result: suspend (T) -> R public fun result(f: suspend (T) -> R) { diff --git a/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/data/DataSetBuilder.kt b/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/data/DataSetBuilder.kt index 580e702b..20e3ed1b 100644 --- a/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/data/DataSetBuilder.kt +++ b/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/data/DataSetBuilder.kt @@ -4,7 +4,7 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.collect import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.meta.MetaBuilder +import space.kscience.dataforge.meta.MutableMeta import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.plus @@ -124,8 +124,8 @@ public suspend inline fun DataSetBuilder.static(name: Name, public suspend inline fun DataSetBuilder.static( name: String, data: T, - metaBuilder: MetaBuilder.() -> Unit, -): Unit = emit(name.toName(), Data.static(data, Meta(metaBuilder))) + mutableMeta: MutableMeta.() -> Unit, +): Unit = emit(name.toName(), Data.static(data, Meta(mutableMeta))) /** * Update data with given node data and meta with node meta. diff --git a/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/data/dataSetMeta.kt b/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/data/dataSetMeta.kt index b1508d29..bdbbf3a8 100644 --- a/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/data/dataSetMeta.kt +++ b/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/data/dataSetMeta.kt @@ -1,7 +1,7 @@ package space.kscience.dataforge.data import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.meta.MetaBuilder +import space.kscience.dataforge.meta.MutableMeta /** @@ -17,4 +17,4 @@ public suspend fun DataSetBuilder<*>.meta(meta: Meta): Unit = emit(DataSet.META_ /** * Add meta-data node to a [DataSet] */ -public suspend fun DataSetBuilder<*>.meta(metaBuilder: MetaBuilder.() -> Unit): Unit = meta(Meta(metaBuilder)) \ No newline at end of file +public suspend fun DataSetBuilder<*>.meta(mutableMeta: MutableMeta.() -> Unit): Unit = meta(Meta(mutableMeta)) \ No newline at end of file diff --git a/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/data/dataTransform.kt b/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/data/dataTransform.kt index a64d0790..f4c4a710 100644 --- a/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/data/dataTransform.kt +++ b/dataforge-data/src/commonMain/kotlin/space/kscience/dataforge/data/dataTransform.kt @@ -2,7 +2,7 @@ package space.kscience.dataforge.data import kotlinx.coroutines.flow.* import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.meta.MetaBuilder +import space.kscience.dataforge.meta.MutableMeta import space.kscience.dataforge.meta.seal import space.kscience.dataforge.meta.toMutableMeta import space.kscience.dataforge.misc.DFInternal @@ -140,7 +140,7 @@ public suspend inline fun Flow>.foldToDa public suspend fun DataSet.map( outputType: KType, coroutineContext: CoroutineContext = EmptyCoroutineContext, - metaTransform: MetaBuilder.() -> Unit = {}, + metaTransform: MutableMeta.() -> Unit = {}, block: suspend (T) -> R, ): DataTree = DataTree(outputType) { populate( @@ -156,7 +156,7 @@ public suspend fun DataSet.map( @OptIn(DFInternal::class) public suspend inline fun DataSet.map( coroutineContext: CoroutineContext = EmptyCoroutineContext, - noinline metaTransform: MetaBuilder.() -> Unit = {}, + noinline metaTransform: MutableMeta.() -> Unit = {}, noinline block: suspend (T) -> R, ): DataTree = map(typeOf(), coroutineContext, metaTransform, block) 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 37b5658b..2dd51ab4 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 @@ -34,7 +34,7 @@ public fun Meta.toYaml(): YamlMap { return YamlMap(map) } -private class YamlMeta(private val yamlMap: YamlMap, private val descriptor: NodeDescriptor? = null) : MetaBase() { +private class YamlMeta(private val yamlMap: YamlMap, private val descriptor: NodeDescriptor? = null) : AbstractTypedMeta() { private fun buildItems(): Map { val map = LinkedHashMap() 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 index 38ca6ec2..dae6183a 100644 --- a/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/BinaryMetaFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/BinaryMetaFormat.kt @@ -100,7 +100,7 @@ public object BinaryMetaFormat : MetaFormat, MetaFormatFactory { } @Suppress("UNCHECKED_CAST") - public fun Input.readMetaItem(): TypedMetaItem { + public fun Input.readMetaItem(): TypedMetaItem { return when (val keyChar = readByte().toInt().toChar()) { 'S' -> MetaItemValue(StringValue(readString())) 'N' -> MetaItemValue(Null) diff --git a/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/EnvelopeBuilder.kt b/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/EnvelopeBuilder.kt index 211b4f32..62d95b01 100644 --- a/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/EnvelopeBuilder.kt +++ b/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/EnvelopeBuilder.kt @@ -4,7 +4,7 @@ import io.ktor.utils.io.core.Output import space.kscience.dataforge.meta.* public class EnvelopeBuilder : Envelope { - private val metaBuilder = MetaBuilder() + private val metaBuilder = MutableMeta() override var data: Binary? = null override var meta: Meta @@ -13,7 +13,7 @@ public class EnvelopeBuilder : Envelope { metaBuilder.update(value) } - public fun meta(block: MetaBuilder.() -> Unit) { + public fun meta(block: MutableMeta.() -> Unit) { metaBuilder.apply(block) } diff --git a/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/JsonMetaFormat.kt b/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/JsonMetaFormat.kt index ca5d52b0..8cac788e 100644 --- a/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/JsonMetaFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/space/kscience/dataforge/io/JsonMetaFormat.kt @@ -11,9 +11,8 @@ import space.kscience.dataforge.context.Context import space.kscience.dataforge.io.IOFormat.Companion.NAME_KEY import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.descriptors.NodeDescriptor -import space.kscience.dataforge.meta.node import space.kscience.dataforge.meta.toJson -import space.kscience.dataforge.meta.toMetaItem +import space.kscience.dataforge.meta.toMeta import kotlin.reflect.KType import kotlin.reflect.typeOf @@ -36,7 +35,7 @@ public class JsonMetaFormat(private val json: Json = DEFAULT_JSON) : MetaFormat override fun readMeta(input: Input, descriptor: NodeDescriptor?): Meta { val str = input.readUtf8String()//readByteArray().decodeToString() val jsonElement = json.parseToJsonElement(str) - val item = jsonElement.toMetaItem(descriptor) + val item = jsonElement.toMeta(descriptor) return item.node ?: Meta.EMPTY } 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 a5860d31..1e8d7ed7 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 @@ -4,7 +4,6 @@ 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.number import kotlin.test.Test import kotlin.test.assertEquals @@ -72,7 +71,7 @@ class MetaFormatTest { add(JsonPrimitive(3.0)) }) } - val meta = json.toMetaItem().node!! + val meta = json.toMeta().node!! assertEquals(true, meta["$JSON_ARRAY_KEY[0].$JSON_ARRAY_KEY[1].d"].boolean) assertEquals("value", meta["$JSON_ARRAY_KEY[1]"].string) @@ -98,7 +97,7 @@ class MetaFormatTest { } """.trimIndent() val json = Json.parseToJsonElement(jsonString) - val meta = json.toMetaItem().node!! + val meta = json.toMeta().node!! assertEquals(ListValue.EMPTY, meta["comments"].value) } diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/Configurable.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/Configurable.kt index dbd22f21..64ba5ba6 100644 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/Configurable.kt +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/Configurable.kt @@ -1,8 +1,6 @@ package space.kscience.dataforge.meta import space.kscience.dataforge.misc.DFBuilder -import space.kscience.dataforge.names.Name -import kotlin.properties.ReadWriteProperty /** * A container that holds a [ObservableMeta]. @@ -11,15 +9,11 @@ public interface Configurable { /** * Backing config */ - public val config: ObservableMeta + public val config: MutableMeta } public fun T.configure(meta: Meta): T = this.apply { config.update(meta) } @DFBuilder -public inline fun T.configure(action: ObservableMeta.() -> Unit): T = apply { config.apply(action) } - -/* Node delegates */ - -public fun Configurable.config(key: Name? = null): ReadWriteProperty = config.node(key) +public inline fun T.configure(action: MutableMeta.() -> Unit): T = apply { config.apply(action) } \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ItemDelegate.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ItemDelegate.kt deleted file mode 100644 index 81340807..00000000 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ItemDelegate.kt +++ /dev/null @@ -1,111 +0,0 @@ -package space.kscience.dataforge.meta - -import space.kscience.dataforge.meta.transformations.MetaConverter -import space.kscience.dataforge.names.Name -import space.kscience.dataforge.names.asName -import space.kscience.dataforge.values.Value -import kotlin.properties.ReadOnlyProperty - -/* Meta delegates */ - -public typealias ItemDelegate = ReadOnlyProperty - -public fun ItemProvider.item(key: Name? = null): ItemDelegate = ReadOnlyProperty { _, property -> - get(key ?: property.name.asName()) -} - -//TODO add caching for sealed nodes - - -/** - * Apply a converter to this delegate creating a delegate with a custom type - */ -public fun ItemDelegate.convert( - converter: MetaConverter, -): ReadOnlyProperty = ReadOnlyProperty { thisRef, property -> - this@convert.getValue(thisRef, property)?.let(converter::itemToObject) -} - -/* - * - */ -public fun ItemDelegate.convert( - converter: MetaConverter, - default: () -> R, -): ReadOnlyProperty = ReadOnlyProperty { thisRef, property -> - this@convert.getValue(thisRef, property)?.let(converter::itemToObject) ?: default() -} - -/** - * A converter with a custom reader transformation - */ -public fun ItemDelegate.convert( - reader: (MetaItem?) -> R, -): ReadOnlyProperty = ReadOnlyProperty { thisRef, property -> - this@convert.getValue(thisRef, property).let(reader) -} - -/* Read-only delegates for [ItemProvider] */ - -/** - * A property delegate that uses custom key - */ -public fun ItemProvider.value(key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.value) - -public fun ItemProvider.string(key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.string) - -public fun ItemProvider.boolean(key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.boolean) - -public fun ItemProvider.number(key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.number) - -public fun ItemProvider.double(key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.double) - -public fun ItemProvider.float(key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.float) - -public fun ItemProvider.int(key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.int) - -public fun ItemProvider.long(key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.long) - -public fun ItemProvider.node(key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.meta) - -public fun ItemProvider.string(default: String, key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.string) { default } - -public fun ItemProvider.boolean(default: Boolean, key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.boolean) { default } - -public fun ItemProvider.number(default: Number, key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.number) { default } - -public fun ItemProvider.double(default: Double, key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.double) { default } - -public fun ItemProvider.float(default: Float, key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.float) { default } - -public fun ItemProvider.int(default: Int, key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.int) { default } - -public fun ItemProvider.long(default: Long, key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.long) { default } - -public inline fun > ItemProvider.enum(default: E, key: Name? = null): ReadOnlyProperty = - item(key).convert(MetaConverter.enum()) { default } - -public fun ItemProvider.string(key: Name? = null, default: () -> String): ReadOnlyProperty = - item(key).convert(MetaConverter.string, default) - -public fun ItemProvider.boolean(key: Name? = null, default: () -> Boolean): ReadOnlyProperty = - item(key).convert(MetaConverter.boolean, default) - -public fun ItemProvider.number(key: Name? = null, default: () -> Number): ReadOnlyProperty = - item(key).convert(MetaConverter.number, default) diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ItemProvider.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ItemProvider.kt deleted file mode 100644 index 24a61ba0..00000000 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ItemProvider.kt +++ /dev/null @@ -1,88 +0,0 @@ -package space.kscience.dataforge.meta - -import space.kscience.dataforge.names.* - -public fun interface ItemProvider { - //getItem used instead of get in order to provide extension freedom - public fun getItem(name: Name): MetaItem? - - public companion object { - public val EMPTY: ItemProvider = ItemProvider { null } - } -} - - -/* Get operations*/ - -/** - * Perform recursive item search using given [name]. Each [NameToken] is treated as a name in [Meta.items] of a parent node. - * - * If [name] is empty return current [Meta] as a [MetaItemNode] - */ -public operator fun ItemProvider?.get(name: Name): MetaItem? = this?.getItem(name) - -/** - * Root item of this provider - */ -public val ItemProvider.rootItem: MetaItem? get() = get(Name.EMPTY) - -/** - * The root node of this item provider if it is present - */ -public val ItemProvider.rootNode: Meta? get() = rootItem.node - -/** - * Parse [Name] from [key] using full name notation and pass it to [Meta.get] - */ -public operator fun ItemProvider?.get(key: String): MetaItem? = this?.get(key.toName()) - -/** - * Create a provider that uses given provider for default values if those are not found in this provider - */ -public fun ItemProvider.withDefault(default: ItemProvider?): ItemProvider = if (default == null) { - this -} else { - ItemProvider { - this[it] ?: default[it] - } -} - -/** - * Get all items matching given name. The index of the last element, if present is used as a [Regex], - * against which indexes of elements are matched. - */ -public fun ItemProvider.getIndexed(name: Name): Map { - val root: Meta = when (name.length) { - 0 -> error("Can't use empty name for 'getIndexed'") - 1 -> this.rootNode ?: return emptyMap() - else -> this[name.cutLast()].node ?: return emptyMap() - } - - val (body, index) = name.lastOrNull()!! - return if (index == null) { - root.items.filter { it.key.body == body }.mapKeys { it.key.index } - } else { - val regex = index.toRegex() - root.items.filter { it.key.body == body && (regex.matches(it.key.index ?: "")) } - .mapKeys { it.key.index } - } -} - -public fun ItemProvider.getIndexed(name: String): Map = getIndexed(name.toName()) - -/** - * Return a provider referencing a child node - */ -public fun ItemProvider.getChild(childName: Name): ItemProvider = get(childName).node ?: ItemProvider.EMPTY - -public fun ItemProvider.getChild(childName: String): ItemProvider = getChild(childName.toName()) - -///** -// * Get all items matching given name. -// */ -//@Suppress("UNCHECKED_CAST") -//public fun > M.getIndexed(name: Name): Map> = -// (this as Meta).getIndexed(name) as Map> -// -//public fun > M.getIndexed(name: String): Map> = -// getIndexed(name.toName()) 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 ea855ca4..93e506d9 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 @@ -3,20 +3,15 @@ package space.kscience.dataforge.meta import kotlinx.serialization.json.* -import space.kscience.dataforge.meta.JsonMeta.Companion.JSON_ARRAY_KEY -import space.kscience.dataforge.meta.descriptors.ItemDescriptor -import space.kscience.dataforge.meta.descriptors.ItemDescriptor.Companion.DEFAULT_INDEX_KEY -import space.kscience.dataforge.meta.descriptors.NodeDescriptor -import space.kscience.dataforge.meta.descriptors.ValueDescriptor +import space.kscience.dataforge.meta.descriptors.MetaDescriptor import space.kscience.dataforge.names.NameToken -import space.kscience.dataforge.names.withIndex import space.kscience.dataforge.values.* /** * @param descriptor reserved for custom serialization in future */ -public fun Value.toJson(descriptor: ValueDescriptor? = null): JsonElement = when (type) { +public fun Value.toJson(descriptor: MetaDescriptor? = null): JsonElement = when (type) { ValueType.NUMBER -> JsonPrimitive(numberOrNull) ValueType.STRING -> JsonPrimitive(string) ValueType.BOOLEAN -> JsonPrimitive(boolean) @@ -26,73 +21,47 @@ public fun Value.toJson(descriptor: ValueDescriptor? = null): JsonElement = when //Use these methods to customize JSON key mapping @Suppress("NULLABLE_EXTENSION_OPERATOR_WITH_SAFE_CALL_RECEIVER") -private fun String.toJsonKey(descriptor: ItemDescriptor?) = descriptor?.attributes?.get("jsonName").string ?: toString() +private fun String.toJsonKey(descriptor: MetaDescriptor?) = descriptor?.attributes?.get("jsonName").string ?: toString() -//private fun NodeDescriptor?.getDescriptor(key: String) = this?.items?.get(key) - -/** - * Convert given [Meta] to [JsonObject]. Primitives and nodes are copied as is, same name siblings are treated as json arrays - */ -private fun Meta.toJsonWithIndex(descriptor: NodeDescriptor?, indexValue: String?): JsonObject { - - val elementMap = HashMap() - - fun MetaItem.toJsonElement(itemDescriptor: ItemDescriptor?, index: String?): JsonElement = when (this) { - is MetaItemValue -> { - value.toJson(itemDescriptor as? ValueDescriptor) - } - is MetaItemNode -> { - node.toJsonWithIndex(itemDescriptor as? NodeDescriptor, index) +private fun Meta.toJsonWithIndex(descriptor: MetaDescriptor?, index: String?): JsonElement = if (items.isEmpty()) { + value?.toJson(descriptor) ?: JsonNull +} else { + val pairs: MutableList> = items.entries.groupBy { + it.key.body + }.mapTo(ArrayList()) { (body, list) -> + val childDescriptor = descriptor?.children?.get(body) + if (list.size == 1) { + val (token, element) = list.first() + val child: JsonElement = element.toJsonWithIndex(childDescriptor, token.index) + body to child + } else { + val elements: List = list.sortedBy { it.key.index }.mapIndexed { index, entry -> + //Use index if it is not equal to the item order + val actualIndex = if (index.toString() != entry.key.index) entry.key.index else null + entry.value.toJsonWithIndex(childDescriptor, actualIndex) + } + body to JsonArray(elements) } } - fun addElement(key: String) { - val itemDescriptor = descriptor?.items?.get(key) - val jsonKey = key.toJsonKey(itemDescriptor) - val items: Map = getIndexed(key) - when (items.size) { - 0 -> { - //do nothing - } - 1 -> { - val (index, item) = items.entries.first() - val element = item.toJsonElement(itemDescriptor, index) - if (index == null) { - elementMap[jsonKey] = element - } else { - //treat arrays with single element - elementMap[jsonKey] = buildJsonArray { - add(element) - } - } - } - else -> { - val array = buildJsonArray { - items.forEach { (index, item) -> - add(item.toJsonElement(itemDescriptor, index)) - } - } - elementMap[jsonKey] = array - } - } + //Add index if needed + if (index != null) { + pairs += Meta.INDEX_KEY to JsonPrimitive(index) } - ((descriptor?.items?.keys ?: emptySet()) + items.keys.map { it.body }).forEach(::addElement) - - - if (indexValue != null) { - val indexKey = descriptor?.indexKey ?: DEFAULT_INDEX_KEY - elementMap[indexKey] = JsonPrimitive(indexValue) + //Add value if needed + if (value != null) { + pairs += Meta.VALUE_KEY to value!!.toJson(null) } - return JsonObject(elementMap) + JsonObject(pairs.toMap()) } -public fun Meta.toJson(descriptor: NodeDescriptor? = null): JsonObject = toJsonWithIndex(descriptor, null) +public fun Meta.toJson(descriptor: MetaDescriptor? = null): JsonElement = toJsonWithIndex(descriptor, null) -public fun JsonObject.toMeta(descriptor: NodeDescriptor? = null): JsonMeta = JsonMeta(this, descriptor) +public fun JsonObject.toMeta(descriptor: MetaDescriptor? = null): JsonMeta = JsonMeta(this, descriptor) -public fun JsonPrimitive.toValue(descriptor: ValueDescriptor?): Value { +public fun JsonPrimitive.toValue(descriptor: MetaDescriptor?): Value { return when (this) { JsonNull -> Null else -> { @@ -105,79 +74,47 @@ public fun JsonPrimitive.toValue(descriptor: ValueDescriptor?): Value { } } -public fun JsonElement.toMetaItem(descriptor: ItemDescriptor? = null): TypedMetaItem = when (this) { - is JsonPrimitive -> { - val value = this.toValue(descriptor as? ValueDescriptor) - MetaItemValue(value) - } - is JsonObject -> { - val meta = JsonMeta(this, descriptor as? NodeDescriptor) - MetaItemNode(meta) - } - is JsonArray -> { - if (this.all { it is JsonPrimitive }) { - val value = if (isEmpty()) { - Null - } else { - map { - //We already checked that all values are primitives - (it as JsonPrimitive).toValue(descriptor as? ValueDescriptor) - }.asValue() - } - MetaItemValue(value) - } else { - //We can't return multiple items therefore we create top level node - buildJsonObject { put(JSON_ARRAY_KEY, this@toMetaItem) }.toMetaItem(descriptor) - } - } -} +public fun JsonElement.toMeta(descriptor: MetaDescriptor? = null): TypedMeta = JsonMeta(this, descriptor) /** * A meta wrapping json object */ -public class JsonMeta(private val json: JsonObject, private val descriptor: NodeDescriptor? = null) : MetaBase() { +public class JsonMeta( + private val json: JsonElement, + private val descriptor: MetaDescriptor? = null +) : TypedMeta { - private fun buildItems(): Map> { - val map = LinkedHashMap>() + private val indexName by lazy { descriptor?.indexKey ?: Meta.INDEX_KEY } - json.forEach { (jsonKey, value) -> - val key = NameToken(jsonKey) - val itemDescriptor = descriptor?.items?.get(jsonKey) - when (value) { - is JsonPrimitive -> { - map[key] = MetaItemValue(value.toValue(itemDescriptor as? ValueDescriptor)) - } - is JsonObject -> { - map[key] = MetaItemNode( - JsonMeta( - value, - itemDescriptor as? NodeDescriptor - ) - ) - } - is JsonArray -> if (value.all { it is JsonPrimitive }) { - val listValue = ListValue( - value.map { - //We already checked that all values are primitives - (it as JsonPrimitive).toValue(itemDescriptor as? ValueDescriptor) - } - ) - map[key] = MetaItemValue(listValue) - } else value.forEachIndexed { index, jsonElement -> - val indexKey = (itemDescriptor as? NodeDescriptor)?.indexKey ?: DEFAULT_INDEX_KEY - val indexValue: String = (jsonElement as? JsonObject) - ?.get(indexKey)?.jsonPrimitive?.contentOrNull - ?: index.toString() //In case index is non-string, the backward transformation will be broken. - - val token = key.withIndex(indexValue) - map[token] = jsonElement.toMetaItem(itemDescriptor) - } + 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() + } else { + null } } - return map } - override val items: Map> by lazy(::buildItems) + 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() + } + } public companion object { /** diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/Laminate.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/Laminate.kt index 044c5699..b2af2e73 100644 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/Laminate.kt +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/Laminate.kt @@ -2,12 +2,15 @@ package space.kscience.dataforge.meta import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.NameToken +import space.kscience.dataforge.values.Value /** * A meta laminate consisting of multiple immutable meta layers. For mutable front layer, use [Scheme]. * If [layers] list contains a [Laminate] it is flat-mapped. */ -public class Laminate(layers: List) : MetaBase() { +public class Laminate(layers: List) : TypedMeta { + + override val value: Value? = layers.firstNotNullOfOrNull { it.value } public val layers: List = layers.flatMap { if (it is Laminate) { @@ -17,7 +20,7 @@ public class Laminate(layers: List) : MetaBase() { } } - override val items: Map> by lazy { + override val items: Map by lazy { layers.map { it.items.keys }.flatten().associateWith { key -> layers.asSequence().map { it.items[key] }.filterNotNull().let(replaceRule) } @@ -30,7 +33,7 @@ public class Laminate(layers: List) : MetaBase() { val items = layers.map { it.items.keys }.flatten().associateWith { key -> layers.asSequence().map { it.items[key] }.filterNotNull().merge() } - return SealedMeta(items) + return SealedMeta(value, items) } public companion object { @@ -40,33 +43,21 @@ public class Laminate(layers: List) : MetaBase() { * * TODO add picture */ - public val replaceRule: (Sequence) -> TypedMetaItem = { it.first().seal() } + public val replaceRule: (Sequence) -> SealedMeta = { it.first().seal() } - private fun Sequence.merge(): TypedMetaItem { - return when { - all { it is MetaItemValue } -> //If all items are values, take first - first().seal() - all { it is MetaItemNode } -> { - //list nodes in item - val nodes = map { (it as MetaItemNode).node } - //represent as key->value entries - val entries = nodes.flatMap { it.items.entries.asSequence() } - //group by keys - val groups = entries.groupBy { it.key } - // recursively apply the rule - val items = groups.mapValues { entry -> - entry.value.asSequence().map { it.value }.merge() - } - MetaItemNode(SealedMeta(items)) - - } - else -> map { - when (it) { - is MetaItemValue -> MetaItemNode(Meta { Meta.VALUE_KEY put it.value }) - is MetaItemNode -> it - } - }.merge() + private fun Sequence.merge(): SealedMeta { + val value = firstNotNullOfOrNull { it.value } + //list nodes in item + val nodes = toList() + //represent as key->value entries + val entries = nodes.flatMap { it.items.entries.asSequence() } + //group by keys + val groups = entries.groupBy { it.key } + // recursively apply the rule + val items = groups.mapValues { entry -> + entry.value.asSequence().map { it.value }.merge() } + return SealedMeta(value,items) } @@ -74,7 +65,7 @@ public class Laminate(layers: List) : MetaBase() { * The values a replaced but meta children are joined * TODO add picture */ - public val mergeRule: (Sequence) -> TypedMetaItem = { it.merge() } + public val mergeRule: (Sequence) -> TypedMeta = { it.merge() } } } @@ -84,7 +75,7 @@ public fun Laminate(vararg layers: Meta?): Laminate = Laminate(layers.filterNotN /** * Performance optimized version of get method */ -public fun Laminate.getFirst(name: Name): MetaItem? { +public fun Laminate.getFirst(name: Name): Meta? { layers.forEach { layer -> layer[name]?.let { return it } } 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 6eac28e7..bc318100 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 @@ -1,51 +1,37 @@ package space.kscience.dataforge.meta import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import space.kscience.dataforge.misc.Type import space.kscience.dataforge.names.* -import space.kscience.dataforge.values.Value +import space.kscience.dataforge.values.* /** * The object that could be represented as [Meta]. Meta provided by [toMeta] method should fully represent object state. * Meaning that two states with the same meta are equal. */ -@Serializable(MetaSerializer::class) + public interface MetaRepr { public fun toMeta(): Meta } /** - * Generic meta tree representation. Elements are [TypedMetaItem] objects that could be represented by three different entities: - * * [MetaItemValue] (leaf) - * * [MetaItemNode] single node - * - * * Same name siblings are supported via elements with the same [Name] but different queries + * A meta node + * TODO add documentation + * Same name siblings are supported via elements with the same [Name] but different indices. */ +@Type(Meta.TYPE) @Serializable(MetaSerializer::class) -public interface Meta : MetaRepr, ItemProvider { - /** - * Top level items of meta tree - */ - public val items: Map - - override fun getItem(name: Name): MetaItem? { - if (name.isEmpty()) return MetaItemNode(this) - return name.firstOrNull()?.let { token -> - val tail = name.cutFirst() - when (tail.length) { - 0 -> items[token] - else -> items[token]?.node?.get(tail) - } - } - } +public interface Meta : MetaRepr { + public val value: Value? + public val items: Map override fun toMeta(): Meta = this - override fun equals(other: Any?): Boolean - - override fun hashCode(): Int - override fun toString(): String + override fun equals(other: Any?): Boolean + override fun hashCode(): Int public companion object { public const val TYPE: String = "meta" @@ -54,43 +40,179 @@ public interface Meta : MetaRepr, ItemProvider { * A key for single value node */ public const val VALUE_KEY: String = "@value" + public const val INDEX_KEY: String = "@index" - public fun equals(meta1: Meta?, meta2: Meta?): Boolean = meta1?.items == meta2?.items - - public val EMPTY: Meta = object : MetaBase() { - override val items: Map = emptyMap() + public fun hashCode(meta: Meta): Int { + var result = meta.value?.hashCode() ?: 0 + result = 31 * result + meta.items.hashCode() + return result } + + public fun equals(meta1: Meta?, meta2: Meta?): Boolean { + return meta1?.value == meta2?.value && meta1?.items == meta2?.items + } + + private val json = Json { + prettyPrint = true + useArrayPolymorphism = true + } + + public fun toString(meta: Meta): String = json.encodeToString(MetaSerializer, meta) + + public val EMPTY: Meta = SealedMeta(null, emptyMap()) } } -public operator fun Meta.get(token: NameToken): MetaItem? = items.get(token) + +public operator fun Meta.get(token: NameToken): Meta? = items[token] /** - * Get a sequence of [Name]-[Value] pairs + * Perform recursive item search using given [name]. Each [NameToken] is treated as a name in [Meta.items] of a parent node. + * + * If [name] is empty return current [Meta] */ -public fun Meta.valueSequence(): Sequence> { - return items.asSequence().flatMap { (key, item) -> - when (item) { - is MetaItemValue -> sequenceOf(key.asName() to item.value) - is MetaItemNode -> item.node.valueSequence().map { pair -> (key.asName() + pair.first) to pair.second } +public tailrec operator fun Meta.get(name: Name): Meta? = if (name.isEmpty()) { + this +} else { + get(name.firstOrNull()!!)?.get(name.cutFirst()) +} + +/** + * Parse [Name] from [key] using full name notation and pass it to [Meta.get] + */ +public operator fun Meta.get(key: String): Meta? = this[key.toName()] + +/** + * Get all items matching given name. The index of the last element, if present is used as a [Regex], + * against which indexes of elements are matched. + */ +public fun Meta.getIndexed(name: Name): Map { + val root: Meta = when (name.length) { + 0 -> error("Can't use empty name for 'getIndexed'") + 1 -> this + else -> this[name.cutLast()] ?: return emptyMap() + } + + val (body, index) = name.lastOrNull()!! + return if (index == null) { + root.items + .filter { it.key.body == body } + .mapKeys { it.key.index } + } else { + val regex = index.toRegex() + root.items + .filter { it.key.body == body && (regex.matches(it.key.index ?: "")) } + .mapKeys { it.key.index } + } +} + + +/** + * A meta node that ensures that all of its descendants has at least the same type. + * + */ +public interface TypedMeta> : Meta { + + /** + * Access self as a recursive type instance + */ + @Suppress("UNCHECKED_CAST") + public val self: M + get() = this as M + + override val value: Value? + override val items: Map + + override fun toMeta(): Meta = this +} + +//public typealias Meta = TypedMeta<*> + +public operator fun > TypedMeta.get(token: NameToken): M? = items[token] + +/** + * Perform recursive item search using given [name]. Each [NameToken] is treated as a name in [TypedMeta.items] of a parent node. + * + * If [name] is empty return current [Meta] + */ +public tailrec operator fun > TypedMeta.get(name: Name): M? = if (name.isEmpty()) { + self +} else { + get(name.firstOrNull()!!)?.get(name.cutFirst()) +} + +/** + * Parse [Name] from [key] using full name notation and pass it to [TypedMeta.get] + */ +public operator fun > TypedMeta.get(key: String): M? = this[key.toName()] + + +/** + * Get a sequence of [Name]-[Value] pairs using top-down traversal of the tree + */ +public fun Meta.valueSequence(): Sequence> = sequence { + items.forEach { (key, item) -> + item.value?.let { itemValue -> + yield(key.asName() to itemValue) } + yieldAll(item.valueSequence().map { pair -> (key.asName() + pair.first) to pair.second }) } } /** - * Get a sequence of all [Name]-[TypedMetaItem] pairs for all items including nodes + * Get a sequence of all [Name]-[TypedMeta] pairs in a top-down traversal */ -public fun Meta.itemSequence(): Sequence> = sequence { +public fun Meta.nodeSequence(): Sequence> = sequence { items.forEach { (key, item) -> yield(key.asName() to item) - if (item is MetaItemNode) { - yieldAll(item.node.itemSequence().map { (innerKey, innerItem) -> - (key + innerKey) to innerItem - }) - } + yieldAll(item.nodeSequence().map { (innerKey, innerItem) -> + (key + innerKey) to innerItem + }) } } -public operator fun Meta.iterator(): Iterator> = itemSequence().iterator() +public operator fun Meta.iterator(): Iterator> = nodeSequence().iterator() -public fun Meta.isEmpty(): Boolean = this === Meta.EMPTY || this.items.isEmpty() \ No newline at end of file +public fun Meta.isEmpty(): Boolean = this === Meta.EMPTY || (value == null && items.isEmpty()) + + +/* Get operations*/ + +/** + * Get all items matching given name. The index of the last element, if present is used as a [Regex], + * against which indexes of elements are matched. + */ +@Suppress("UNCHECKED_CAST") +public fun > TypedMeta.getIndexed(name: Name): Map = + (this as Meta).getIndexed(name) as Map + +public fun > TypedMeta.getIndexed(name: String): Map = getIndexed(name.toName()) + + +public val Meta?.string: String? get() = this?.value?.string +public val Meta?.boolean: Boolean? get() = this?.value?.boolean +public val Meta?.number: Number? get() = this?.value?.numberOrNull +public val Meta?.double: Double? get() = number?.toDouble() +public val Meta?.float: Float? get() = number?.toFloat() +public val Meta?.int: Int? get() = number?.toInt() +public val Meta?.long: Long? get() = number?.toLong() +public val Meta?.short: Short? get() = number?.toShort() + +public inline fun > Meta?.enum(): E? = this?.value?.let { + if (it is EnumValue<*>) { + it.value as E + } else { + string?.let { str -> enumValueOf(str) } + } +} + +public val Meta.stringList: List? get() = value?.list?.map { it.string } + +/** + * Create a provider that uses given provider for default values if those are not found in this provider + */ +public fun Meta.withDefault(default: Meta?): Meta = if (default == null) { + this +} else { + Laminate(this, default) +} \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MetaBuilder.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MetaBuilder.kt deleted file mode 100644 index c1870a77..00000000 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MetaBuilder.kt +++ /dev/null @@ -1,146 +0,0 @@ -package space.kscience.dataforge.meta - -import kotlinx.serialization.Serializable -import space.kscience.dataforge.misc.DFBuilder -import space.kscience.dataforge.names.Name -import space.kscience.dataforge.names.NameToken -import space.kscience.dataforge.names.asName -import space.kscience.dataforge.values.EnumValue -import space.kscience.dataforge.values.Value -import space.kscience.dataforge.values.asValue -import kotlin.jvm.JvmName - -/** - * DSL builder for meta. Is not intended to store mutable state - */ -@DFBuilder -@Serializable -public class MetaBuilder : AbstractMutableMeta() { - override val children: MutableMap> = LinkedHashMap() - - override fun wrapNode(meta: Meta): MetaBuilder = if (meta is MetaBuilder) meta else meta.toMutableMeta() - override fun empty(): MetaBuilder = MetaBuilder() - - public infix fun String.put(item: MetaItem?) { - set(this, item) - } - - public infix fun String.put(value: Value?) { - set(this, value) - } - - public infix fun String.put(string: String?) { - set(this, string?.asValue()) - } - - public infix fun String.put(number: Number?) { - set(this, number?.asValue()) - } - - public infix fun String.put(boolean: Boolean?) { - set(this, boolean?.asValue()) - } - - public infix fun String.put(enum: Enum<*>) { - set(this, EnumValue(enum)) - } - - @JvmName("putValues") - public infix fun String.put(iterable: Iterable) { - set(this, iterable.asValue()) - } - - @JvmName("putNumbers") - public infix fun String.put(iterable: Iterable) { - set(this, iterable.map { it.asValue() }.asValue()) - } - - @JvmName("putStrings") - public infix fun String.put(iterable: Iterable) { - set(this, iterable.map { it.asValue() }.asValue()) - } - - public infix fun String.put(array: DoubleArray) { - set(this, array.asValue()) - } - - public infix fun String.put(meta: Meta?) { - this@MetaBuilder[this] = meta - } - - public infix fun String.put(repr: MetaRepr?) { - set(this, repr?.toMeta()) - } - - @JvmName("putMetas") - public infix fun String.put(value: Iterable) { - set(this,value.toList()) - } - - public inline infix fun String.put(metaBuilder: MetaBuilder.() -> Unit) { - this@MetaBuilder[this] = MetaBuilder().apply(metaBuilder) - } - - public infix fun Name.put(value: Value?) { - set(this, value) - } - - public infix fun Name.put(string: String?) { - set(this, string?.asValue()) - } - - public infix fun Name.put(number: Number?) { - set(this, number?.asValue()) - } - - public infix fun Name.put(boolean: Boolean?) { - set(this, boolean?.asValue()) - } - - public infix fun Name.put(enum: Enum<*>) { - set(this, EnumValue(enum)) - } - - @JvmName("putValues") - public infix fun Name.put(iterable: Iterable) { - set(this, iterable.asValue()) - } - - public infix fun Name.put(meta: Meta?) { - this@MetaBuilder[this] = meta - } - - public infix fun Name.put(repr: MetaRepr?) { - set(this, repr?.toMeta()) - } - - @JvmName("putMetas") - public infix fun Name.put(value: Iterable) { - set(this, value.toList()) - } - - public infix fun Name.put(metaBuilder: MetaBuilder.() -> Unit) { - this@MetaBuilder[this] = MetaBuilder().apply(metaBuilder) - } -} - -/** - * For safety, builder always copies the initial meta even if it is builder itself - */ -public fun Meta.toMutableMeta(): MetaBuilder { - return MetaBuilder().also { builder -> - items.mapValues { entry -> - val item = entry.value - builder[entry.key.asName()] = when (item) { - is MetaItemValue -> item.value - is MetaItemNode -> MetaItemNode(item.node.toMutableMeta()) - } - } - } -} - -/** - * Build a [MetaBuilder] using given transformation - */ -@Suppress("FunctionName") -public inline fun Meta(builder: MetaBuilder.() -> Unit): MetaBuilder = MetaBuilder().apply(builder) \ No newline at end of file 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 new file mode 100644 index 00000000..673dd2bf --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MetaDelegate.kt @@ -0,0 +1,111 @@ +package space.kscience.dataforge.meta + +import space.kscience.dataforge.meta.transformations.MetaConverter +import space.kscience.dataforge.names.Name +import space.kscience.dataforge.names.asName +import space.kscience.dataforge.values.Value +import kotlin.properties.ReadOnlyProperty + +/* Meta delegates */ + +public typealias MetaDelegate = ReadOnlyProperty + +public fun Meta.item(key: Name? = null): MetaDelegate = ReadOnlyProperty { _, property -> + get(key ?: property.name.asName()) +} + +//TODO add caching for sealed nodes + + +/** + * Apply a converter to this delegate creating a delegate with a custom type + */ +public fun MetaDelegate.convert( + converter: MetaConverter, +): ReadOnlyProperty = ReadOnlyProperty { thisRef, property -> + this@convert.getValue(thisRef, property)?.let(converter::itemToObject) +} + +/* + * + */ +public fun MetaDelegate.convert( + converter: MetaConverter, + default: () -> R, +): ReadOnlyProperty = ReadOnlyProperty { thisRef, property -> + this@convert.getValue(thisRef, property)?.let(converter::itemToObject) ?: default() +} + +/** + * A converter with a custom reader transformation + */ +public fun MetaDelegate.convert( + reader: (Meta?) -> R, +): ReadOnlyProperty = ReadOnlyProperty { thisRef, property -> + this@convert.getValue(thisRef, property).let(reader) +} + +/* Read-only delegates for [Meta] */ + +/** + * A property delegate that uses custom key + */ +public fun Meta.value(key: Name? = null): ReadOnlyProperty = + item(key).convert(MetaConverter.value) + +public fun Meta.string(key: Name? = null): ReadOnlyProperty = + item(key).convert(MetaConverter.string) + +public fun Meta.boolean(key: Name? = null): ReadOnlyProperty = + item(key).convert(MetaConverter.boolean) + +public fun Meta.number(key: Name? = null): ReadOnlyProperty = + item(key).convert(MetaConverter.number) + +public fun Meta.double(key: Name? = null): ReadOnlyProperty = + item(key).convert(MetaConverter.double) + +public fun Meta.float(key: Name? = null): ReadOnlyProperty = + item(key).convert(MetaConverter.float) + +public fun Meta.int(key: Name? = null): ReadOnlyProperty = + item(key).convert(MetaConverter.int) + +public fun Meta.long(key: Name? = null): ReadOnlyProperty = + item(key).convert(MetaConverter.long) + +public fun Meta.node(key: Name? = null): ReadOnlyProperty = + item(key).convert(MetaConverter.meta) + +public fun Meta.string(default: String, key: Name? = null): ReadOnlyProperty = + item(key).convert(MetaConverter.string) { default } + +public fun Meta.boolean(default: Boolean, key: Name? = null): ReadOnlyProperty = + item(key).convert(MetaConverter.boolean) { default } + +public fun Meta.number(default: Number, key: Name? = null): ReadOnlyProperty = + item(key).convert(MetaConverter.number) { default } + +public fun Meta.double(default: Double, key: Name? = null): ReadOnlyProperty = + item(key).convert(MetaConverter.double) { default } + +public fun Meta.float(default: Float, key: Name? = null): ReadOnlyProperty = + item(key).convert(MetaConverter.float) { default } + +public fun Meta.int(default: Int, key: Name? = null): ReadOnlyProperty = + item(key).convert(MetaConverter.int) { default } + +public fun Meta.long(default: Long, key: Name? = null): ReadOnlyProperty = + item(key).convert(MetaConverter.long) { default } + +public inline fun > Meta.enum(default: E, key: Name? = null): ReadOnlyProperty = + item(key).convert(MetaConverter.enum()) { default } + +public fun Meta.string(key: Name? = null, default: () -> String): ReadOnlyProperty = + item(key).convert(MetaConverter.string, default) + +public fun Meta.boolean(key: Name? = null, default: () -> Boolean): ReadOnlyProperty = + item(key).convert(MetaConverter.boolean, default) + +public fun Meta.number(key: Name? = null, default: () -> Number): ReadOnlyProperty = + item(key).convert(MetaConverter.number, default) diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MetaItem.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MetaItem.kt deleted file mode 100644 index a486a6ee..00000000 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MetaItem.kt +++ /dev/null @@ -1,96 +0,0 @@ -package space.kscience.dataforge.meta - -import kotlinx.serialization.Serializable -import space.kscience.dataforge.names.Name -import space.kscience.dataforge.names.isEmpty -import space.kscience.dataforge.values.* - -/** - * A member of the meta tree. Could be represented as one of following: - * * a [MetaItemValue] (leaf) - * * a [MetaItemNode] (node) - */ -@Serializable(MetaItemSerializer::class) -public sealed class TypedMetaItem : ItemProvider { - - abstract override fun equals(other: Any?): Boolean - - abstract override fun hashCode(): Int - - public companion object { - public fun of(arg: Any?): MetaItem { - return when (arg) { - null -> Null.asMetaItem() - is MetaItem -> arg - is Meta -> arg.asMetaItem() - is ItemProvider -> arg.rootItem ?: Null.asMetaItem() - else -> Value.of(arg).asMetaItem() - } - } - } -} - -public typealias MetaItem = TypedMetaItem<*> - -@Serializable(MetaItemSerializer::class) -public class MetaItemValue(public val value: Value) : TypedMetaItem() { - override fun getItem(name: Name): MetaItem? = if (name.isEmpty()) this else null - - override fun toString(): String = value.toString() - - override fun equals(other: Any?): Boolean { - return this.value == (other as? MetaItemValue)?.value - } - - override fun hashCode(): Int { - return value.hashCode() - } -} - -@Serializable(MetaItemSerializer::class) -public class MetaItemNode(public val node: M) : TypedMetaItem() { - override fun getItem(name: Name): MetaItem? = if (name.isEmpty()) this else node.getItem(name) - - //Fixing serializer for node could cause class cast problems, but it should not since Meta descendants are not serializable - override fun toString(): String = node.toString() - - override fun equals(other: Any?): Boolean = Meta.equals(node, (other as? MetaItemNode<*>)?.node) - - override fun hashCode(): Int = node.hashCode() -} - -public fun Value.asMetaItem(): MetaItemValue = MetaItemValue(this) -public fun M.asMetaItem(): MetaItemNode = MetaItemNode(this) - - -/** - * Unsafe methods to access values and nodes directly from [TypedMetaItem] - */ -public val MetaItem?.value: Value? - get() = (this as? MetaItemValue)?.value - ?: (this?.node?.get(Meta.VALUE_KEY) as? MetaItemValue)?.value - -public val MetaItem?.string: String? get() = value?.string -public val MetaItem?.boolean: Boolean? get() = value?.boolean -public val MetaItem?.number: Number? get() = value?.numberOrNull -public val MetaItem?.double: Double? get() = number?.toDouble() -public val MetaItem?.float: Float? get() = number?.toFloat() -public val MetaItem?.int: Int? get() = number?.toInt() -public val MetaItem?.long: Long? get() = number?.toLong() -public val MetaItem?.short: Short? get() = number?.toShort() - -public inline fun > MetaItem?.enum(): E? = - if (this is MetaItemValue && this.value is EnumValue<*>) { - this.value.value as E - } else { - string?.let { enumValueOf(it) } - } - -public val MetaItem.stringList: List? get() = value?.list?.map { it.string } - -public val TypedMetaItem?.node: M? - get() = when (this) { - null -> null - is MetaItemValue -> null//error("Trying to interpret value meta item as node item") - is MetaItemNode -> node - } \ No newline at end of file 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 3285dc65..b447faa5 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,80 +1,46 @@ package space.kscience.dataforge.meta -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.KSerializer import kotlinx.serialization.builtins.MapSerializer -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* -import kotlinx.serialization.json.JsonDecoder -import kotlinx.serialization.json.JsonEncoder -import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.JsonElement import space.kscience.dataforge.names.NameToken import space.kscience.dataforge.names.NameTokenSerializer -import space.kscience.dataforge.values.ValueSerializer - -public object MetaItemSerializer : KSerializer { - - @OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class) - override val descriptor: SerialDescriptor = buildSerialDescriptor("MetaItem", PolymorphicKind.SEALED) { - element("isNode") - element("value", buildSerialDescriptor("MetaItem.value", SerialKind.CONTEXTUAL)) - } - - override fun deserialize(decoder: Decoder): MetaItem { - decoder.decodeStructure(descriptor) { - //Force strict serialization order - require(decodeElementIndex(descriptor) == 0) { "Node flag must be first item serialized" } - val isNode = decodeBooleanElement(descriptor, 0) - require(decodeElementIndex(descriptor) == 1) { "Missing MetaItem content" } - val item = if (isNode) { - decodeSerializableElement(descriptor,1, MetaSerializer).asMetaItem() - } else { - decodeSerializableElement(descriptor,1, ValueSerializer).asMetaItem() - } - require(decodeElementIndex(descriptor) == CompositeDecoder.DECODE_DONE){"Serialized MetaItem contains additional fields"} - return item - } - } - - override fun serialize(encoder: Encoder, value: MetaItem) { - encoder.encodeStructure(descriptor) { - encodeBooleanElement(descriptor, 0, value is MetaItemNode) - when (value) { - is MetaItemValue -> encodeSerializableElement(descriptor, 1, ValueSerializer, value.value) - is MetaItemNode -> encodeSerializableElement(descriptor, 1, MetaSerializer, value.node) - } - } - } -} /** * Serialized for meta */ public object MetaSerializer : KSerializer { - private val mapSerializer: KSerializer>> = MapSerializer( + private val itemsSerializer: KSerializer> = MapSerializer( NameTokenSerializer, - MetaItemSerializer//MetaItem.serializer(MetaSerializer) + MetaSerializer ) - override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Meta") + override val descriptor: SerialDescriptor = JsonElement.serializer().descriptor - override fun deserialize(decoder: Decoder): Meta { - return if (decoder is JsonDecoder) { - JsonObject.serializer().deserialize(decoder).toMeta() - } else { - object : MetaBase() { - override val items: Map = mapSerializer.deserialize(decoder) - } - } - } + override fun deserialize(decoder: Decoder): Meta = JsonElement.serializer().deserialize(decoder).toMeta() override fun serialize(encoder: Encoder, value: Meta) { - if (encoder is JsonEncoder) { - JsonObject.serializer().serialize(encoder, value.toJson()) - } else { - mapSerializer.serialize(encoder, value.items) - } + JsonElement.serializer().serialize(encoder, value.toJson()) + } +} + +/** + * A serializer for [MutableMeta] + */ +public object MutableMetaSerializer : KSerializer { + override val descriptor: SerialDescriptor = MetaSerializer.descriptor + + + override fun deserialize(decoder: Decoder): MutableMeta { + val meta = decoder.decodeSerializableValue(MetaSerializer) + return (meta as? MutableMeta) ?: meta.toMutableMeta() + } + + override fun serialize(encoder: Encoder, value: MutableMeta) { + encoder.encodeSerializableValue(MetaSerializer, value) } } \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MutableItemProvider.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MutableItemProvider.kt deleted file mode 100644 index c9c51556..00000000 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MutableItemProvider.kt +++ /dev/null @@ -1,153 +0,0 @@ -package space.kscience.dataforge.meta - -import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import space.kscience.dataforge.names.* -import space.kscience.dataforge.values.Value - -@Serializable(MutableItemProviderSerializer::class) -public interface MutableItemProvider : ItemProvider { - public fun setItem(name: Name, item: MetaItem?) -} - -/** - * A serializer form [MutableItemProvider] - */ -public class MutableItemProviderSerializer : KSerializer { - override val descriptor: SerialDescriptor = MetaSerializer.descriptor - - - override fun deserialize(decoder: Decoder): MutableItemProvider { - val meta = decoder.decodeSerializableValue(MetaSerializer) - return (meta as? MetaBuilder) ?: meta.toMutableMeta() - } - - override fun serialize(encoder: Encoder, value: MutableItemProvider) { - encoder.encodeSerializableValue(MetaSerializer, value.rootItem?.node ?: Meta.EMPTY) - } -} - -public operator fun MutableItemProvider.set(name: Name, item: MetaItem?): Unit = setItem(name, item) - -public operator fun MutableItemProvider.set(name: Name, value: Value?): Unit = set(name, value?.asMetaItem()) - -public operator fun MutableItemProvider.set(name: Name, meta: Meta?): Unit = set(name, meta?.asMetaItem()) - -public operator fun MutableItemProvider.set(key: String, item: MetaItem?): Unit = set(key.toName(), item) - -public operator fun MutableItemProvider.set(key: String, meta: Meta?): Unit = set(key, meta?.asMetaItem()) - -@Suppress("NOTHING_TO_INLINE") -public inline fun MutableItemProvider.remove(name: Name): Unit = setItem(name, null) - -@Suppress("NOTHING_TO_INLINE") -public inline fun MutableItemProvider.remove(name: String): Unit = remove(name.toName()) - -/** - * Universal unsafe set method - */ -public operator fun MutableItemProvider.set(name: Name, value: Any?) { - when (value) { - null -> remove(name) - else -> set(name, MetaItem.of(value)) - } -} - -public operator fun MutableItemProvider.set(name: NameToken, value: Any?): Unit = - set(name.asName(), value) - -public operator fun MutableItemProvider.set(key: String, value: Any?): Unit = - set(key.toName(), value) - -public operator fun MutableItemProvider.set(key: String, index: String, value: Any?): Unit = - set(key.toName().withIndex(index), value) - - -/* Same name siblings generation */ - -public fun MutableItemProvider.setIndexedItems( - name: Name, - items: Iterable, - indexFactory: (MetaItem, index: Int) -> String = { _, index -> index.toString() }, -) { - val tokens = name.tokens.toMutableList() - val last = tokens.last() - items.forEachIndexed { index, meta -> - val indexedToken = NameToken(last.body, (last.index ?: "") + indexFactory(meta, index)) - tokens[tokens.lastIndex] = indexedToken - set(Name(tokens), meta) - } -} - -public fun MutableItemProvider.setIndexed( - name: Name, - metas: Iterable, - indexFactory: (Meta, index: Int) -> String = { _, index -> index.toString() }, -) { - setIndexedItems(name, metas.map { MetaItemNode(it) }) { item, index -> indexFactory(item.node!!, index) } -} - -public operator fun MutableItemProvider.set(name: Name, metas: Iterable): Unit = setIndexed(name, metas) -public operator fun MutableItemProvider.set(name: String, metas: Iterable): Unit = - setIndexed(name.toName(), metas) - -/** - * Get a [MutableItemProvider] referencing a child node - */ -public fun MutableItemProvider.getChild(childName: Name): MutableItemProvider { - fun createProvider() = object : MutableItemProvider { - override fun setItem(name: Name, item: MetaItem?) { - this@getChild.setItem(childName + name, item) - } - - override fun getItem(name: Name): MetaItem? = this@getChild.getItem(childName + name) - } - - return when { - childName.isEmpty() -> this - this is MutableMeta<*> -> { - get(childName).node ?: createProvider() - } - else -> { - createProvider() - } - } -} - -public fun MutableItemProvider.getChild(childName: String): MutableItemProvider = getChild(childName.toName()) - - -/** - * Update existing mutable node with another node. The rules are following: - * * value replaces anything - * * 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 MutableItemProvider.update(meta: Meta) { - meta.valueSequence().forEach { (name, value) -> set(name, value) } -} - -/** - * Edit a provider child at given name location - */ -public fun MutableItemProvider.editChild(name: Name, builder: MutableItemProvider.() -> Unit): MutableItemProvider = - getChild(name).apply(builder) - -/** - * 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 MutableItemProvider.withDefault(default: ItemProvider?): MutableItemProvider = - if (default == null || (default is Meta && default.isEmpty())) { - //Optimize for use with empty default - this - } else object : MutableItemProvider { - override fun setItem(name: Name, item: MetaItem?) { - this@withDefault.setItem(name, item) - } - - override fun getItem(name: Name): MetaItem? = this@withDefault.getItem(name) ?: default.getItem(name) - } \ No newline at end of file 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 6a8b0203..ed64e153 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,74 +1,352 @@ 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.jvm.Synchronized -public interface MutableMeta> : TypedMeta, MutableItemProvider { - override val items: Map> -} /** - * A mutable meta node with attachable change listener. - * - * Changes in Meta are not thread safe. + * Mutable variant of [Meta] + * TODO documentation */ -public abstract class AbstractMutableMeta> : AbstractTypedMeta(), MutableMeta { +@Serializable(MutableMetaSerializer::class) +public interface MutableMeta : Meta { - protected abstract val children: MutableMap> + /** + * Get or set value of this node + */ + override var value: Value? - override val items: Map> get() = children + /** + * Set or replace node at given [name] + */ + public operator fun set(name: Name, meta: Meta) - //protected abstract fun itemChanged(name: Name, oldItem: MetaItem<*>?, newItem: MetaItem<*>?) + /** + * Remove a note at a given [name] if it is present + */ + public fun removeNode(name: Name) - protected open fun replaceItem(key: NameToken, oldItem: TypedMetaItem?, newItem: TypedMetaItem?) { - if (newItem == null) { - children.remove(key) - } else { - children[key] = newItem + /** + * Get existing node or create a new one + */ + public fun getOrCreate(name: Name): MutableMeta + + //TODO to be moved to extensions with multi-receivers + + public infix fun Name.put(value: Value?) { + set(this, value) + } + + public infix fun Name.put(string: String) { + set(this, string.asValue()) + } + + public infix fun Name.put(number: Number) { + set(this, number.asValue()) + } + + public infix fun Name.put(boolean: Boolean) { + set(this, boolean.asValue()) + } + + public infix fun Name.put(enum: Enum<*>) { + set(this, EnumValue(enum)) + } + + public infix fun Name.put(iterable: Iterable) { + setIndexed(this, iterable) + } + + public infix fun Name.put(meta: Meta) { + set(this, meta) + } + + public infix fun Name.put(repr: MetaRepr) { + put(repr.toMeta()) + } + + public infix fun Name.put(mutableMeta: MutableMeta.() -> Unit) { + set(this, Meta(mutableMeta)) + } + + public infix fun String.put(meta: Meta) { + toName() put meta + } + + public infix fun String.put(value: Value?) { + set(toName(), value) + } + + public infix fun String.put(string: String) { + set(toName(), string.asValue()) + } + + public infix fun String.put(number: Number) { + set(toName(), number.asValue()) + } + + public infix fun String.put(boolean: Boolean) { + set(toName(), boolean.asValue()) + } + + public infix fun String.put(enum: Enum<*>) { + set(toName(), EnumValue(enum)) + } + + public infix fun String.put(array: DoubleArray) { + set(toName(), array.asValue()) + } + + public infix fun String.put(repr: MetaRepr) { + toName() put repr.toMeta() + } + + public infix fun String.put(builder: MutableMeta.() -> Unit) { + set(toName(), MutableMeta(builder)) + } +} + +@Serializable(MutableMetaSerializer::class) +public interface MutableTypedMeta> : TypedMeta, MutableMeta { + /** + * Zero-copy attach or replace existing node. Node is used with any additional state, listeners, etc. + * In some cases it is possible to have the same node as a child to several others + */ + public fun attach(name: Name, node: M) + + override fun getOrCreate(name: Name): M +} + +public operator fun MutableMeta.set(key: String, item: Meta?): Unit = + set(key.toName(), item) + + +public fun MutableMeta.remove(name: Name): Unit = set(name, null) + +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: 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( + name: Name, + items: Iterable, + indexFactory: (Meta, index: Int) -> String = { _, index -> index.toString() }, +) { + val tokens = name.tokens.toMutableList() + val last = tokens.last() + items.forEachIndexed { index, meta -> + val indexedToken = NameToken(last.body, (last.index ?: "") + indexFactory(meta, index)) + tokens[tokens.lastIndex] = indexedToken + set(Name(tokens), meta) + } +} + +public fun MutableMeta.setIndexed( + name: Name, + metas: Iterable, + indexFactory: (Meta, index: Int) -> String = { _, index -> index.toString() }, +) { + setIndexedItems(name, metas) { item, index -> indexFactory(item, index) } +} + +public operator fun > MutableTypedMeta.set(name: Name, metas: Iterable): Unit = + setIndexed(name, metas) + +public operator fun > MutableTypedMeta.set(name: String, metas: Iterable): Unit = + setIndexed(name.toName(), metas) + + +/** + * Update existing mutable node with another node. The rules are following: + * * value replaces anything + * * 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) { + meta.valueSequence().forEach { (name, value) -> + set(name, value) + } +} + +///** +// * Get child with given name or create a new one +// */ +//public fun > MutableTypedMeta.getOrCreate(name: Name): M = +// get(name) ?: empty().also { attach(name, it) } + +/** + * Edit node at [name] + */ +public fun > MutableTypedMeta.edit(name: Name, builder: M.() -> Unit): M = + getOrCreate(name).apply(builder) + +/** + * Set a value at a given [name]. If node does not exist, create it. + */ +public operator fun > MutableTypedMeta.set(name: Name, value: Value?) { + edit(name) { + this.value = value + } +} + +///** +// * 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?, + children: Map = emptyMap() +) : ObservableMutableMeta { + + private val children: LinkedHashMap = + LinkedHashMap(children.mapValues { (key, meta) -> + MutableMetaImpl(meta.value, meta.items).apply { adoptBy(this, key) } + }) + + override val items: Map get() = children + + private val listeners = HashSet() + + private fun changed(name: Name) { + listeners.forEach { it.callback(this, name) } + } + + @Synchronized + override fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit) { + listeners.add(MetaListener(owner, callback)) + } + + @Synchronized + override fun removeListener(owner: Any?) { + listeners.removeAll { it.owner === owner } + } + + + private fun adoptBy(parent: MutableMetaImpl, key: NameToken) { + onChange(parent) { name -> + parent.changed(key + name) } - //itemChanged(key.asName(), oldItem, newItem) } - private fun wrapItem(item: MetaItem?): TypedMetaItem? = when (item) { - null -> null - is MetaItemValue -> item - is MetaItemNode -> MetaItemNode(wrapNode(item.node)) - } - - /** - * Transform given meta to node type of this meta tree - */ - protected abstract fun wrapNode(meta: Meta): M - - /** - * Create empty node - */ - internal abstract fun empty(): M - - override fun setItem(name: Name, item: MetaItem?) { + override fun attach(name: Name, node: ObservableMutableMeta) { when (name.length) { - 0 -> error("Can't set a meta item for empty name") + 0 -> error("Can't set a meta with empty name") 1 -> { - val token = name.firstOrNull()!! - val oldItem: TypedMetaItem? = getItem(name) - replaceItem(token, oldItem, wrapItem(item)) - } - else -> { - val token = name.firstOrNull()!! - //get existing or create new node. Query is ignored for new node - if (items[token] == null) { - replaceItem(token, null, MetaItemNode(empty())) - } - items[token]?.node!!.set(name.cutFirst(), item) + val key = name.firstOrNull()!! + children[key] = node + adoptBy(this, key) + changed(name) } + else -> get(name.cutLast())?.attach(name.lastOrNull()!!.asName(), node) } } + + override fun getOrCreate(name: Name): ObservableMutableMeta = + get(name) ?: MutableMetaImpl(null).also { attach(name, it) } + + override fun removeNode(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()) + } + } + + private fun replaceItem( + key: NameToken, + oldItem: ObservableMutableMeta?, + newItem: MutableMetaImpl? + ) { + if (oldItem != newItem) { + if (newItem == null) { + children.remove(key) + } else { + newItem.adoptBy(this, key) + children[key] = newItem + } + changed(key.asName()) + } + } + + private fun wrapItem(meta: Meta): MutableMetaImpl = + MutableMetaImpl(meta.value, meta.items.mapValuesTo(LinkedHashMap()) { wrapItem(it.value) }) + + + override fun set(name: Name, meta: Meta) { + val oldItem: ObservableMutableMeta? = get(name) + if (oldItem != meta) { + when (name.length) { + 0 -> error("Can't set a meta with empty name") + 1 -> { + val token = name.firstOrNull()!! + replaceItem(token, oldItem, wrapItem(meta)) + } + else -> { + val token = name.firstOrNull()!! + //get existing or create new node. Index is ignored for new node + if (items[token] == null) { + replaceItem(token, null, MutableMetaImpl(null)) + } + items[token]?.set(name.cutFirst(), meta) + } + } + changed(name) + } + } + + override fun toString(): String = Meta.toString(this) + override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta) + override fun hashCode(): Int = Meta.hashCode(this) } /** * Append the node with a same-name-sibling, automatically generating numerical index */ -public fun MutableItemProvider.append(name: Name, value: Any?) { +@DFExperimental +public fun MutableMeta.append(name: Name, value: Any?) { require(!name.isEmpty()) { "Name could not be empty for append operation" } val newIndex = name.lastOrNull()!!.index if (newIndex != null) { @@ -79,17 +357,40 @@ public fun MutableItemProvider.append(name: Name, value: Any?) { } } -public fun MutableItemProvider.append(name: String, value: Any?): Unit = append(name.toName(), value) +@DFExperimental +public fun MutableMeta.append(name: String, value: Any?): Unit = append(name.toName(), value) + +///** +// * Apply existing node with given [builder] or create a new element with it. +// */ +//@DFExperimental +//public fun MutableMeta.edit(name: Name, builder: MutableMeta.() -> Unit) { +// val item = when (val existingItem = get(name)) { +// null -> MutableMeta().also { set(name, it) } +// is MetaItemNode -> existingItem.node +// else -> error("Can't edit value meta item") +// } +// item.apply(builder) +//} /** - * Apply existing node with given [builder] or create a new element with it. + * Create a mutable copy of this meta. The copy is created even if the Meta is already mutable */ -@DFExperimental -public fun > M.edit(name: Name, builder: M.() -> Unit) { - val item = when (val existingItem = get(name)) { - null -> empty().also { set(name, it) } - is MetaItemNode -> existingItem.node - else -> error("Can't edit value meta item") - } - item.apply(builder) -} \ No newline at end of file +public fun Meta.toMutableMeta(): ObservableMutableMeta = MutableMetaImpl(value, items) + +public fun Meta.asMutableMeta(): MutableMeta = (this as? MutableMeta) ?: toMutableMeta() + +/** + * Build a [MutableMeta] using given transformation + */ +@Suppress("FunctionName") +public fun MutableMeta(builder: MutableMeta.() -> Unit = {}): ObservableMutableMeta = + MutableMetaImpl(null).apply(builder) + + +/** + * Create a copy of this [Meta], optionally applying the given [block]. + * 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 diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MutableItemDelegate.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MutableMetaDelegate.kt similarity index 56% rename from dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MutableItemDelegate.kt rename to dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MutableMetaDelegate.kt index 075e9a04..b2fa42fc 100644 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MutableItemDelegate.kt +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/MutableMetaDelegate.kt @@ -9,14 +9,14 @@ import kotlin.reflect.KProperty /* Read-write delegates */ -public typealias MutableItemDelegate = ReadWriteProperty +public typealias MutableMetaDelegate = ReadWriteProperty -public fun MutableItemProvider.item(key: Name? = null): MutableItemDelegate = object : MutableItemDelegate { - override fun getValue(thisRef: Any?, property: KProperty<*>): MetaItem? { +public fun MutableMeta.item(key: Name? = null): MutableMetaDelegate = object : MutableMetaDelegate { + override fun getValue(thisRef: Any?, property: KProperty<*>): Meta? { return get(key ?: property.name.asName()) } - override fun setValue(thisRef: Any?, property: KProperty<*>, value: MetaItem?) { + override fun setValue(thisRef: Any?, property: KProperty<*>, value: Meta?) { val name = key ?: property.name.asName() set(name, value) } @@ -27,7 +27,7 @@ public fun MutableItemProvider.item(key: Name? = null): MutableItemDelegate = ob /** * A type converter for a mutable [TypedMetaItem] delegate */ -public fun MutableItemDelegate.convert( +public fun MutableMetaDelegate.convert( converter: MetaConverter, ): ReadWriteProperty = object : ReadWriteProperty { @@ -40,7 +40,7 @@ public fun MutableItemDelegate.convert( } } -public fun MutableItemDelegate.convert( +public fun MutableMetaDelegate.convert( converter: MetaConverter, default: () -> R, ): ReadWriteProperty = object : ReadWriteProperty { @@ -54,9 +54,9 @@ public fun MutableItemDelegate.convert( } } -public fun MutableItemDelegate.convert( - reader: (MetaItem?) -> R, - writer: (R) -> MetaItem?, +public fun MutableMetaDelegate.convert( + reader: (Meta?) -> R, + writer: (R) -> Meta?, ): ReadWriteProperty = object : ReadWriteProperty { override fun getValue(thisRef: Any?, property: KProperty<*>): R = @@ -74,120 +74,112 @@ public fun MutableItemDelegate.convert( /** * A property delegate that uses custom key */ -public fun MutableItemProvider.value(key: Name? = null): ReadWriteProperty = +public fun MutableMeta.value(key: Name? = null): ReadWriteProperty = item(key).convert(MetaConverter.value) -public fun MutableItemProvider.string(key: Name? = null): ReadWriteProperty = +public fun MutableMeta.string(key: Name? = null): ReadWriteProperty = item(key).convert(MetaConverter.string) -public fun MutableItemProvider.boolean(key: Name? = null): ReadWriteProperty = +public fun MutableMeta.boolean(key: Name? = null): ReadWriteProperty = item(key).convert(MetaConverter.boolean) -public fun MutableItemProvider.number(key: Name? = null): ReadWriteProperty = +public fun MutableMeta.number(key: Name? = null): ReadWriteProperty = item(key).convert(MetaConverter.number) -public fun MutableItemProvider.string(default: String, key: Name? = null): ReadWriteProperty = +public fun MutableMeta.string(default: String, key: Name? = null): ReadWriteProperty = item(key).convert(MetaConverter.string) { default } -public fun MutableItemProvider.boolean(default: Boolean, key: Name? = null): ReadWriteProperty = +public fun MutableMeta.boolean(default: Boolean, key: Name? = null): ReadWriteProperty = item(key).convert(MetaConverter.boolean) { default } -public fun MutableItemProvider.number(default: Number, key: Name? = null): ReadWriteProperty = +public fun MutableMeta.number(default: Number, key: Name? = null): ReadWriteProperty = item(key).convert(MetaConverter.number) { default } -public fun MutableItemProvider.value(key: Name? = null, default: () -> Value): ReadWriteProperty = +public fun MutableMeta.value(key: Name? = null, default: () -> Value): ReadWriteProperty = item(key).convert(MetaConverter.value, default) -public fun MutableItemProvider.string(key: Name? = null, default: () -> String): ReadWriteProperty = +public fun MutableMeta.string(key: Name? = null, default: () -> String): ReadWriteProperty = item(key).convert(MetaConverter.string, default) -public fun MutableItemProvider.boolean(key: Name? = null, default: () -> Boolean): ReadWriteProperty = +public fun MutableMeta.boolean(key: Name? = null, default: () -> Boolean): ReadWriteProperty = item(key).convert(MetaConverter.boolean, default) -public fun MutableItemProvider.number(key: Name? = null, default: () -> Number): ReadWriteProperty = +public fun MutableMeta.number(key: Name? = null, default: () -> Number): ReadWriteProperty = item(key).convert(MetaConverter.number, default) -public inline fun > MutableItemProvider.enum( +public inline fun > MutableMeta.enum( default: E, key: Name? = null, ): ReadWriteProperty = item(key).convert(MetaConverter.enum()) { default } -public fun MutableItemProvider.node(key: Name? = null): ReadWriteProperty = item(key).convert( - reader = { it.node }, - writer = { it?.asMetaItem() } -) - -public inline fun > M.node(key: Name? = null): ReadWriteProperty = - item(key).convert(reader = { it?.let { it.node as M } }, writer = { it?.let { MetaItemNode(it) } }) - /* Number delegates */ -public fun MutableItemProvider.int(key: Name? = null): ReadWriteProperty = +public fun MutableMeta.int(key: Name? = null): ReadWriteProperty = item(key).convert(MetaConverter.int) -public fun MutableItemProvider.double(key: Name? = null): ReadWriteProperty = +public fun MutableMeta.double(key: Name? = null): ReadWriteProperty = item(key).convert(MetaConverter.double) -public fun MutableItemProvider.long(key: Name? = null): ReadWriteProperty = +public fun MutableMeta.long(key: Name? = null): ReadWriteProperty = item(key).convert(MetaConverter.long) -public fun MutableItemProvider.float(key: Name? = null): ReadWriteProperty = +public fun MutableMeta.float(key: Name? = null): ReadWriteProperty = item(key).convert(MetaConverter.float) /* Safe number delegates*/ -public fun MutableItemProvider.int(default: Int, key: Name? = null): ReadWriteProperty = +public fun MutableMeta.int(default: Int, key: Name? = null): ReadWriteProperty = item(key).convert(MetaConverter.int) { default } -public fun MutableItemProvider.double(default: Double, key: Name? = null): ReadWriteProperty = +public fun MutableMeta.double(default: Double, key: Name? = null): ReadWriteProperty = item(key).convert(MetaConverter.double) { default } -public fun MutableItemProvider.long(default: Long, key: Name? = null): ReadWriteProperty = +public fun MutableMeta.long(default: Long, key: Name? = null): ReadWriteProperty = item(key).convert(MetaConverter.long) { default } -public fun MutableItemProvider.float(default: Float, key: Name? = null): ReadWriteProperty = +public fun MutableMeta.float(default: Float, key: Name? = null): ReadWriteProperty = item(key).convert(MetaConverter.float) { default } /* Extra delegates for special cases */ -public fun MutableItemProvider.stringList( +public fun MutableMeta.stringList( vararg default: String, key: Name? = null, ): ReadWriteProperty> = item(key).convert( reader = { it?.stringList ?: listOf(*default) }, - writer = { it.map { str -> str.asValue() }.asValue().asMetaItem() } + writer = { Meta(it.map { str -> str.asValue() }.asValue()) } ) -public fun MutableItemProvider.stringList( +public fun MutableMeta.stringList( key: Name? = null, ): ReadWriteProperty?> = item(key).convert( reader = { it?.stringList }, - writer = { it?.map { str -> str.asValue() }?.asValue()?.asMetaItem() } + writer = { it?.map { str -> str.asValue() }?.asValue()?.let { Meta(it) } } ) -public fun MutableItemProvider.numberList( +public fun MutableMeta.numberList( vararg default: Number, key: Name? = null, ): ReadWriteProperty> = item(key).convert( reader = { it?.value?.list?.map { value -> value.numberOrNull ?: Double.NaN } ?: listOf(*default) }, - writer = { it.map { num -> num.asValue() }.asValue().asMetaItem() } + writer = { Meta(it.map { num -> num.asValue() }.asValue()) } ) /* A special delegate for double arrays */ -public fun MutableItemProvider.doubleArray( +public fun MutableMeta.doubleArray( vararg default: Double, key: Name? = null, ): ReadWriteProperty = item(key).convert( reader = { it?.value?.doubleArray ?: doubleArrayOf(*default) }, - writer = { DoubleArrayValue(it).asMetaItem() } + writer = { Meta(DoubleArrayValue(it)) } ) -public fun MutableItemProvider.listValue( +public fun MutableMeta.listValue( key: Name? = null, writer: (T) -> Value = { Value.of(it) }, reader: (Value) -> T, diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ObservableItemProvider.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ObservableItemProvider.kt deleted file mode 100644 index 1593ef7a..00000000 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ObservableItemProvider.kt +++ /dev/null @@ -1,159 +0,0 @@ -package space.kscience.dataforge.meta - -import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import space.kscience.dataforge.names.* -import kotlin.js.JsName -import kotlin.jvm.Synchronized -import kotlin.reflect.KProperty1 - - -internal data class ItemListener( - val owner: Any? = null, - val action: (name: Name, oldItem: MetaItem?, newItem: MetaItem?) -> Unit, -) - -/** - * An item provider that could be observed and mutated - */ -public interface ObservableItemProvider : ItemProvider, MutableItemProvider { - /** - * Add change listener to this meta. Owner is declared to be able to remove listeners later. Listener without owner could not be removed - */ - public fun onChange(owner: Any?, action: (name: Name, oldItem: MetaItem?, newItem: MetaItem?) -> Unit) - - /** - * Remove all listeners belonging to given owner - */ - public fun removeListener(owner: Any?) -} - -private open class ObservableItemProviderWrapper( - open val itemProvider: MutableItemProvider, - open val parent: Pair? = null -) : ObservableItemProvider { - - override fun getItem(name: Name): MetaItem? = itemProvider.getItem(name) - - private val listeners = HashSet() - - @Synchronized - private fun itemChanged(name: Name, oldItem: MetaItem?, newItem: MetaItem?) { - listeners.forEach { it.action(name, oldItem, newItem) } - } - - override fun setItem(name: Name, item: MetaItem?) { - val oldItem = getItem(name) - itemProvider.setItem(name, item) - itemChanged(name, oldItem, item) - - //Recursively send update to parent listeners - parent?.let { (parentNode, token) -> - parentNode.itemChanged(token + name, oldItem, item) - } - } - - @Synchronized - override fun onChange(owner: Any?, action: (Name, MetaItem?, MetaItem?) -> Unit) { - listeners.add(ItemListener(owner, action)) - } - - @Synchronized - override fun removeListener(owner: Any?) { - listeners.removeAll { it.owner === owner } - } -} - -public fun MutableItemProvider.asObservable(): ObservableItemProvider = - (this as? ObservableItemProvider) ?: ObservableItemProviderWrapper(this) - -/** - * A Meta instance that could be both mutated and observed. - */ -@Serializable(ObservableMetaSerializer::class) -public interface ObservableMeta : ObservableItemProvider, MutableMeta - -/** - * A wrapper class that creates observable meta node from regular meta node - */ -private class ObservableMetaWrapper>( - override val itemProvider: M, - override val parent: Pair, Name>? = null -) : ObservableItemProviderWrapper(itemProvider, parent), ObservableMeta { - override fun equals(other: Any?): Boolean = (itemProvider == other) - - override fun hashCode(): Int = itemProvider.hashCode() - - override fun toString(): String = itemProvider.toString() - - private fun wrapItem(name: Name, item: TypedMetaItem): TypedMetaItem { - return when (item) { - is MetaItemValue -> item - is MetaItemNode -> ObservableMetaWrapper(item.node, this to name).asMetaItem() - } - } - - override fun getItem(name: Name): TypedMetaItem? = itemProvider[name]?.let { - wrapItem(name, it) - } - - override val items: Map> - get() = itemProvider.items.mapValues { (token, childItem: TypedMetaItem) -> - wrapItem(token.asName(), childItem) - } -} - -/** - * If this meta is observable return itself. Otherwise return an observable wrapper. The changes of initial meta are - * reflected on wrapper but are **NOT** observed. - */ -public fun > M.asObservable(): ObservableMeta = - (this as? ObservableMeta) ?: ObservableMetaWrapper(this) - -@JsName("buildObservableMeta") -public fun ObservableMeta(): ObservableMeta = MetaBuilder().asObservable() - -/** - * Use the value of the property in a [callBack]. - * The callback is called once immediately after subscription to pass the initial value. - * - * Optional [owner] property is used for - */ -public fun O.useProperty( - property: KProperty1, - owner: Any? = null, - callBack: O.(T) -> Unit, -) { - //Pass initial value. - callBack(property.get(this)) - onChange(owner) { name, oldItem, newItem -> - if (name.startsWith(property.name.asName()) && oldItem != newItem) { - callBack(property.get(this)) - } - } -} - -public object ObservableMetaSerializer : KSerializer { - public fun empty(): ObservableMeta = ObservableMeta() - override val descriptor: SerialDescriptor get() = MetaSerializer.descriptor - - override fun deserialize(decoder: Decoder): ObservableMeta { - return MetaSerializer.deserialize(decoder).toMutableMeta().asObservable() - } - - override fun serialize(encoder: Encoder, value: ObservableMeta) { - MetaSerializer.serialize(encoder, value) - } -} - -public operator fun ObservableMeta.get(token: NameToken): TypedMetaItem? = items[token] - -/** - * Create a copy of this config, optionally applying the given [block]. - * The listeners of the original Config are not retained. - */ -public inline fun ObservableMeta.copy(block: ObservableMeta.() -> Unit = {}): ObservableMeta = - asObservable().apply(block) \ No newline at end of file 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 new file mode 100644 index 00000000..3a0d3752 --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ObservableMeta.kt @@ -0,0 +1,116 @@ +package space.kscience.dataforge.meta + +import space.kscience.dataforge.names.Name +import space.kscience.dataforge.names.NameToken +import space.kscience.dataforge.names.asName +import space.kscience.dataforge.names.startsWith +import space.kscience.dataforge.values.Value +import kotlin.jvm.Synchronized +import kotlin.reflect.KProperty1 + + +internal data class MetaListener( + val owner: Any? = null, + val callback: Meta.(name: Name) -> Unit, +) + +/** + * An item provider that could be observed and mutated + */ +public interface ObservableMeta : Meta { + /** + * Add change listener to this meta. Owner is declared to be able to remove listeners later. Listener without owner could not be removed + */ + public fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit) + + /** + * Remove all listeners belonging to given owner + */ + public fun removeListener(owner: Any?) +} + +/** + * A [Meta] which is both observable and mutable + */ +public interface ObservableMutableMeta : ObservableMeta, MutableTypedMeta + +private class ObservableMetaWrapper>( + val origin: M, +) : ObservableMutableMeta, Meta by origin { + + private val listeners = HashSet() + + private fun changed(name: Name) { + listeners.forEach { it.callback(this, name) } + } + + @Synchronized + override fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit) { + listeners.add(MetaListener(owner, callback)) + } + + @Synchronized + override fun removeListener(owner: Any?) { + listeners.removeAll { it.owner === owner } + } + + override val items: Map> + get() = origin.items.mapValues { ObservableMetaWrapper(it.value) } + + override var value: Value? + get() = origin.value + set(value) { + origin.value = value + changed(Name.EMPTY) + } + + override fun attach(name: Name, node: ObservableMutableMeta) { + origin.attach(name, node.origin) + changed(name) + } + + override fun getOrCreate(name: Name): ObservableMutableMeta = + get(name) ?: ObservableMetaWrapper(origin.getOrCreate(name)) + + + override fun removeNode(name: Name) { + origin.removeNode(name) + changed(name) + } + + override fun set(name: Name, meta: Meta) { + val oldMeta = get(name) + origin[name] = meta + if (oldMeta != meta) { + changed(name) + } + } + + override fun toMeta(): Meta { + return origin.toMeta() + } +} + +public fun > MutableTypedMeta.asObservable(): ObservableMeta = + (this as? ObservableMeta) ?: ObservableMetaWrapper(self) + + +/** + * Use the value of the property in a [callBack]. + * The callback is called once immediately after subscription to pass the initial value. + * + * Optional [owner] property is used for + */ +public fun O.useProperty( + property: KProperty1, + owner: Any? = null, + callBack: O.(T) -> Unit, +) { + //Pass initial value. + callBack(property.get(this)) + onChange(owner) { name -> + if (name.startsWith(property.name.asName())) { + callBack(property.get(this@useProperty)) + } + } +} \ No newline at end of file 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 f9768a9a..574b57b0 100644 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/Scheme.kt +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/Scheme.kt @@ -1,54 +1,38 @@ package space.kscience.dataforge.meta -import space.kscience.dataforge.meta.descriptors.Described -import space.kscience.dataforge.meta.descriptors.NodeDescriptor -import space.kscience.dataforge.meta.descriptors.get -import space.kscience.dataforge.meta.descriptors.validateItem +import space.kscience.dataforge.meta.descriptors.* import space.kscience.dataforge.names.Name -import kotlin.jvm.Synchronized /** * 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( - private var items: ObservableItemProvider = ObservableMeta(), - final override var descriptor: NodeDescriptor? = null -) : Described, MetaRepr, ObservableItemProvider { +public open class Scheme internal constructor( + source: MutableMeta = MutableMeta() +) : Described, ObservableMutableMeta, Meta by source { - /** - * Add a listener to this scheme changes. If the inner provider is observable, then listening will be delegated to it. - * Otherwise, local listeners will be created. - */ - @Synchronized - override fun onChange(owner: Any?, action: (Name, MetaItem?, MetaItem?) -> Unit) { - items.onChange(owner, action) - } + private var source = source.asObservable() + + final override var descriptor: MetaDescriptor? = null + internal set - /** - * Remove all listeners belonging to given owner - */ - @Synchronized - override fun removeListener(owner: Any?) { - items.removeListener(owner) - } internal fun wrap( - items: MutableItemProvider, + items: MutableMeta, preserveDefault: Boolean = false ) { - this.items = if (preserveDefault) items.withDefault(this.items).asObservable() else items.asObservable() + this.source = if (preserveDefault) items.withDefault(this.source) else items } - - /** - * Get a property with default - */ - override fun getItem(name: Name): MetaItem? = items[name] ?: descriptor?.get(name)?.defaultValue +// +// /** +// * 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] */ - public open fun validateItem(name: Name, item: MetaItem?): Boolean { + public open fun validate(name: Name, item: Meta?): Boolean { val descriptor = descriptor?.get(name) return descriptor?.validateItem(item) ?: true } @@ -56,30 +40,25 @@ public open class Scheme( /** * Set a configurable property */ - override fun setItem(name: Name, item: MetaItem?) { - val oldItem = items[name] - if (oldItem != item) { - if (validateItem(name, item)) { - items[name] = item + override fun set(name: Name, meta: Meta) { + val oldItem = source[name] + if (oldItem != meta) { + if (validate(name, meta)) { + source[name] = meta } else { - error("Validation failed for property $name with value $item") + error("Validation failed for property $name with value $meta") } } } - override fun toMeta(): Laminate = Laminate(items.rootNode, descriptor?.defaultMeta) + override fun toMeta(): Laminate = Laminate(source, descriptor?.defaultMeta) } /** - * The scheme is considered empty only if its root item does not exist. - */ -public fun Scheme.isEmpty(): Boolean = rootItem == null - -/** - * Relocate scheme target onto given [MutableItemProvider]. Old provider does not get updates anymore. + * Relocate scheme target onto given [MutableTypedMeta]. Old provider does not get updates anymore. * Current state of the scheme used as a default. */ -public fun T.retarget(provider: MutableItemProvider): T = apply { +public fun T.retarget(provider: MutableMeta): T = apply { wrap(provider, true) } @@ -95,16 +74,16 @@ public open class SchemeSpec( private val builder: () -> T, ) : Specification, Described { - override fun read(items: ItemProvider): T = empty().also { - it.wrap(ObservableMeta().withDefault(items)) + override fun read(items: Meta): T = empty().also { + it.wrap(MutableMeta().withDefault(items)) } - override fun write(target: MutableItemProvider): T = empty().also { + override fun write(target: MutableMeta): T = empty().also { it.wrap(target) } //TODO Generate descriptor from Scheme class - override val descriptor: NodeDescriptor? get() = null + override val descriptor: MetaDescriptor? get() = null override fun empty(): T = builder().also { it.descriptor = descriptor 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 4d258e72..4d07ab4d 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,6 +1,7 @@ package space.kscience.dataforge.meta import space.kscience.dataforge.names.NameToken +import space.kscience.dataforge.values.Value /** * The meta implementation which is guaranteed to be immutable. @@ -8,16 +9,25 @@ import space.kscience.dataforge.names.NameToken * If the argument is possibly mutable node, it is copied on creation */ public class SealedMeta internal constructor( - override val items: Map>, -) : AbstractTypedMeta() + override val value: Value?, + override val items: Map +) : TypedMeta { + 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) +} /** * Generate sealed node from [this]. If it is already sealed return it as is */ -public fun Meta.seal(): SealedMeta = this as? SealedMeta ?: SealedMeta(items.mapValues { entry -> entry.value.seal() }) +public fun Meta.seal(): SealedMeta = this as? SealedMeta ?: SealedMeta(value, items.mapValues { entry -> + entry.value.seal() +}) + +@Suppress("FunctionName") +public fun Meta(value: Value): SealedMeta = SealedMeta(value, emptyMap()) + +@Suppress("FunctionName") +public fun Meta(builder: MutableMeta.() -> Unit): SealedMeta = + MutableMeta(builder).seal() -@Suppress("UNCHECKED_CAST") -public fun MetaItem.seal(): TypedMetaItem = when (this) { - is MetaItemValue -> this - is MetaItemNode -> MetaItemNode(node.seal()) -} \ No newline at end of file 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 10562cd5..f207c0cf 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 @@ -6,13 +6,13 @@ import space.kscience.dataforge.names.asName import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty -public interface ReadOnlySpecification { +public interface ReadOnlySpecification { /** * Read generic read-only meta with this [Specification] producing instance of desired type. + * The source is not mutated even if it is in theory mutable */ - public fun read(items: ItemProvider): T - + public fun read(source: Meta): T /** * Generate an empty object @@ -31,48 +31,49 @@ public interface ReadOnlySpecification { * By convention [Scheme] companion should inherit this class * */ -public interface Specification : ReadOnlySpecification { +public interface Specification : ReadOnlySpecification { /** - * Wrap [MutableItemProvider], using it as inner storage (changes to [Specification] are reflected on [MutableItemProvider] + * Wrap [MutableTypedMeta], using it as inner storage (changes to [Specification] are reflected on [MutableTypedMeta] */ - public fun write(target: MutableItemProvider): T + public fun write(target: MutableMeta): T } /** - * Update a [MutableItemProvider] using given specification + * Update a [MutableTypedMeta] using given specification */ -public fun MutableItemProvider.update(spec: Specification, action: T.() -> Unit) { - spec.write(this).apply(action) -} +public fun , T : Any> M.update( + spec: Specification, + action: T.() -> Unit +): T = spec.write(this).apply(action) + /** * Update configuration using given specification */ -public fun > Configurable.update( - spec: S, - action: C.() -> Unit, -) { - config.update(spec, action) -} +public fun Configurable.update( + spec: Specification, + action: T.() -> Unit, +): T = spec.write(config).apply(action) -public fun TypedMetaItem>.withSpec(spec: Specification): T? = - node?.let { spec.write(it) } +// +//public fun > MutableMeta.withSpec(spec: Specification): M? = +// spec.write(it) /** * A delegate that uses a [Specification] to wrap a child of this provider */ -public fun MutableItemProvider.spec( +public fun MutableMeta.spec( spec: Specification, key: Name? = null, ): ReadWriteProperty = object : ReadWriteProperty { override fun getValue(thisRef: Any?, property: KProperty<*>): T { val name = key ?: property.name.asName() - return getChild(name).let { spec.write(it) } + return spec.write(getOrCreate(name)) } override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { val name = key ?: property.name.asName() - set(name, value.toMeta().asMetaItem()) + set(name, value.toMeta()) } } @@ -82,21 +83,13 @@ public fun MutableItemProvider.spec( * The list is a snapshot of children state, so change in structure is not reflected on its composition. */ @DFExperimental -public fun MutableItemProvider.listOfSpec( +public fun MutableMeta.listOfSpec( spec: Specification, key: Name? = null, ): ReadWriteProperty> = object : ReadWriteProperty> { override fun getValue(thisRef: Any?, property: KProperty<*>): List { val name = key ?: property.name.asName() - return getIndexed(name).map { - when (val value = it.value) { - is MetaItemNode<*> -> when (value.node) { - is MutableItemProvider -> spec.write(value.node) - else -> spec.read(value.node) - } - is MetaItemValue -> spec.read(value) - } - } + return getIndexed(name).values.map { spec.write(it as MutableMeta) } } override fun setValue(thisRef: Any?, property: KProperty<*>, value: List) { diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/TypedMeta.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/TypedMeta.kt deleted file mode 100644 index f6f02c70..00000000 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/TypedMeta.kt +++ /dev/null @@ -1,56 +0,0 @@ -package space.kscience.dataforge.meta - -import kotlinx.serialization.json.Json -import space.kscience.dataforge.names.Name -import space.kscience.dataforge.names.NameToken -import space.kscience.dataforge.names.toName - -/** - * A meta node that ensures that all of its descendants has at least the same type - */ -public interface TypedMeta> : Meta { - override val items: Map> - - @Suppress("UNCHECKED_CAST") - override fun getItem(name: Name): TypedMetaItem? = super.getItem(name)?.let { it as TypedMetaItem } - //Typed meta guarantees that all children have M type -} - - -/** - * The same as [Meta.get], but with specific node type - */ -public operator fun > M.get(name: Name): TypedMetaItem? = getItem(name) - - -public operator fun > M.get(key: String): TypedMetaItem? = this[key.toName()] -public operator fun > M.get(key: NameToken): TypedMetaItem? = items[key] - -/** - * Equals, hashcode and to string for any meta - */ -public abstract class MetaBase : Meta { - - override fun equals(other: Any?): Boolean = if (other is Meta) { - Meta.equals(this, other) - } else { - false - } - - override fun hashCode(): Int = items.hashCode() - - override fun toString(): String = json.encodeToString(MetaSerializer, this) - - public companion object{ - private val json = Json { - prettyPrint = true - useArrayPolymorphism = true - } - - } -} - -/** - * Equals and hash code implementation for meta node - */ -public abstract class AbstractTypedMeta> : TypedMeta, MetaBase() 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 73c4ad7c..b9f72f75 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 @@ -4,7 +4,7 @@ package space.kscience.dataforge.meta.descriptors * An object which provides its descriptor */ public interface Described { - public val descriptor: ItemDescriptor? + public val descriptor: MetaDescriptor? public companion object { //public const val DESCRIPTOR_NODE: String = "@descriptor" diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/ItemDescriptor.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/ItemDescriptor.kt index 29e6e2dd..03b4fdde 100644 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/ItemDescriptor.kt +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/ItemDescriptor.kt @@ -1,126 +1,127 @@ -package space.kscience.dataforge.meta.descriptors - -import space.kscience.dataforge.meta.* -import space.kscience.dataforge.misc.DFBuilder -import space.kscience.dataforge.names.* - -/** - * A common parent for [ValueDescriptor] and [NodeDescriptor]. Describes a single [TypedMetaItem] or a group of same-name-siblings. - */ -public sealed interface ItemDescriptor: MetaRepr { - - /** - * True if same name siblings with this name are allowed - */ - public val multiple: Boolean - - /** - * The item description text - */ - public val info: String? - - /** - * True if the item is required - */ - public val required: Boolean - - - /** - * Additional attributes of an item. For example validation and widget parameters - * - * @return - */ - public val attributes: Meta? - - /** - * An index field by which this node is identified in case of same name siblings construct - */ - public val indexKey: String - - /** - * Compute and cache the default [MetaItem] value described by this descriptor - */ - public val defaultValue: MetaItem? - - public companion object { - public const val DEFAULT_INDEX_KEY: String = "@index" - } -} - - -/** - * The builder for [ItemDescriptor] - */ -@DFBuilder -public sealed class ItemDescriptorBuilder(final override val config: ObservableMeta) : Configurable, ItemDescriptor { - - /** - * True if same name siblings with this name are allowed - */ - override var multiple: Boolean by config.boolean(false) - - /** - * The item description text - */ - override var info: String? by config.string() - - /** - * True if the item is required - */ - abstract override var required: Boolean - - - /** - * Additional attributes of an item. For example validation and widget parameters - * - * @return - */ - override var attributes: ObservableMeta? by config.node() - - /** - * An index field by which this node is identified in case of same name siblings construct - */ - override var indexKey: String by config.string(DEFAULT_INDEX_KEY) - - public abstract fun build(): ItemDescriptor - - override fun toMeta(): Meta = config - - public companion object { - public const val DEFAULT_INDEX_KEY: String = "@index" - } -} - -/** - * Configure attributes of the descriptor, creating an attributes node if needed. - */ -public inline fun ItemDescriptorBuilder.attributes(block: ObservableMeta.() -> Unit) { - (attributes ?: ObservableMeta().also { this.attributes = it }).apply(block) -} - -/** - * Check if given item suits the descriptor - */ -public fun ItemDescriptor.validateItem(item: MetaItem?): Boolean { - if (item == null) return !required - return when (this) { - is ValueDescriptor -> isAllowedValue(item.value ?: return false) - is NodeDescriptor -> items.all { (key, d) -> - d.validateItem(item.node[key]) - } - } -} - -/** - * Get a descriptor item associated with given name or null if item for given name not provided - */ -public operator fun ItemDescriptor.get(name: Name): ItemDescriptor? { - if (name.isEmpty()) return this - return when (this) { - is ValueDescriptor -> null // empty name already checked - is NodeDescriptor -> items[name.firstOrNull()!!.toString()]?.get(name.cutFirst()) - } -} - -public operator fun ItemDescriptor.get(name: String): ItemDescriptor? = get(name.toName()) - +//package space.kscience.dataforge.meta.descriptors +// +//import space.kscience.dataforge.meta.* +//import space.kscience.dataforge.misc.DFBuilder +//import space.kscience.dataforge.names.* +//import space.kscience.dataforge.values.Value +// +///** +// * A common parent for [ValueDescriptor] and [NodeDescriptor]. Describes a single [TypedMetaItem] or a group of same-name-siblings. +// */ +//public sealed interface ItemDescriptor: MetaRepr { +// +// /** +// * True if same name siblings with this name are allowed +// */ +// public val multiple: Boolean +// +// /** +// * The item description text +// */ +// public val info: String? +// +// /** +// * True if the item is required +// */ +// public val required: Boolean +// +// +// /** +// * Additional attributes of an item. For example validation and widget parameters +// * +// * @return +// */ +// public val attributes: Meta? +// +// /** +// * An index field by which this node is identified in case of same name siblings construct +// */ +// public val indexKey: String +// +// /** +// * Compute and cache the default [Meta] value described by this descriptor +// */ +// public val defaultValue: Meta? +// +// public companion object { +// public const val DEFAULT_INDEX_KEY: String = "@index" +// } +//} +// +// +///** +// * The builder for [ItemDescriptor] +// */ +//@DFBuilder +//public sealed class ItemDescriptorBuilder(final override val config: MutableMeta) : Configurable, ItemDescriptor { +// +// /** +// * True if same name siblings with this name are allowed +// */ +// override var multiple: Boolean by config.boolean(false) +// +// /** +// * The item description text +// */ +// override var info: String? by config.string() +// +// /** +// * True if the item is required +// */ +// abstract override var required: Boolean +// +// +// /** +// * Additional attributes of an item. For example validation and widget parameters +// * +// * @return +// */ +// override var attributes: MutableMeta? by config.node() +// +// /** +// * An index field by which this node is identified in case of same name siblings construct +// */ +// override var indexKey: String by config.string(DEFAULT_INDEX_KEY) +// +// public abstract fun build(): ItemDescriptor +// +// override fun toMeta(): Meta = config +// +// public companion object { +// public const val DEFAULT_INDEX_KEY: String = "@index" +// } +//} +// +///** +// * Configure attributes of the descriptor, creating an attributes node if needed. +// */ +//public inline fun ItemDescriptorBuilder.attributes(block: MutableMeta.() -> Unit) { +// (attributes ?: MutableMeta().also { this.attributes = it }).apply(block) +//} +// +///** +// * Check if given item suits the descriptor +// */ +//public fun ItemDescriptor.validateItem(item: MetaItem?): Boolean { +// if (item == null) return !required +// return when (this) { +// is ValueDescriptor -> isAllowedValue(item.value ?: return false) +// is NodeDescriptor -> items.all { (key, d) -> +// d.validateItem(item.node[key]) +// } +// } +//} +// +///** +// * Get a descriptor item associated with given name or null if item for given name not provided +// */ +//public operator fun ItemDescriptor.get(name: Name): ItemDescriptor? { +// if (name.isEmpty()) return this +// return when (this) { +// is ValueDescriptor -> null // empty name already checked +// is NodeDescriptor -> items[name.firstOrNull()!!.toString()]?.get(name.cutFirst()) +// } +//} +// +//public operator fun ItemDescriptor.get(name: String): ItemDescriptor? = get(name.toName()) +// 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 new file mode 100644 index 00000000..a3f523e5 --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/MetaDescriptor.kt @@ -0,0 +1,60 @@ +package space.kscience.dataforge.meta.descriptors + +import kotlinx.serialization.Serializable +import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.names.* +import space.kscience.dataforge.values.Value +import space.kscience.dataforge.values.ValueType + +/** + * The descriptor for a meta + * @param info description text + * @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 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 + */ +@Serializable +public data class MetaDescriptor( + public val info: String? = null, + public val children: Map = emptyMap(), + public val multiple: Boolean = false, + public val required: Boolean = false, + public val type: List? = null, + public val indexKey: String = Meta.INDEX_KEY, + public val defaultValue: Value? = null, + public val attributes: Meta = Meta.EMPTY, +) + +public operator fun MetaDescriptor.get(name: Name): MetaDescriptor? = when (name.length) { + 0 -> this + 1 -> children[name.firstOrNull()!!.toString()] + else -> get(name.firstOrNull()!!.asName())?.get(name.cutFirst()) +} + +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 + + 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 diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/NodeDescriptor.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/NodeDescriptor.kt index 71cb1cf9..88a9cc9e 100644 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/NodeDescriptor.kt +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/NodeDescriptor.kt @@ -1,222 +1,222 @@ -package space.kscience.dataforge.meta.descriptors - -import space.kscience.dataforge.meta.* -import space.kscience.dataforge.misc.DFBuilder -import space.kscience.dataforge.names.* - - -/** - * A [Meta] that is constructed from [NodeDescriptor] - */ -private class DescriptorMeta(val descriptor: NodeDescriptor) : Meta, MetaBase() { - override val items: Map - get() = buildMap { - descriptor.items.forEach { (token, descriptorItem) -> - val item = descriptorItem.defaultValue - if (item != null) { - put(NameToken(token), item) - } - } - } -} - - -/** - * Descriptor for meta node. Could contain additional information for viewing - * and editing. - * - * @author Alexander Nozik - */ -@DFBuilder -public sealed interface NodeDescriptor : ItemDescriptor { - /** - * True if the node is required - * - * @return - */ - override val required: Boolean - - /** - * The default for this node. Null if there is no default. - * - * @return - */ - public val default: Meta? - - /** - * The map of children item descriptors (both nodes and values) - */ - public val items: Map - - /** - * The map of children node descriptors - */ - public val nodes: Map - - /** - * The list of children value descriptors - */ - public val values: Map - - /** - * Generate a laminate representing default item set generated by this descriptor - */ - public val defaultMeta: Laminate - - public companion object { - - internal val ITEM_KEY: Name = "item".asName() - internal val IS_NODE_KEY: Name = "@isNode".asName() - - //TODO infer descriptor from spec - } -} - - -@DFBuilder -public class NodeDescriptorBuilder(config: ObservableMeta = ObservableMeta()) : ItemDescriptorBuilder(config), NodeDescriptor { - init { - config[IS_NODE_KEY] = true - } - - /** - * True if the node is required - * - * @return - */ - override var required: Boolean by config.boolean { default == null } - - /** - * The default for this node. Null if there is no default. - * - * @return - */ - override var default: ObservableMeta? by config.node() - - /** - * The map of children item descriptors (both nodes and values) - */ - override val items: Map - get() = config.getIndexed(ITEM_KEY).entries.associate { (name, item) -> - if (name == null) error("Child item index should not be null") - val node = item.node ?: error("Node descriptor must be a node") - if (node[IS_NODE_KEY].boolean == true) { - name to NodeDescriptorBuilder(node as ObservableMeta) - } else { - name to ValueDescriptorBuilder(node as ObservableMeta) - } - } - - /** - * The map of children node descriptors - */ - @Suppress("UNCHECKED_CAST") - override val nodes: Map - get() = config.getIndexed(ITEM_KEY).entries.filter { - it.value.node[IS_NODE_KEY].boolean == true - }.associate { (name, item) -> - if (name == null) error("Child node index should not be null") - val node = item.node ?: error("Node descriptor must be a node") - name to NodeDescriptorBuilder(node as ObservableMeta) - } - - /** - * The list of children value descriptors - */ - override val values: Map - get() = config.getIndexed(ITEM_KEY).entries.filter { - it.value.node[IS_NODE_KEY].boolean != true - }.associate { (name, item) -> - if (name == null) error("Child value index should not be null") - val node = item.node ?: error("Node descriptor must be a node") - name to ValueDescriptorBuilder(node as ObservableMeta) - } - - private fun buildNode(name: Name): NodeDescriptorBuilder { - return when (name.length) { - 0 -> this - 1 -> { - val token = NameToken(ITEM_KEY.toString(), name.toString()) - val config: ObservableMeta = config[token].node ?: ObservableMeta().also { - it[IS_NODE_KEY] = true - config[token] = it - } - NodeDescriptorBuilder(config) - } - else -> buildNode(name.firstOrNull()?.asName()!!).buildNode(name.cutFirst()) - } - } - - /** - * Define a child item descriptor for this node - */ - private fun newItem(key: String, descriptor: ItemDescriptor) { - if (items.keys.contains(key)) error("The key $key already exists in descriptor") - val token = ITEM_KEY.withIndex(key) - config[token] = descriptor.toMeta() - } - - public fun item(name: Name, descriptor: ItemDescriptor) { - buildNode(name.cutLast()).newItem(name.lastOrNull().toString(), descriptor) - } - - public fun item(name: String, descriptor: ItemDescriptor) { - item(name.toName(), descriptor) - } - - /** - * Create and configure a child node descriptor - */ - public fun node(name: Name, block: NodeDescriptorBuilder.() -> Unit) { - item(name, NodeDescriptorBuilder().apply(block)) - } - - public fun node(name: String, block: NodeDescriptorBuilder.() -> Unit) { - node(name.toName(), block) - } - - /** - * Create and configure child value descriptor - */ - public fun value(name: Name, block: ValueDescriptorBuilder.() -> Unit) { - require(name.length >= 1) { "Name length for value descriptor must be non-empty" } - item(name, ValueDescriptorBuilder().apply(block)) - } - - public fun value(name: String, block: ValueDescriptorBuilder.() -> Unit) { - value(name.toName(), block) - } - - /** - * Generate a laminate representing default item set generated by this descriptor - */ - override val defaultMeta: Laminate by lazy { Laminate(default, DescriptorMeta(this)) } - - /** - * Build a default [MetaItemNode] from this node descriptor - */ - override val defaultValue: MetaItem get() = MetaItemNode(defaultMeta) - - override fun build(): NodeDescriptor = NodeDescriptorBuilder(config.copy()) - - public companion object { - - internal val ITEM_KEY: Name = "item".asName() - internal val IS_NODE_KEY: Name = "@isNode".asName() - - //TODO infer descriptor from spec - } -} - -public inline fun NodeDescriptor(block: NodeDescriptorBuilder.() -> Unit): NodeDescriptor = - NodeDescriptorBuilder().apply(block) - -/** - * Merge two node descriptors into one using first one as primary - */ -public operator fun NodeDescriptor.plus(other: NodeDescriptor): NodeDescriptor { - return NodeDescriptorBuilder().apply { - config.update(other.toMeta()) - config.update(this@plus.toMeta()) - } -} \ No newline at end of file +//package space.kscience.dataforge.meta.descriptors +// +//import space.kscience.dataforge.meta.* +//import space.kscience.dataforge.misc.DFBuilder +//import space.kscience.dataforge.names.* +// +// +///** +// * A [Meta] that is constructed from [NodeDescriptor] +// */ +//private class DescriptorMeta(val descriptor: NodeDescriptor) : AbstractMeta() { +// override val items: Map +// get() = buildMap { +// descriptor.items.forEach { (token, descriptorItem) -> +// val item = descriptorItem.defaultValue +// if (item != null) { +// put(NameToken(token), item) +// } +// } +// } +//} +// +// +///** +// * Descriptor for meta node. Could contain additional information for viewing +// * and editing. +// * +// * @author Alexander Nozik +// */ +//@DFBuilder +//public sealed interface NodeDescriptor : ItemDescriptor { +// /** +// * True if the node is required +// * +// * @return +// */ +// override val required: Boolean +// +// /** +// * The default for this node. Null if there is no default. +// * +// * @return +// */ +// public val default: Meta? +// +// /** +// * The map of children item descriptors (both nodes and values) +// */ +// public val items: Map +// +// /** +// * The map of children node descriptors +// */ +// public val nodes: Map +// +// /** +// * The list of children value descriptors +// */ +// public val values: Map +// +// /** +// * Generate a laminate representing default item set generated by this descriptor +// */ +// public val defaultMeta: Laminate +// +// public companion object { +// +// internal val ITEM_KEY: Name = "item".asName() +// internal val IS_NODE_KEY: Name = "@isNode".asName() +// +// //TODO infer descriptor from spec +// } +//} +// +// +//@DFBuilder +//public class NodeDescriptorBuilder(config: MutableMeta = MutableMeta()) : ItemDescriptorBuilder(config), NodeDescriptor { +// init { +// config[IS_NODE_KEY] = true +// } +// +// /** +// * True if the node is required +// * +// * @return +// */ +// override var required: Boolean by config.boolean { default == null } +// +// /** +// * The default for this node. Null if there is no default. +// * +// * @return +// */ +// override var default: MutableMeta? by config.node() +// +// /** +// * The map of children item descriptors (both nodes and values) +// */ +// override val items: Map +// get() = config.getIndexed(ITEM_KEY).entries.associate { (name, item) -> +// if (name == null) error("Child item index should not be null") +// val node = item.node ?: error("Node descriptor must be a node") +// if (node[IS_NODE_KEY].boolean == true) { +// name to NodeDescriptorBuilder(node as MutableMeta) +// } else { +// name to ValueDescriptorBuilder(node as MutableMeta) +// } +// } +// +// /** +// * The map of children node descriptors +// */ +// @Suppress("UNCHECKED_CAST") +// override val nodes: Map +// get() = config.getIndexed(ITEM_KEY).entries.filter { +// it.value.node[IS_NODE_KEY].boolean == true +// }.associate { (name, item) -> +// if (name == null) error("Child node index should not be null") +// val node = item.node ?: error("Node descriptor must be a node") +// name to NodeDescriptorBuilder(node as MutableMeta) +// } +// +// /** +// * The list of children value descriptors +// */ +// override val values: Map +// get() = config.getIndexed(ITEM_KEY).entries.filter { +// it.value.node[IS_NODE_KEY].boolean != true +// }.associate { (name, item) -> +// if (name == null) error("Child value index should not be null") +// val node = item.node ?: error("Node descriptor must be a node") +// name to ValueDescriptorBuilder(node as MutableMeta) +// } +// +// private fun buildNode(name: Name): NodeDescriptorBuilder { +// return when (name.length) { +// 0 -> this +// 1 -> { +// val token = NameToken(ITEM_KEY.toString(), name.toString()) +// val config: MutableMeta = config[token].node ?: MutableMeta().also { +// it[IS_NODE_KEY] = true +// config[token] = it +// } +// NodeDescriptorBuilder(config) +// } +// else -> buildNode(name.firstOrNull()?.asName()!!).buildNode(name.cutFirst()) +// } +// } +// +// /** +// * Define a child item descriptor for this node +// */ +// private fun newItem(key: String, descriptor: ItemDescriptor) { +// if (items.keys.contains(key)) error("The key $key already exists in descriptor") +// val token = ITEM_KEY.withIndex(key) +// config[token] = descriptor.toMeta() +// } +// +// public fun item(name: Name, descriptor: ItemDescriptor) { +// buildNode(name.cutLast()).newItem(name.lastOrNull().toString(), descriptor) +// } +// +// public fun item(name: String, descriptor: ItemDescriptor) { +// item(name.toName(), descriptor) +// } +// +// /** +// * Create and configure a child node descriptor +// */ +// public fun node(name: Name, block: NodeDescriptorBuilder.() -> Unit) { +// item(name, NodeDescriptorBuilder().apply(block)) +// } +// +// public fun node(name: String, block: NodeDescriptorBuilder.() -> Unit) { +// node(name.toName(), block) +// } +// +// /** +// * Create and configure child value descriptor +// */ +// public fun value(name: Name, block: ValueDescriptorBuilder.() -> Unit) { +// require(name.length >= 1) { "Name length for value descriptor must be non-empty" } +// item(name, ValueDescriptorBuilder().apply(block)) +// } +// +// public fun value(name: String, block: ValueDescriptorBuilder.() -> Unit) { +// value(name.toName(), block) +// } +// +// /** +// * Generate a laminate representing default item set generated by this descriptor +// */ +// override val defaultMeta: Laminate by lazy { Laminate(default, DescriptorMeta(this)) } +// +// /** +// * Build a default [MetaItemNode] from this node descriptor +// */ +// override val defaultValue: MetaItem get() = MetaItemNode(defaultMeta) +// +// override fun build(): NodeDescriptor = NodeDescriptorBuilder(config.copy()) +// +// public companion object { +// +// internal val ITEM_KEY: Name = "item".asName() +// internal val IS_NODE_KEY: Name = "@isNode".asName() +// +// //TODO infer descriptor from spec +// } +//} +// +//public inline fun NodeDescriptor(block: NodeDescriptorBuilder.() -> Unit): NodeDescriptor = +// NodeDescriptorBuilder().apply(block) +// +///** +// * Merge two node descriptors into one using first one as primary +// */ +//public operator fun NodeDescriptor.plus(other: NodeDescriptor): NodeDescriptor { +// return NodeDescriptorBuilder().apply { +// config.update(other.toMeta()) +// config.update(this@plus.toMeta()) +// } +//} \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/ValueDescriptor.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/ValueDescriptor.kt index a58248bd..abcd5ebd 100644 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/ValueDescriptor.kt +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/descriptors/ValueDescriptor.kt @@ -1,139 +1,139 @@ -package space.kscience.dataforge.meta.descriptors - -import space.kscience.dataforge.meta.* -import space.kscience.dataforge.misc.DFBuilder -import space.kscience.dataforge.values.* - - -/** - * A descriptor for meta value - * - * Descriptor can have non-atomic path. It is resolved when descriptor is added to the node - * - * @author Alexander Nozik - */ -@DFBuilder -public sealed interface ValueDescriptor : ItemDescriptor { - - /** - * True if the value is required - * - * @return - */ - override val required: Boolean - - /** - * The default for this value. Null if there is no default. - * - * @return - */ - public val default: Value? - - - /** - * A list of allowed ValueTypes. Empty if any value type allowed - * - * @return - */ - public val type: List? - - /** - * Check if given value is allowed for here. The type should be allowed and - * if it is value should be within allowed values - * - * @param value - * @return - */ - public fun isAllowedValue(value: Value): Boolean = - (type?.let { it.contains(ValueType.STRING) || it.contains(value.type) } ?: true) - && (allowedValues.isEmpty() || allowedValues.contains(value)) - - /** - * A list of allowed values with descriptions. If empty than any value is - * allowed. - * - * @return - */ - public val allowedValues: List -} - -/** - * A builder fir [ValueDescriptor] - */ -@DFBuilder -public class ValueDescriptorBuilder( - config: ObservableMeta = ObservableMeta() -) : ItemDescriptorBuilder(config), ValueDescriptor { - - /** - * True if the value is required - * - * @return - */ - override var required: Boolean by config.boolean { default == null } - - /** - * The default for this value. Null if there is no default. - * - * @return - */ - override var default: Value? by config.value() - - public fun default(v: Any) { - this.default = Value.of(v) - } - - /** - * A list of allowed ValueTypes. Empty if any value type allowed - * - * @return - */ - override var type: List? by config.listValue { ValueType.valueOf(it.string) } - - public fun type(vararg t: ValueType) { - this.type = listOf(*t) - } - - /** - * Check if given value is allowed for here. The type should be allowed and - * if it is value should be within allowed values - * - * @param value - * @return - */ - override fun isAllowedValue(value: Value): Boolean { - return (type?.let { it.contains(ValueType.STRING) || it.contains(value.type) } ?: true) - && (allowedValues.isEmpty() || allowedValues.contains(value)) - } - - /** - * A list of allowed values with descriptions. If empty than any value is - * allowed. - * - * @return - */ - override var allowedValues: List by config.item().convert( - reader = { - val value = it.value - when { - value?.list != null -> value.list - type?.let { type -> type.size == 1 && type[0] === ValueType.BOOLEAN } ?: false -> listOf(True, False) - else -> emptyList() - } - }, - writer = { - MetaItemValue(it.asValue()) - } - ) - - /** - * Allow given list of value and forbid others - */ - public fun allow(vararg v: Any) { - this.allowedValues = v.map { Value.of(it) } - } - - override val defaultValue: MetaItem? get() = default?.asMetaItem() - - override fun build(): ValueDescriptor = ValueDescriptorBuilder(config.copy()) -} +//package space.kscience.dataforge.meta.descriptors +// +//import space.kscience.dataforge.meta.* +//import space.kscience.dataforge.misc.DFBuilder +//import space.kscience.dataforge.values.* +// +// +///** +// * A descriptor for meta value +// * +// * Descriptor can have non-atomic path. It is resolved when descriptor is added to the node +// * +// * @author Alexander Nozik +// */ +//@DFBuilder +//public sealed interface ValueDescriptor : ItemDescriptor { +// +// /** +// * True if the value is required +// * +// * @return +// */ +// override val required: Boolean +// +// /** +// * The default for this value. Null if there is no default. +// * +// * @return +// */ +// public val default: Value? +// +// +// /** +// * A list of allowed ValueTypes. Empty if any value type allowed +// * +// * @return +// */ +// public val type: List? +// +// /** +// * Check if given value is allowed for here. The type should be allowed and +// * if it is value should be within allowed values +// * +// * @param value +// * @return +// */ +// public fun isAllowedValue(value: Value): Boolean = +// (type?.let { it.contains(ValueType.STRING) || it.contains(value.type) } ?: true) +// && (allowedValues.isEmpty() || allowedValues.contains(value)) +// +// /** +// * A list of allowed values with descriptions. If empty than any value is +// * allowed. +// * +// * @return +// */ +// public val allowedValues: List +//} +// +///** +// * A builder fir [ValueDescriptor] +// */ +//@DFBuilder +//public class ValueDescriptorBuilder( +// config: MutableMeta = MutableMeta() +//) : ItemDescriptorBuilder(config), ValueDescriptor { +// +// /** +// * True if the value is required +// * +// * @return +// */ +// override var required: Boolean by config.boolean { default == null } +// +// /** +// * The default for this value. Null if there is no default. +// * +// * @return +// */ +// override var default: Value? by config.value() +// +// public fun default(v: Any) { +// this.default = Value.of(v) +// } +// +// /** +// * A list of allowed ValueTypes. Empty if any value type allowed +// * +// * @return +// */ +// override var type: List? by config.listValue { ValueType.valueOf(it.string) } +// +// public fun type(vararg t: ValueType) { +// this.type = listOf(*t) +// } +// +// /** +// * Check if given value is allowed for here. The type should be allowed and +// * if it is value should be within allowed values +// * +// * @param value +// * @return +// */ +// override fun isAllowedValue(value: Value): Boolean { +// return (type?.let { it.contains(ValueType.STRING) || it.contains(value.type) } ?: true) +// && (allowedValues.isEmpty() || allowedValues.contains(value)) +// } +// +// /** +// * A list of allowed values with descriptions. If empty than any value is +// * allowed. +// * +// * @return +// */ +// override var allowedValues: List by config.item().convert( +// reader = { +// val value = it.value +// when { +// value?.list != null -> value.list +// type?.let { type -> type.size == 1 && type[0] === ValueType.BOOLEAN } ?: false -> listOf(True, False) +// else -> emptyList() +// } +// }, +// writer = { +// MetaItemValue(it.asValue()) +// } +// ) +// +// /** +// * Allow given list of value and forbid others +// */ +// public fun allow(vararg v: Any) { +// this.allowedValues = v.map { Value.of(it) } +// } +// +// override val defaultValue: MetaItem? get() = default?.asMetaItem() +// +// override fun build(): ValueDescriptor = ValueDescriptorBuilder(config.copy()) +//} 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 index 8149843e..e8846e7b 100644 --- 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 @@ -4,10 +4,10 @@ import space.kscience.dataforge.names.Name import space.kscience.dataforge.values.ValueType import space.kscience.dataforge.values.asValue -public inline fun > NodeDescriptorBuilder.enum( +public inline fun > MetaDescriptorBuilder.enum( key: Name, default: E?, - crossinline modifier: ValueDescriptor.() -> Unit = {}, + crossinline modifier: MetaDescriptor.() -> Unit = {}, ): Unit = value(key) { type(ValueType.STRING) default?.let { diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/mapMeta.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/mapMeta.kt index c6cd2a4e..558e7fb3 100644 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/mapMeta.kt +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/mapMeta.kt @@ -1,6 +1,6 @@ package space.kscience.dataforge.meta -import space.kscience.dataforge.meta.descriptors.NodeDescriptor +import space.kscience.dataforge.meta.descriptors.MetaDescriptor import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.names.toName import space.kscience.dataforge.values.ListValue @@ -9,7 +9,7 @@ import space.kscience.dataforge.values.Value /** * Convert meta to map of maps */ -public fun Meta.toMap(descriptor: NodeDescriptor? = null): Map { +public fun Meta.toMap(descriptor: MetaDescriptor? = null): Map { return items.entries.associate { (token, item) -> token.toString() to when (item) { is MetaItemNode -> item.node.toMap() @@ -23,7 +23,7 @@ public fun Meta.toMap(descriptor: NodeDescriptor? = null): Map { * All other values will be converted to values. */ @DFExperimental -public fun Map.toMeta(descriptor: NodeDescriptor? = null): Meta = Meta { +public fun Map.toMeta(descriptor: MetaDescriptor? = null): Meta = Meta { @Suppress("UNCHECKED_CAST") fun toItem(value: Any?): MetaItem = when (value) { is MetaItem -> 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 8bce8aec..e80b64bb 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 @@ -21,12 +21,12 @@ public interface TransformationRule { * @return a sequence of item paths to be transformed */ public fun selectItems(meta: Meta): Sequence = - meta.itemSequence().filter { matches(it.first, it.second) }.map { it.first } + meta.nodeSequence().filter { matches(it.first, it.second) }.map { it.first } /** * Apply transformation for a single item (Node or Value) to the target */ - public fun > transformItem(name: Name, item: MetaItem?, target: M): Unit + public fun transformItem(name: Name, item: MetaItem?, target: MutableTypedMeta): Unit } /** @@ -39,9 +39,9 @@ public data class KeepTransformationRule(val selector: (Name) -> Boolean) : } override fun selectItems(meta: Meta): Sequence = - meta.itemSequence().map { it.first }.filter(selector) + meta.nodeSequence().map { it.first }.filter(selector) - override fun > transformItem(name: Name, item: MetaItem?, target: M) { + override fun transformItem(name: Name, item: MetaItem?, target: MutableTypedMeta) { if (selector(name)) target.set(name, item) } } @@ -51,7 +51,7 @@ public data class KeepTransformationRule(val selector: (Name) -> Boolean) : */ public data class SingleItemTransformationRule( val from: Name, - val transform: MutableMeta<*>.(Name, MetaItem?) -> Unit, + val transform: MutableTypedMeta.(Name, MetaItem?) -> Unit, ) : TransformationRule { override fun matches(name: Name, item: MetaItem?): Boolean { return name == from @@ -59,7 +59,7 @@ public data class SingleItemTransformationRule( override fun selectItems(meta: Meta): Sequence = sequenceOf(from) - override fun > transformItem(name: Name, item: MetaItem?, target: M) { + override fun transformItem(name: Name, item: MetaItem?, target: MutableTypedMeta) { 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: MutableMeta<*>.(name: Name, MatchResult, MetaItem?) -> Unit, + val transform: MutableTypedMeta.(name: Name, MatchResult, MetaItem?) -> Unit, ) : TransformationRule { override fun matches(name: Name, item: MetaItem?): Boolean { return from.matches(name.toString()) } - override fun > transformItem(name: Name, item: MetaItem?, target: M) { + override fun transformItem(name: Name, item: MetaItem?, target: MutableTypedMeta) { 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,7 +131,7 @@ public value class MetaTransformation(private val transformations: Collection> bind(source: ObservableMeta, target: M) { + public fun bind(source: ObservableMeta, target: MutableTypedMeta) { source.onChange(target) { name, _, newItem -> transformations.forEach { t -> if (t.matches(name, newItem)) { 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 785730d9..e1adfb45 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,7 @@ package space.kscience.dataforge.values import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.meta.MetaBuilder +import space.kscience.dataforge.meta.MutableMeta /** * Check if value is null @@ -35,4 +35,4 @@ public val Value.doubleArray: DoubleArray } -public fun Value.toMeta(): MetaBuilder = Meta { Meta.VALUE_KEY put this } \ No newline at end of file +public fun Value.toMeta(): MutableMeta = Meta { Meta.VALUE_KEY put this } \ No newline at end of file diff --git a/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/ConfigTest.kt b/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/ConfigTest.kt index 3ec27c97..a2117abf 100644 --- a/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/ConfigTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/ConfigTest.kt @@ -6,7 +6,7 @@ import kotlin.test.assertEquals class ConfigTest { @Test fun testIndexedWrite(){ - val config = MetaBuilder() + val config = MutableMeta() config["a[1].b"] = 1 assertEquals(null, config["a.b"].int) assertEquals(1, config["a[1].b"].int) 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 51503f29..7e4caa1c 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 @@ -14,7 +14,7 @@ class MutableMetaTest{ "b" put 22 "c" put "StringValue" } - }.asObservable() + } meta.remove("aNode.c") assertEquals(meta["aNode.c"], null) diff --git a/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/SchemeTest.kt b/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/SchemeTest.kt index 308f6eee..87948ec9 100644 --- a/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/SchemeTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/space/kscience/dataforge/meta/SchemeTest.kt @@ -8,7 +8,7 @@ import kotlin.test.assertEquals class SchemeTest { @Test fun testSchemeWrappingBeforeEdit() { - val config = MetaBuilder() + val config = MutableMeta() val scheme = TestScheme.write(config) scheme.a = 29 assertEquals(29, config["a"].int) @@ -18,7 +18,7 @@ class SchemeTest { fun testSchemeWrappingAfterEdit() { val scheme = TestScheme.empty() scheme.a = 29 - val config = MetaBuilder() + val config = MutableMeta() scheme.retarget(config) assertEquals(29, scheme.a) } 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 9a57298f..4fade703 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 @@ -49,7 +49,7 @@ class SpecificationTest { @Test fun testChildModification() { - val config = MetaBuilder() + val config = MutableMeta() val child = config.getChild("child") val scheme = TestScheme.write(child) scheme.a = 22 @@ -60,7 +60,7 @@ class SpecificationTest { @Test fun testChildUpdate() { - val config = MetaBuilder() + val config = MutableMeta() val child = config.getChild("child") child.update(TestScheme) { a = 22 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 6568d9cb..1bf587cd 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 @@ -38,7 +38,7 @@ public fun Meta.toDynamic(): dynamic { return res } -public class DynamicMeta(internal val obj: dynamic) : MetaBase() { +public class DynamicMeta(internal val obj: dynamic) : AbstractTypedMeta() { private fun keys(): Array = js("Object").keys(obj) private fun isArray(@Suppress("UNUSED_PARAMETER") obj: dynamic): Boolean = diff --git a/dataforge-workspace/src/commonMain/kotlin/space/kscience/dataforge/workspace/Workspace.kt b/dataforge-workspace/src/commonMain/kotlin/space/kscience/dataforge/workspace/Workspace.kt index 16af8969..8c70c29c 100644 --- a/dataforge-workspace/src/commonMain/kotlin/space/kscience/dataforge/workspace/Workspace.kt +++ b/dataforge-workspace/src/commonMain/kotlin/space/kscience/dataforge/workspace/Workspace.kt @@ -2,7 +2,7 @@ package space.kscience.dataforge.workspace import space.kscience.dataforge.context.ContextAware import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.meta.MetaBuilder +import space.kscience.dataforge.meta.MutableMeta import space.kscience.dataforge.misc.Type import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.toName @@ -55,5 +55,5 @@ public suspend fun Workspace.produce(task: String, target: String): TaskResult<* public suspend fun Workspace.produce(task: String, meta: Meta): TaskResult<*> = produce(task.toName(), meta) -public suspend fun Workspace.produce(task: String, block: MetaBuilder.() -> Unit = {}): TaskResult<*> = +public suspend fun Workspace.produce(task: String, block: MutableMeta.() -> Unit = {}): TaskResult<*> = produce(task, Meta(block)) 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 c17c5991..1fa4ba5e 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 @@ -8,7 +8,7 @@ import space.kscience.dataforge.data.DataSet import space.kscience.dataforge.data.DataSetBuilder import space.kscience.dataforge.data.DataTree import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.meta.MetaBuilder +import space.kscience.dataforge.meta.MutableMeta import space.kscience.dataforge.meta.descriptors.NodeDescriptor import space.kscience.dataforge.misc.DFBuilder import space.kscience.dataforge.misc.DFExperimental @@ -87,8 +87,8 @@ public class WorkspaceBuilder(private val parentContext: Context = Global) : Tas /** * Define a new target with a builder */ -public inline fun WorkspaceBuilder.target(name: String, metaBuilder: MetaBuilder.() -> Unit): Unit = - target(name, Meta(metaBuilder)) +public inline fun WorkspaceBuilder.target(name: String, mutableMeta: MutableMeta.() -> Unit): Unit = + target(name, Meta(mutableMeta)) @DFBuilder public fun Workspace(parentContext: Context = Global, builder: WorkspaceBuilder.() -> Unit): Workspace = diff --git a/dataforge-workspace/src/jvmTest/kotlin/space/kscience/dataforge/workspace/SimpleWorkspaceTest.kt b/dataforge-workspace/src/jvmTest/kotlin/space/kscience/dataforge/workspace/SimpleWorkspaceTest.kt index 16ae6da6..e5657444 100644 --- a/dataforge-workspace/src/jvmTest/kotlin/space/kscience/dataforge/workspace/SimpleWorkspaceTest.kt +++ b/dataforge-workspace/src/jvmTest/kotlin/space/kscience/dataforge/workspace/SimpleWorkspaceTest.kt @@ -28,7 +28,7 @@ public inline fun P.toFactory(): PluginFactory

= object override val type: KClass = P::class } -public fun Workspace.runBlocking(task: String, block: MetaBuilder.() -> Unit = {}): DataSet = runBlocking { +public fun Workspace.runBlocking(task: String, block: MutableMeta.() -> Unit = {}): DataSet = runBlocking { produce(task, block) } diff --git a/settings.gradle.kts b/settings.gradle.kts index 2ba9a36a..6d8e56a0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,7 +5,7 @@ pluginManagement { gradlePluginPortal() } - val toolsVersion = "0.10.0" + val toolsVersion = "0.10.2" plugins { id("ru.mipt.npm.gradle.project") version toolsVersion