Optimized type projections in meta. Set operations are now non-generic

This commit is contained in:
Alexander Nozik 2019-06-01 16:04:45 +03:00
parent f0413464a3
commit d08e1ee57d
11 changed files with 117 additions and 98 deletions

View File

@ -90,6 +90,7 @@ object BinaryMetaFormat : MetaFormat {
return readText(max = length)
}
@Suppress("UNCHECKED_CAST")
private fun Input.readMetaItem(): MetaItem<MetaBuilder> {
val keyChar = readByte().toChar()
return when (keyChar) {
@ -119,6 +120,6 @@ object BinaryMetaFormat : MetaFormat {
MetaItem.NodeItem(meta)
}
else -> error("Unknown serialization key character: $keyChar")
}
} as MetaItem<MetaBuilder>
}
}

View File

@ -49,8 +49,7 @@ fun Value.toJson(): JsonElement {
fun Meta.toJson(): JsonObject {
val map = this.items.mapValues { entry ->
val value = entry.value
when (value) {
when (val value = entry.value) {
is MetaItem.ValueItem -> value.value.toJson()
is MetaItem.NodeItem -> value.node.toJson()
}
@ -70,8 +69,9 @@ class JsonMeta(val json: JsonObject) : Meta {
}
}
@Suppress("UNCHECKED_CAST")
private operator fun MutableMap<String, MetaItem<JsonMeta>>.set(key: String, value: JsonElement) = when (value) {
is JsonPrimitive -> this[key] = MetaItem.ValueItem(value.toValue())
is JsonPrimitive -> this[key] = MetaItem.ValueItem(value.toValue()) as MetaItem<JsonMeta>
is JsonObject -> this[key] = MetaItem.NodeItem(value.toMeta())
is JsonArray -> {
when {
@ -82,12 +82,12 @@ class JsonMeta(val json: JsonObject) : Meta {
(it as JsonPrimitive).toValue()
}
)
this[key] = MetaItem.ValueItem(listValue)
this[key] = MetaItem.ValueItem(listValue) as MetaItem<JsonMeta>
}
else -> value.forEachIndexed { index, jsonElement ->
when (jsonElement) {
is JsonObject -> this["$key[$index]"] = MetaItem.NodeItem(JsonMeta(jsonElement))
is JsonPrimitive -> this["$key[$index]"] = MetaItem.ValueItem(jsonElement.toValue())
is JsonPrimitive -> this["$key[$index]"] = MetaItem.ValueItem(jsonElement.toValue()) as MetaItem<JsonMeta>
is JsonArray -> TODO("Nested arrays not supported")
}
}

View File

@ -1,6 +1,5 @@
package hep.dataforge.meta
import hep.dataforge.names.Name
import hep.dataforge.names.NameToken
import hep.dataforge.names.asName
@ -9,12 +8,12 @@ import hep.dataforge.names.asName
/**
* Mutable meta representing object state
*/
open class Config : AbstractMutableMeta<Config>() {
class Config : AbstractMutableMeta<Config>() {
/**
* Attach configuration node instead of creating one
*/
override fun wrap(name: Name, meta: Meta): Config = meta.toConfig()
override fun wrapNode(meta: Meta): Config = meta.toConfig()
override fun empty(): Config = Config()
@ -29,7 +28,7 @@ fun Meta.toConfig(): Config = this as? Config ?: Config().also { builder ->
this.items.mapValues { entry ->
val item = entry.value
builder[entry.key.asName()] = when (item) {
is MetaItem.ValueItem -> MetaItem.ValueItem(item.value)
is MetaItem.ValueItem -> item.value
is MetaItem.NodeItem -> MetaItem.NodeItem(item.node.toConfig())
}
}

View File

@ -14,8 +14,8 @@ import hep.dataforge.values.boolean
* * a [ValueItem] (leaf)
* * a [NodeItem] (node)
*/
sealed class MetaItem<M : Meta> {
data class ValueItem<M : Meta>(val value: Value) : MetaItem<M>()
sealed class MetaItem<out M : Meta> {
data class ValueItem(val value: Value) : MetaItem<Nothing>()
data class NodeItem<M : Meta>(val node: M) : MetaItem<M>()
}
@ -35,7 +35,7 @@ interface MetaRepr {
* * Same name siblings are supported via elements with the same [Name] but different queries
*/
interface Meta : MetaRepr {
val items: Map<NameToken, MetaItem<out Meta>>
val items: Map<NameToken, MetaItem<*>>
override fun toMeta(): Meta = this
@ -50,12 +50,7 @@ interface Meta : MetaRepr {
/* Get operations*/
/**
* Fast [String]-based accessor for item map
*/
operator fun <T> Map<NameToken, T>.get(body: String, query: String = ""): T? = get(NameToken(body, query))
operator fun Meta?.get(name: Name): MetaItem<out Meta>? {
operator fun Meta?.get(name: Name): MetaItem<*>? {
if (this == null) return null
return name.first()?.let { token ->
val tail = name.cutFirst()
@ -66,13 +61,13 @@ operator fun Meta?.get(name: Name): MetaItem<out Meta>? {
}
}
operator fun Meta?.get(token: NameToken): MetaItem<out Meta>? = this?.items?.get(token)
operator fun Meta?.get(key: String): MetaItem<out Meta>? = get(key.toName())
operator fun Meta?.get(token: NameToken): MetaItem<*>? = this?.items?.get(token)
operator fun Meta?.get(key: String): MetaItem<*>? = get(key.toName())
/**
* Get all items matching given name.
*/
fun Meta.getAll(name: Name): Map<String, MetaItem<out Meta>> {
fun Meta.getAll(name: Name): Map<String, MetaItem<*>> {
val root = when (name.length) {
0 -> error("Can't use empty name for that")
1 -> this
@ -88,7 +83,7 @@ fun Meta.getAll(name: Name): Map<String, MetaItem<out Meta>> {
?: emptyMap()
}
fun Meta.getAll(name: String): Map<String, MetaItem<out Meta>> = getAll(name.toName())
fun Meta.getAll(name: String): Map<String, MetaItem<*>> = getAll(name.toName())
/**
* Get a sequence of [Name]-[Value] pairs
@ -109,8 +104,8 @@ fun Meta.sequence(): Sequence<Pair<Name, MetaItem<*>>> {
return sequence {
items.forEach { (key, item) ->
yield(key.asName() to item)
if(item is NodeItem<*>) {
yieldAll(item.node.sequence().map { (innerKey, innerItem)->
if (item is NodeItem<*>) {
yieldAll(item.node.sequence().map { (innerKey, innerItem) ->
(key + innerKey) to innerItem
})
}
@ -130,7 +125,7 @@ interface MetaNode<M : MetaNode<M>> : Meta {
/**
* Get all items matching given name.
*/
fun <M : MetaNode<M>> MetaNode<M>.getAll(name: Name): Map<String, MetaItem<M>> {
fun <M : MetaNode<M>> M.getAll(name: Name): Map<String, MetaItem<M>> {
val root: MetaNode<M>? = when (name.length) {
0 -> error("Can't use empty name for that")
1 -> this
@ -158,7 +153,11 @@ operator fun <M : MetaNode<M>> MetaNode<M>.get(name: Name): MetaItem<M>? {
}
}
operator fun <M : MetaNode<M>> MetaNode<M>?.get(key: String): MetaItem<M>? = this?.let { get(key.toName()) }
operator fun <M : MetaNode<M>> MetaNode<M>?.get(key: String): MetaItem<M>? = if (this == null) {
null
} else {
this[key.toName()]
}
/**
* Equals and hash code implementation for meta node
@ -189,13 +188,14 @@ class SealedMeta internal constructor(override val items: Map<NameToken, MetaIte
*/
fun Meta.seal(): SealedMeta = this as? SealedMeta ?: SealedMeta(items.mapValues { entry -> entry.value.seal() })
@Suppress("UNCHECKED_CAST")
fun MetaItem<*>.seal(): MetaItem<SealedMeta> = when (this) {
is MetaItem.ValueItem -> MetaItem.ValueItem(value)
is MetaItem.NodeItem -> MetaItem.NodeItem(node.seal())
is ValueItem -> this as MetaItem<SealedMeta>
is NodeItem -> NodeItem(node.seal())
}
object EmptyMeta : Meta {
override val items: Map<NameToken, MetaItem<out Meta>> = emptyMap()
override val items: Map<NameToken, MetaItem<*>> = emptyMap()
}
/**
@ -203,8 +203,8 @@ object EmptyMeta : Meta {
*/
val MetaItem<*>?.value
get() = (this as? MetaItem.ValueItem)?.value
?: (this?.node?.get(VALUE_KEY) as? MetaItem.ValueItem)?.value
get() = (this as? ValueItem)?.value
?: (this?.node?.get(VALUE_KEY) as? ValueItem)?.value
val MetaItem<*>?.string get() = value?.string
val MetaItem<*>?.boolean get() = value?.boolean
@ -226,8 +226,8 @@ val MetaItem<*>?.stringList get() = value?.list?.map { it.string } ?: emptyList(
val <M : Meta> MetaItem<M>?.node: M?
get() = when (this) {
null -> null
is MetaItem.ValueItem -> error("Trying to interpret value meta item as node item")
is MetaItem.NodeItem -> node
is ValueItem -> error("Trying to interpret value meta item as node item")
is NodeItem -> node
}
/**
@ -239,4 +239,4 @@ interface Metoid {
fun Value.toMeta() = buildMeta { Meta.VALUE_KEY to this }
fun Meta.isEmpty() = this === EmptyMeta || this.items.isEmpty()
fun Meta.isEmpty() = this === EmptyMeta || this.items.isEmpty()

View File

@ -1,6 +1,5 @@
package hep.dataforge.meta
import hep.dataforge.names.Name
import hep.dataforge.names.asName
import hep.dataforge.values.Value
@ -8,7 +7,7 @@ import hep.dataforge.values.Value
* DSL builder for meta. Is not intended to store mutable state
*/
class MetaBuilder : AbstractMutableMeta<MetaBuilder>() {
override fun wrap(name: Name, meta: Meta): MetaBuilder = meta.builder()
override fun wrapNode(meta: Meta): MetaBuilder = meta.builder()
override fun empty(): MetaBuilder = MetaBuilder()
infix fun String.to(value: Any) {
@ -39,7 +38,7 @@ fun Meta.builder(): MetaBuilder {
items.mapValues { entry ->
val item = entry.value
builder[entry.key.asName()] = when (item) {
is MetaItem.ValueItem -> MetaItem.ValueItem<MetaBuilder>(item.value)
is MetaItem.ValueItem -> item.value
is MetaItem.NodeItem -> MetaItem.NodeItem(item.node.builder())
}
}

View File

@ -80,6 +80,9 @@ data class RegexpItemTransformationRule(
}
/**
* A set of [TransformationRule] to either transform static meta or create dynamically updated [MutableMeta]
*/
inline class MetaTransformation(val transformations: Collection<TransformationRule>) {
/**
@ -109,7 +112,7 @@ inline class MetaTransformation(val transformations: Collection<TransformationRu
* Listens for changes in the source node and translates them into second node if transformation set contains a corresponding rule.
*/
fun <M : MutableMeta<M>> bind(source: MutableMeta<*>, target: M) {
source.onChange(target) { name, oldItem, newItem ->
source.onChange(target) { name, _, newItem ->
transformations.forEach { t ->
if (t.matches(name, newItem)) {
t.transformItem(name, newItem, target)
@ -123,18 +126,29 @@ inline class MetaTransformation(val transformations: Collection<TransformationRu
}
}
/**
* A builder for a set of transformation rules
*/
class MetaTransformationBuilder {
val transformations = HashSet<TransformationRule>()
/**
* Keep all items with name satisfying the criteria
*/
fun keep(selector: (Name) -> Boolean) {
transformations.add(KeepTransformationRule(selector))
}
/**
* Keep specific item (including its descendants)
*/
fun keep(name: Name) {
keep{it == name}
}
fun keepNode(name: Name){
keep{it.startsWith(name)}
fun move(from: Name, to: Name){
transformations.add(
SingleItemTransformationRule(from){ _, item -> setItem(to, item)}
)
}
}

View File

@ -10,15 +10,8 @@ internal data class MetaListener(
interface MutableMeta<M : MutableMeta<M>> : MetaNode<M> {
/**
* Transform given meta to node type of this meta tree
* @param name the name of the node where meta should be attached. Needed for correct assignment validators and styles
* @param meta the node itself
*/
fun wrap(name: Name, meta: Meta): M
override val items: Map<NameToken, MetaItem<M>>
operator fun set(name: Name, item: MetaItem<M>?)
operator fun set(name: Name, item: MetaItem<*>?)
fun onChange(owner: Any? = null, action: (Name, MetaItem<*>?, MetaItem<*>?) -> Unit)
fun removeListener(owner: Any? = null)
}
@ -69,59 +62,70 @@ abstract class AbstractMutableMeta<M : MutableMeta<M>> : AbstractMetaNode<M>(),
itemChanged(key.asName(), oldItem, newItem)
}
@Suppress("UNCHECKED_CAST")
protected fun wrapItem(item: MetaItem<*>?): MetaItem<M>? = when (item) {
null -> null
is MetaItem.ValueItem -> item as MetaItem<M>
is MetaItem.NodeItem<*> -> MetaItem.NodeItem(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 operator fun set(name: Name, item: MetaItem<M>?) {
override operator fun set(name: Name, item: MetaItem<*>?) {
when (name.length) {
0 -> error("Can't setValue meta item for empty name")
1 -> {
val token = name.first()!!
replaceItem(token, get(name), item)
replaceItem(token, get(name), wrapItem(item))
}
else -> {
val token = name.first()!!
//get existing or create new node. Query is ignored for new node
val child = this.items[token]?.node
?: empty().also { this[token.body.toName()] = MetaItem.NodeItem(it) }
child[name.cutFirst()] = item
if(items[token] == null){
replaceItem(token,null, MetaItem.NodeItem(empty()))
}
items[token]?.node!![name.cutFirst()] = item
}
}
}
}
fun <M : MutableMeta<M>> MutableMeta<M>.remove(name: Name) = set(name, null)
fun <M : MutableMeta<M>> MutableMeta<M>.remove(name: String) = remove(name.toName())
fun MutableMeta<*>.remove(name: Name) = set(name, null)
fun MutableMeta<*>.remove(name: String) = remove(name.toName())
fun <M : MutableMeta<M>> MutableMeta<M>.setValue(name: Name, value: Value) =
fun MutableMeta<*>.setValue(name: Name, value: Value) =
set(name, MetaItem.ValueItem(value))
fun <M : MutableMeta<M>> MutableMeta<M>.setValue(name: String, value: Value) =
fun MutableMeta<*>.setValue(name: String, value: Value) =
set(name.toName(), MetaItem.ValueItem(value))
//fun <M : MutableMeta<M>> MutableMeta<M>.setItem(token: NameToken, item: MetaItem<M>?) = set(token.asName(), item)
//fun <M : MutableMeta<M>> MutableMeta<M>.setItem(name: String, item: MetaItem<M>) = set(name.toName(), item)
fun <M : MutableMeta<M>> MutableMeta<M>.setItem(name: Name, item: MetaItem<*>) {
fun MutableMeta<*>.setItem(name: Name, item: MetaItem<*>?) {
when (item) {
is MetaItem.ValueItem<*> -> setValue(name, item.value)
null -> remove(name)
is MetaItem.ValueItem -> setValue(name, item.value)
is MetaItem.NodeItem<*> -> setNode(name, item.node)
}
}
fun <M : MutableMeta<M>> MutableMeta<M>.setItem(name: String, item: MetaItem<*>) = setItem(name.toName(), item)
fun MutableMeta<*>.setItem(name: String, item: MetaItem<*>?) = setItem(name.toName(), item)
fun <M : MutableMeta<M>> MutableMeta<M>.setNode(name: Name, node: Meta) =
set(name, MetaItem.NodeItem(wrap(name, node)))
fun MutableMeta<*>.setNode(name: Name, node: Meta) =
set(name, MetaItem.NodeItem(node))
fun <M : MutableMeta<M>> MutableMeta<M>.setNode(name: String, node: Meta) = setNode(name.toName(), node)
fun MutableMeta<*>.setNode(name: String, node: Meta) = setNode(name.toName(), node)
/**
* Universal set method
*/
operator fun <M : MutableMeta<M>> MutableMeta<M>.set(name: Name, value: Any?) {
operator fun MutableMeta<*>.set(name: Name, value: Any?) {
when (value) {
null -> remove(name)
is MetaItem<*> -> setItem(name, value)
@ -131,9 +135,9 @@ operator fun <M : MutableMeta<M>> MutableMeta<M>.set(name: Name, value: Any?) {
}
}
operator fun <M : MutableMeta<M>> M.set(name: NameToken, value: Any?) = set(name.asName(), value)
operator fun MutableMeta<*>.set(name: NameToken, value: Any?) = set(name.asName(), value)
operator fun <M : MutableMeta<M>> M.set(key: String, value: Any?) = set(key.toName(), value)
operator fun MutableMeta<*>.set(key: String, value: Any?) = set(key.toName(), value)
/**
* Update existing mutable node with another node. The rules are following:
@ -143,8 +147,7 @@ operator fun <M : MutableMeta<M>> M.set(key: String, value: Any?) = set(key.toNa
*/
fun <M : MutableMeta<M>> M.update(meta: Meta) {
meta.items.forEach { entry ->
val value = entry.value
when (value) {
when (val value = entry.value) {
is MetaItem.ValueItem -> setValue(entry.key.asName(), value.value)
is MetaItem.NodeItem -> (this[entry.key.asName()] as? MetaItem.NodeItem)?.node?.update(value.node)
?: run { setNode(entry.key.asName(), value.node) }
@ -154,10 +157,10 @@ fun <M : MutableMeta<M>> M.update(meta: Meta) {
/* Same name siblings generation */
fun <M : MutableMeta<M>> M.setIndexedItems(
fun MutableMeta<*>.setIndexedItems(
name: Name,
items: Iterable<MetaItem<M>>,
indexFactory: MetaItem<M>.(index: Int) -> String = { it.toString() }
items: Iterable<MetaItem<*>>,
indexFactory: MetaItem<*>.(index: Int) -> String = { it.toString() }
) {
val tokens = name.tokens.toMutableList()
val last = tokens.last()
@ -168,21 +171,21 @@ fun <M : MutableMeta<M>> M.setIndexedItems(
}
}
fun <M : MutableMeta<M>> M.setIndexed(
fun MutableMeta<*>.setIndexed(
name: Name,
metas: Iterable<Meta>,
indexFactory: MetaItem<M>.(index: Int) -> String = { it.toString() }
indexFactory: MetaItem<*>.(index: Int) -> String = { it.toString() }
) {
setIndexedItems(name, metas.map { MetaItem.NodeItem(wrap(name, it)) }, indexFactory)
setIndexedItems(name, metas.map { MetaItem.NodeItem(it) }, indexFactory)
}
operator fun <M : MutableMeta<M>> M.set(name: Name, metas: Iterable<Meta>) = setIndexed(name, metas)
operator fun <M : MutableMeta<M>> M.set(name: String, metas: Iterable<Meta>) = setIndexed(name.toName(), metas)
operator fun MutableMeta<*>.set(name: Name, metas: Iterable<Meta>): Unit = setIndexed(name, metas)
operator fun MutableMeta<*>.set(name: String, metas: Iterable<Meta>): Unit = setIndexed(name.toName(), metas)
/**
* Append the node with a same-name-sibling, automatically generating numerical index
*/
fun <M : MutableMeta<M>> M.append(name: Name, value: Any?) {
fun MutableMeta<*>.append(name: Name, value: Any?) {
require(!name.isEmpty()) { "Name could not be empty for append operation" }
val newIndex = name.last()!!.index
if (newIndex.isNotEmpty()) {
@ -193,4 +196,4 @@ fun <M : MutableMeta<M>> M.append(name: Name, value: Any?) {
}
}
fun <M : MutableMeta<M>> M.append(name: String, value: Any?) = append(name.toName(), value)
fun MutableMeta<*>.append(name: String, value: Any?) = append(name.toName(), value)

View File

@ -12,14 +12,11 @@ import kotlin.reflect.KProperty
* @param style - the style
*/
class Styled(val base: Meta, val style: Config = Config().empty()) : AbstractMutableMeta<Styled>() {
override fun wrap(name: Name, meta: Meta): Styled {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun wrapNode(meta: Meta): Styled = Styled(meta)
override fun empty(): Styled {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun empty(): Styled = Styled(EmptyMeta)
@Suppress("UNCHECKED_CAST")
override val items: Map<NameToken, MetaItem<Styled>>
get() = (base.items.keys + style.items.keys).associate { key ->
val value = base.items[key]
@ -27,10 +24,10 @@ class Styled(val base: Meta, val style: Config = Config().empty()) : AbstractMut
val item: MetaItem<Styled> = when (value) {
null -> when (styleValue) {
null -> error("Should be unreachable")
is MetaItem.ValueItem -> MetaItem.ValueItem(styleValue.value)
is MetaItem.NodeItem -> MetaItem.NodeItem(Styled(style.empty(), styleValue.node))
else -> styleValue.value as MetaItem<Styled>
}
is MetaItem.ValueItem -> MetaItem.ValueItem(value.value)
is MetaItem.ValueItem -> value as MetaItem<Styled>
is MetaItem.NodeItem -> MetaItem.NodeItem(
Styled(value.node, styleValue?.node ?: Config.empty())
)
@ -38,7 +35,7 @@ class Styled(val base: Meta, val style: Config = Config().empty()) : AbstractMut
key to item
}
override fun set(name: Name, item: MetaItem<Styled>?) {
override fun set(name: Name, item: MetaItem<*>?) {
if (item == null) {
style.remove(name)
} else {

View File

@ -121,6 +121,11 @@ fun Name.withIndex(index: String): Name {
return Name(tokens)
}
/**
* Fast [String]-based accessor for item map
*/
operator fun <T> Map<NameToken, T>.get(body: String, query: String = ""): T? = get(NameToken(body, query))
operator fun <T> Map<Name, T>.get(name: String) = get(name.toName())
operator fun <T> MutableMap<Name, T>.set(name: String, value: T) = set(name.toName(), value)

View File

@ -35,12 +35,13 @@ class DynamicMeta(val obj: dynamic) : Meta {
private fun isArray(@Suppress("UNUSED_PARAMETER") obj: dynamic): Boolean =
js("Array.isArray(obj)") as Boolean
@Suppress("UNCHECKED_CAST")
private fun asItem(obj: dynamic): MetaItem<DynamicMeta>? {
if (obj == null) return MetaItem.ValueItem(Null)
return when (jsTypeOf(obj)) {
"boolean" -> MetaItem.ValueItem(Value.of(obj as Boolean))
"number" -> MetaItem.ValueItem(Value.of(obj as Number))
"string" -> MetaItem.ValueItem(Value.of(obj as String))
if (obj == null) return MetaItem.ValueItem(Null) as MetaItem<DynamicMeta>
return when (jsTypeOf(obj as? Any)) {
"boolean" -> MetaItem.ValueItem(Value.of(obj as Boolean)) as MetaItem<DynamicMeta>
"number" -> MetaItem.ValueItem(Value.of(obj as Number)) as MetaItem<DynamicMeta>
"string" -> MetaItem.ValueItem(Value.of(obj as String)) as MetaItem<DynamicMeta>
"object" -> MetaItem.NodeItem(DynamicMeta(obj))
else -> null
}

View File

@ -61,13 +61,13 @@ class SimpleWorkspaceTest {
allData()
}
joinByGroup<Int, Double> { context ->
group("even", filter = { name, data -> name.toString().toInt() % 2 == 0 }) {
group("even", filter = { name, _ -> name.toString().toInt() % 2 == 0 }) {
result { data ->
context.logger.info { "Starting even" }
data.values.average()
}
}
group("odd", filter = { name, data -> name.toString().toInt() % 2 == 1 }) {
group("odd", filter = { name, _ -> name.toString().toInt() % 2 == 1 }) {
result { data ->
context.logger.info { "Starting odd" }
data.values.average()