Laminate refactor

This commit is contained in:
Alexander Nozik 2021-08-07 21:26:58 +03:00
parent be2daca25e
commit c01bc36d41
6 changed files with 68 additions and 24 deletions

View File

@ -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]. * 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. * If [layers] list contains a [Laminate] it is flat-mapped.
*/ */
public class Laminate(layers: List<Meta>) : TypedMeta<SealedMeta> { public class Laminate internal constructor(public val layers: List<Meta>) : TypedMeta<Laminate> {
override val value: Value? = layers.firstNotNullOfOrNull { it.value } override val value: Value? = layers.firstNotNullOfOrNull { it.value }
public val layers: List<Meta> = layers.flatMap { override val items: Map<NameToken, Laminate> by lazy {
if (it is Laminate) { layers.map { it.items.keys }.flatten().associateWith { key ->
it.layers Laminate(layers.mapNotNull { it.items[key] })
} else {
listOf(it)
} }
} }
override val items: Map<NameToken, SealedMeta> by lazy { override fun getMeta(name: Name): Laminate? {
layers.map { it.items.keys }.flatten().associateWith { key -> val childLayers = layers.mapNotNull { it.getMeta(name) }
layers.asSequence().map { it.items[key] }.filterNotNull().let(replaceRule) 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 { public fun merge(): SealedMeta {
val items = layers.map { it.items.keys }.flatten().associateWith { key -> val items = layers.map { it.items.keys }.flatten().associateWith { key ->
@ -36,6 +34,17 @@ public class Laminate(layers: List<Meta>) : TypedMeta<SealedMeta> {
return SealedMeta(value, items) 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 toString(): String = Meta.toString(this)
override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta) override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta)
override fun hashCode(): Int = Meta.hashCode(this) override fun hashCode(): Int = Meta.hashCode(this)
@ -47,7 +56,7 @@ public class Laminate(layers: List<Meta>) : TypedMeta<SealedMeta> {
* *
* TODO add picture * TODO add picture
*/ */
public val replaceRule: (Sequence<Meta>) -> SealedMeta = { it.first().seal() } public val replaceRule: (Sequence<Meta>) -> SealedMeta? = { it.firstOrNull()?.seal() }
private fun Sequence<Meta>.merge(): SealedMeta { private fun Sequence<Meta>.merge(): SealedMeta {
val value = firstNotNullOfOrNull { it.value } val value = firstNotNullOfOrNull { it.value }
@ -69,12 +78,22 @@ public class Laminate(layers: List<Meta>) : TypedMeta<SealedMeta> {
* The values a replaced but meta children are joined * The values a replaced but meta children are joined
* TODO add picture * TODO add picture
*/ */
public val mergeRule: (Sequence<Meta>) -> TypedMeta<SealedMeta> = { it.merge() } public val mergeRule: (Sequence<Meta>) -> SealedMeta? = { it.merge() }
} }
} }
@Suppress("FunctionName") public fun Laminate(layers: Collection<Meta?>): Laminate {
public fun Laminate(vararg layers: Meta?): Laminate = Laminate(layers.filterNotNull()) 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 * Performance optimized version of get method

View File

@ -144,9 +144,18 @@ public interface TypedMeta<out M : TypedMeta<M>> : Meta {
public val self: M public val self: M
get() = this as M get() = this as M
override val value: Value?
override val items: Map<NameToken, M> override val items: Map<NameToken, M>
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 override fun toMeta(): Meta = this
} }

View File

@ -1,6 +1,7 @@
package space.kscience.dataforge.meta package space.kscience.dataforge.meta
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.* import space.kscience.dataforge.names.*
import space.kscience.dataforge.values.EnumValue import space.kscience.dataforge.values.EnumValue
import space.kscience.dataforge.values.Value import space.kscience.dataforge.values.Value
@ -154,8 +155,9 @@ public interface MutableTypedMeta<M : MutableTypedMeta<M>> : TypedMeta<M>, Mutab
* Zero-copy attach or replace existing node. Node is used with any additional state, listeners, etc. * 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 * 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) public fun attach(name: Name, node: M)
override fun getMeta(name: Name): M?
override fun getOrCreate(name: Name): M override fun getOrCreate(name: Name): M
} }
@ -298,13 +300,13 @@ private class MutableMetaImpl(
listeners.removeAll { it.owner === owner } listeners.removeAll { it.owner === owner }
} }
private fun ObservableMeta.adoptBy(parent: MutableMetaImpl, key: NameToken) { private fun ObservableMeta.adoptBy(parent: MutableMetaImpl, key: NameToken) {
onChange(parent) { name -> onChange(parent) { name ->
parent.invalidate(key + name) parent.invalidate(key + name)
} }
} }
@DFExperimental
override fun attach(name: Name, node: ObservableMutableMeta) { override fun attach(name: Name, node: ObservableMutableMeta) {
when (name.length) { when (name.length) {
0 -> error("Can't set a meta with empty name") 0 -> error("Can't set a meta with empty name")

View File

@ -1,5 +1,6 @@
package space.kscience.dataforge.meta package space.kscience.dataforge.meta
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.* import space.kscience.dataforge.names.*
import space.kscience.dataforge.values.Value import space.kscience.dataforge.values.Value
import kotlin.jvm.Synchronized import kotlin.jvm.Synchronized
@ -34,7 +35,20 @@ public interface ObservableMeta : Meta {
/** /**
* A [Meta] which is both observable and mutable * A [Meta] which is both observable and mutable
*/ */
public interface ObservableMutableMeta : ObservableMeta, MutableMeta, MutableTypedMeta<ObservableMutableMeta> public interface ObservableMutableMeta : ObservableMeta, MutableMeta, MutableTypedMeta<ObservableMutableMeta>{
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( private class ObservableMetaWrapper(
val origin: MutableMeta, val origin: MutableMeta,
@ -59,7 +73,7 @@ private class ObservableMetaWrapper(
override val items: Map<NameToken, ObservableMetaWrapper> override val items: Map<NameToken, ObservableMetaWrapper>
get() = origin.items.mapValues { ObservableMetaWrapper(it.value) } 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? override var value: Value?
get() = origin.value get() = origin.value
@ -91,6 +105,7 @@ private class ObservableMetaWrapper(
return origin.toMeta() return origin.toMeta()
} }
@DFExperimental
override fun attach(name: Name, node: ObservableMutableMeta) { override fun attach(name: Name, node: ObservableMutableMeta) {
set(name, node) set(name, node)
node.onChange(this) { changeName -> node.onChange(this) { changeName ->

View File

@ -8,7 +8,6 @@ import space.kscience.dataforge.values.asValue
/** /**
* The meta implementation which is guaranteed to be immutable. * The meta implementation which is guaranteed to be immutable.
* *
* If the argument is possibly mutable node, it is copied on creation
*/ */
@Serializable @Serializable
public class SealedMeta internal constructor( 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( public fun Meta.seal(): SealedMeta = this as? SealedMeta ?: SealedMeta(
value, value,

View File

@ -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) 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)) { public fun Name.removeHeadOrNull(head: Name): Name? = if (startsWith(head)) {
Name(tokens.subList(head.length, length)) Name(tokens.subList(head.length, length))