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].
* 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 }
public val layers: List<Meta> = layers.flatMap {
if (it is Laminate) {
it.layers
} else {
listOf(it)
override val items: Map<NameToken, Laminate> by lazy {
layers.map { it.items.keys }.flatten().associateWith { key ->
Laminate(layers.mapNotNull { it.items[key] })
}
}
override val items: Map<NameToken, SealedMeta> 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<Meta>) : TypedMeta<SealedMeta> {
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<Meta>) : TypedMeta<SealedMeta> {
*
* 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 {
val value = firstNotNullOfOrNull { it.value }
@ -61,7 +70,7 @@ public class Laminate(layers: List<Meta>) : TypedMeta<SealedMeta> {
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<Meta>) : TypedMeta<SealedMeta> {
* The values a replaced but meta children are joined
* 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(vararg layers: Meta?): Laminate = Laminate(layers.filterNotNull())
public fun Laminate(layers: Collection<Meta?>): 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

View File

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

View File

@ -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<M : MutableTypedMeta<M>> : TypedMeta<M>, 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")

View File

@ -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<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(
val origin: MutableMeta,
@ -59,7 +73,7 @@ private class ObservableMetaWrapper(
override val items: Map<NameToken, ObservableMetaWrapper>
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 ->

View File

@ -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,

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)
/**
* 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))