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 217efa95..30f6d542 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 @@ -8,26 +8,24 @@ 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) : TypedMeta { +public class Laminate internal constructor(public val layers: List) : TypedMeta { override val value: Value? = layers.firstNotNullOfOrNull { it.value } - public val layers: List = layers.flatMap { - if (it is Laminate) { - it.layers - } else { - listOf(it) + override val items: Map by lazy { + layers.map { it.items.keys }.flatten().associateWith { key -> + Laminate(layers.mapNotNull { it.items[key] }) } } - override val items: Map by lazy { - layers.map { it.items.keys }.flatten().associateWith { key -> - layers.asSequence().map { it.items[key] }.filterNotNull().let(replaceRule) - } + override fun getMeta(name: Name): Laminate? { + val childLayers = layers.mapNotNull { it.getMeta(name) } + return if (childLayers.isEmpty()) null else Laminate(childLayers) } /** - * Generate sealed meta using [mergeRule] + * Generate sealed meta by interweaving all layers. If a value is present in at least on layer, it will be present + * in the result. */ public fun merge(): SealedMeta { val items = layers.map { it.items.keys }.flatten().associateWith { key -> @@ -36,6 +34,17 @@ public class Laminate(layers: List) : TypedMeta { return SealedMeta(value, items) } + /** + * Generate sealed meta by stacking layers. If node is present in the upper layer, then the lower layers will be + * ignored event if they have values that are not present on top layer. + */ + public fun top(): SealedMeta { + val items = layers.map { it.items.keys }.flatten().associateWith { key -> + layers.asSequence().map { it.items[key] }.filterNotNull().first().seal() + } + return SealedMeta(value, items) + } + override fun toString(): String = Meta.toString(this) override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta) override fun hashCode(): Int = Meta.hashCode(this) @@ -47,7 +56,7 @@ public class Laminate(layers: List) : TypedMeta { * * TODO add picture */ - public val replaceRule: (Sequence) -> SealedMeta = { it.first().seal() } + public val replaceRule: (Sequence) -> SealedMeta? = { it.firstOrNull()?.seal() } private fun Sequence.merge(): SealedMeta { val value = firstNotNullOfOrNull { it.value } @@ -61,7 +70,7 @@ public class Laminate(layers: List) : TypedMeta { val items = groups.mapValues { entry -> entry.value.asSequence().map { it.value }.merge() } - return SealedMeta(value,items) + return SealedMeta(value, items) } @@ -69,12 +78,22 @@ public class Laminate(layers: List) : TypedMeta { * The values a replaced but meta children are joined * TODO add picture */ - public val mergeRule: (Sequence) -> TypedMeta = { it.merge() } + public val mergeRule: (Sequence) -> SealedMeta? = { it.merge() } } } -@Suppress("FunctionName") -public fun Laminate(vararg layers: Meta?): Laminate = Laminate(layers.filterNotNull()) +public fun Laminate(layers: Collection): Laminate { + val flatLayers = layers.flatMap { + if (it is Laminate) { + it.layers + } else { + listOf(it) + } + }.filterNotNull() + return Laminate(flatLayers) +} + +public fun Laminate(vararg layers: Meta?): Laminate = Laminate(listOf(*layers)) /** * Performance optimized version of get method 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 21f9e5e3..2e2e4a10 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 @@ -144,9 +144,18 @@ public interface TypedMeta> : Meta { public val self: M get() = this as M - override val value: Value? override val items: Map + override fun getMeta(name: Name): M? { + tailrec fun M.find(name: Name): M? = if (name.isEmpty()) { + this + } else { + items[name.firstOrNull()!!]?.find(name.cutFirst()) + } + + return self.find(name) + } + override fun toMeta(): Meta = this } 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 bb23049a..f626448f 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,6 +1,7 @@ package space.kscience.dataforge.meta import kotlinx.serialization.Serializable +import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.names.* import space.kscience.dataforge.values.EnumValue import space.kscience.dataforge.values.Value @@ -154,8 +155,9 @@ public interface MutableTypedMeta> : TypedMeta, Mutab * 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 */ + @DFExperimental public fun attach(name: Name, node: M) - + override fun getMeta(name: Name): M? override fun getOrCreate(name: Name): M } @@ -298,13 +300,13 @@ private class MutableMetaImpl( listeners.removeAll { it.owner === owner } } - private fun ObservableMeta.adoptBy(parent: MutableMetaImpl, key: NameToken) { onChange(parent) { name -> parent.invalidate(key + name) } } + @DFExperimental override fun attach(name: Name, node: ObservableMutableMeta) { when (name.length) { 0 -> error("Can't set a meta with empty name") diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ObservableMeta.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ObservableMeta.kt index 26cc1cd7..c5f6b4d7 100644 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ObservableMeta.kt +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/meta/ObservableMeta.kt @@ -1,5 +1,6 @@ package space.kscience.dataforge.meta +import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.names.* import space.kscience.dataforge.values.Value import kotlin.jvm.Synchronized @@ -34,7 +35,20 @@ public interface ObservableMeta : Meta { /** * A [Meta] which is both observable and mutable */ -public interface ObservableMutableMeta : ObservableMeta, MutableMeta, MutableTypedMeta +public interface ObservableMutableMeta : ObservableMeta, MutableMeta, MutableTypedMeta{ + override fun getOrCreate(name: Name): ObservableMutableMeta + + + override fun getMeta(name: Name): ObservableMutableMeta?{ + tailrec fun ObservableMutableMeta.find(name: Name): ObservableMutableMeta? = if (name.isEmpty()) { + this + } else { + items[name.firstOrNull()!!]?.find(name.cutFirst()) + } + + return find(name) + } +} private class ObservableMetaWrapper( val origin: MutableMeta, @@ -59,7 +73,7 @@ private class ObservableMetaWrapper( override val items: Map get() = origin.items.mapValues { ObservableMetaWrapper(it.value) } - override fun getMeta(name: Name): MutableMeta? = origin.getMeta(name) + override fun getMeta(name: Name): ObservableMetaWrapper? = origin.getMeta(name)?.let{ObservableMetaWrapper(it)} override var value: Value? get() = origin.value @@ -91,6 +105,7 @@ private class ObservableMetaWrapper( return origin.toMeta() } + @DFExperimental override fun attach(name: Name, node: ObservableMutableMeta) { set(name, node) node.onChange(this) { changeName -> 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 8cda90e9..df9afc12 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 @@ -8,7 +8,6 @@ import space.kscience.dataforge.values.asValue /** * The meta implementation which is guaranteed to be immutable. * - * If the argument is possibly mutable node, it is copied on creation */ @Serializable public class SealedMeta internal constructor( @@ -21,7 +20,7 @@ public class SealedMeta internal constructor( } /** - * Generate sealed node from [this]. If it is already sealed return it as is + * Generate sealed node from [this]. If it is already sealed return it as is. */ public fun Meta.seal(): SealedMeta = this as? SealedMeta ?: SealedMeta( value, diff --git a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/names/Name.kt b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/names/Name.kt index 4ff779a0..7bf8d14e 100644 --- a/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/names/Name.kt +++ b/dataforge-meta/src/commonMain/kotlin/space/kscience/dataforge/names/Name.kt @@ -195,7 +195,7 @@ public fun Name.endsWith(name: Name): Boolean = this.length >= name.length && (this == name || tokens.subList(length - name.length, length) == name.tokens) /** - * if [this] starts with given [head] name, returns the reminder of the name (could be empty). Otherwise returns null + * if [this] starts with given [head] name, returns the reminder of the name (could be empty). Otherwise, returns null */ public fun Name.removeHeadOrNull(head: Name): Name? = if (startsWith(head)) { Name(tokens.subList(head.length, length))