Moved to K1.3.
Removed MultiNodeItems and replaced same name siblings by queries. Laminate merge.
This commit is contained in:
parent
a7a1f776a7
commit
3033cc6304
10
build.gradle
10
build.gradle
@ -1,12 +1,10 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.3.0-rc-190'
|
ext.kotlin_version = '1.3.0'
|
||||||
ext.serialization_version = '0.8.3-rc13'
|
ext.serialization_version = '0.9.0'
|
||||||
ext.kotlinx_io_version = '0.1.0-alpha-24-rc13'
|
ext.kotlinx_io_version = '0.1.0-beta-1'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
jcenter()
|
jcenter()
|
||||||
maven {
|
|
||||||
url = "http://dl.bintray.com/kotlin/kotlin-eap"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
@ -2,10 +2,11 @@ plugins {
|
|||||||
id 'kotlin-multiplatform'
|
id 'kotlin-multiplatform'
|
||||||
//id 'kotlinx-serialization'
|
//id 'kotlinx-serialization'
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
maven { url = 'http://dl.bintray.com/kotlin/kotlin-eap' }
|
jcenter()
|
||||||
mavenCentral()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
targets {
|
targets {
|
||||||
fromPreset(presets.jvm, 'jvm')
|
fromPreset(presets.jvm, 'jvm')
|
||||||
|
@ -54,7 +54,7 @@ object BinaryMetaFormat : MetaFormat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun read(input: Input): Meta {
|
override fun read(input: Input): Meta {
|
||||||
return (input.readMetaItem() as MetaItem.SingleNodeItem).node
|
return (input.readMetaItem() as MetaItem.NodeItem).node
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Output.writeChar(char: Char) = writeByte(char.toByte())
|
private fun Output.writeChar(char: Char) = writeByte(char.toByte())
|
||||||
@ -115,21 +115,14 @@ object BinaryMetaFormat : MetaFormat {
|
|||||||
writeChar('M')
|
writeChar('M')
|
||||||
writeInt(meta.items.size)
|
writeInt(meta.items.size)
|
||||||
meta.items.forEach { (key, item) ->
|
meta.items.forEach { (key, item) ->
|
||||||
writeString(key)
|
writeString(key.toString())
|
||||||
when (item) {
|
when (item) {
|
||||||
is MetaItem.ValueItem -> {
|
is MetaItem.ValueItem -> {
|
||||||
writeValue(item.value)
|
writeValue(item.value)
|
||||||
}
|
}
|
||||||
is MetaItem.SingleNodeItem -> {
|
is MetaItem.NodeItem -> {
|
||||||
writeMeta(item.node)
|
writeMeta(item.node)
|
||||||
}
|
}
|
||||||
is MetaItem.MultiNodeItem -> {
|
|
||||||
writeChar('#')
|
|
||||||
writeInt(item.nodes.size)
|
|
||||||
item.nodes.forEach {
|
|
||||||
writeMeta(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -165,12 +158,7 @@ object BinaryMetaFormat : MetaFormat {
|
|||||||
set(name, item)
|
set(name, item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MetaItem.SingleNodeItem(meta)
|
MetaItem.NodeItem(meta)
|
||||||
}
|
|
||||||
'#' -> {
|
|
||||||
val length = readInt()
|
|
||||||
val nodes = (1..length).map { (readMetaItem() as MetaItem.SingleNodeItem).node }
|
|
||||||
MetaItem.MultiNodeItem(nodes)
|
|
||||||
}
|
}
|
||||||
else -> error("Unknown serialization key character: $keyChar")
|
else -> error("Unknown serialization key character: $keyChar")
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,14 @@ package hep.dataforge.meta.io
|
|||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
import hep.dataforge.meta.MetaItem
|
import hep.dataforge.meta.MetaItem
|
||||||
import hep.dataforge.meta.Value
|
import hep.dataforge.meta.Value
|
||||||
|
import hep.dataforge.names.NameToken
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represent any js object as meta
|
* Represent any js object as meta
|
||||||
*/
|
*/
|
||||||
class JSMeta(val obj: Any) : Meta {
|
class JSMeta(val obj: Any) : Meta {
|
||||||
override val items: Map<String, MetaItem<out Meta>>
|
override val items: Map<NameToken, MetaItem<out Meta>>
|
||||||
get() = listKeys(obj).associateWith { convert(js("obj[it]")) }
|
get() = listKeys(obj).map { NameToken(it) }.associateWith { convert(js("obj[it]")) }
|
||||||
|
|
||||||
private fun listKeys(obj: Any): List<String> = js("Object").keys(obj) as List<String>
|
private fun listKeys(obj: Any): List<String> = js("Object").keys(obj) as List<String>
|
||||||
|
|
||||||
@ -22,15 +23,9 @@ class JSMeta(val obj: Any) : Meta {
|
|||||||
null, isPrimitive(obj), is Number, is String, is Boolean -> MetaItem.ValueItem<JSMeta>(Value.of(obj))
|
null, isPrimitive(obj), is Number, is String, is Boolean -> MetaItem.ValueItem<JSMeta>(Value.of(obj))
|
||||||
isList(obj) -> {
|
isList(obj) -> {
|
||||||
val list = obj as List<*>
|
val list = obj as List<*>
|
||||||
//if first value is primitive, treat as value
|
MetaItem.ValueItem<JSMeta>(Value.of(list))
|
||||||
if (isPrimitive(list.first())) {
|
|
||||||
MetaItem.ValueItem<JSMeta>(Value.of(list))
|
|
||||||
} else {
|
|
||||||
//else treat as meta list
|
|
||||||
MetaItem.MultiNodeItem(list.map { JSMeta(it!!) })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else -> MetaItem.SingleNodeItem(JSMeta(obj))
|
else -> MetaItem.NodeItem(JSMeta(obj))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -34,13 +34,8 @@ private fun Meta.toJson(): JsonObject {
|
|||||||
val builder = JsonObject()
|
val builder = JsonObject()
|
||||||
items.forEach { name, item ->
|
items.forEach { name, item ->
|
||||||
when (item) {
|
when (item) {
|
||||||
is MetaItem.ValueItem -> builder[name] = item.value.toJson()
|
is MetaItem.ValueItem -> builder[name.toString()] = item.value.toJson()
|
||||||
is MetaItem.SingleNodeItem -> builder[name] = item.node.toJson()
|
is MetaItem.NodeItem -> builder[name.toString()] = item.node.toJson()
|
||||||
is MetaItem.MultiNodeItem -> {
|
|
||||||
val array = JsonArray()
|
|
||||||
item.nodes.forEach { array.add(it.toJson()) }
|
|
||||||
builder[name] = array
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return builder
|
return builder
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'kotlin-multiplatform'
|
id 'kotlin-multiplatform'
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
maven { url = 'http://dl.bintray.com/kotlin/kotlin-eap' }
|
jcenter()
|
||||||
mavenCentral()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
targets {
|
targets {
|
||||||
fromPreset(presets.jvm, 'jvm')
|
fromPreset(presets.jvm, 'jvm')
|
||||||
|
@ -22,8 +22,7 @@ fun Meta.toConfig(): Config = this as? Config ?: Config().also { builder ->
|
|||||||
val item = entry.value
|
val item = entry.value
|
||||||
builder[entry.key] = when (item) {
|
builder[entry.key] = when (item) {
|
||||||
is MetaItem.ValueItem -> MetaItem.ValueItem(item.value)
|
is MetaItem.ValueItem -> MetaItem.ValueItem(item.value)
|
||||||
is MetaItem.SingleNodeItem -> MetaItem.SingleNodeItem(item.node.toConfig())
|
is MetaItem.NodeItem -> MetaItem.NodeItem(item.node.toConfig())
|
||||||
is MetaItem.MultiNodeItem -> MetaItem.MultiNodeItem(item.nodes.map { it.toConfig() })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
package hep.dataforge.meta
|
||||||
|
|
||||||
|
import hep.dataforge.names.NameToken
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A meta laminate consisting of multiple immutable meta layers. For mutable front layer, use [StyledConfig].
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class Laminate(val layers: List<Meta>) : Meta {
|
||||||
|
|
||||||
|
override val items: Map<NameToken, MetaItem<out Meta>>
|
||||||
|
get() = layers.map { it.items.keys }.flatten().associateWith { key ->
|
||||||
|
layers.asSequence().map { it.items[key] }.filterNotNull().let(replaceRule)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate sealed meta using [mergeRule]
|
||||||
|
*/
|
||||||
|
fun merge(): SealedMeta {
|
||||||
|
val items = layers.map { it.items.keys }.flatten().associateWith { key ->
|
||||||
|
layers.asSequence().map { it.items[key] }.filterNotNull().merge()
|
||||||
|
}
|
||||||
|
return SealedMeta(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default rule which always uses the first found item in sequence alongside with its children.
|
||||||
|
*
|
||||||
|
* TODO add picture
|
||||||
|
*/
|
||||||
|
val replaceRule: (Sequence<MetaItem<*>>) -> MetaItem<SealedMeta> = { it.first().seal() }
|
||||||
|
|
||||||
|
private fun Sequence<MetaItem<*>>.merge(): MetaItem<SealedMeta> {
|
||||||
|
return when {
|
||||||
|
all { it is MetaItem.ValueItem } -> //If all items are values, take first
|
||||||
|
first().seal()
|
||||||
|
all { it is MetaItem.NodeItem } -> {
|
||||||
|
//list nodes in item
|
||||||
|
val nodes = map { it.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()
|
||||||
|
}
|
||||||
|
MetaItem.NodeItem(SealedMeta(items))
|
||||||
|
|
||||||
|
}
|
||||||
|
else -> map {
|
||||||
|
when (it) {
|
||||||
|
is MetaItem.ValueItem -> MetaItem.NodeItem(buildMeta { Meta.VALUE_KEY to it.value })
|
||||||
|
is MetaItem.NodeItem -> it
|
||||||
|
}
|
||||||
|
}.merge()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The values a replaced but meta children are joined
|
||||||
|
* TODO add picture
|
||||||
|
*/
|
||||||
|
val mergeRule: (Sequence<MetaItem<*>>) -> MetaItem<SealedMeta> = { it.merge() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO add custom rules for Laminate merge
|
@ -1,47 +1,57 @@
|
|||||||
package hep.dataforge.meta
|
package hep.dataforge.meta
|
||||||
|
|
||||||
|
import hep.dataforge.meta.Meta.Companion.VALUE_KEY
|
||||||
|
import hep.dataforge.meta.MetaItem.NodeItem
|
||||||
|
import hep.dataforge.meta.MetaItem.ValueItem
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.NameToken
|
||||||
import hep.dataforge.names.toName
|
import hep.dataforge.names.toName
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A member of the meta tree. Could be represented as one of following:
|
* A member of the meta tree. Could be represented as one of following:
|
||||||
* * a value
|
* * a [ValueItem] (leaf)
|
||||||
* * a single node
|
* * a [NodeItem] (node)
|
||||||
* * a list of nodes
|
|
||||||
*/
|
*/
|
||||||
sealed class MetaItem<M : Meta> {
|
sealed class MetaItem<M : Meta> {
|
||||||
data class ValueItem<M : Meta>(val value: Value) : MetaItem<M>()
|
data class ValueItem<M : Meta>(val value: Value) : MetaItem<M>()
|
||||||
data class SingleNodeItem<M : Meta>(val node: M) : MetaItem<M>()
|
data class NodeItem<M : Meta>(val node: M) : MetaItem<M>()
|
||||||
data class MultiNodeItem<M : Meta>(val nodes: List<M>) : MetaItem<M>()
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun <M : Meta> List<M>.get(query: String): M? {
|
|
||||||
return if (query.isEmpty()) {
|
|
||||||
first()
|
|
||||||
} else {
|
|
||||||
//TODO add custom key queries
|
|
||||||
get(query.toInt())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic meta tree representation. Elements are [MetaItem] objects that could be represented by three different entities:
|
* Generic meta tree representation. Elements are [MetaItem] objects that could be represented by three different entities:
|
||||||
* * [MetaItem.ValueItem] (leaf)
|
* * [MetaItem.ValueItem] (leaf)
|
||||||
* * [MetaItem.SingleNodeItem] single node
|
* * [MetaItem.NodeItem] single node
|
||||||
* * [MetaItem.MultiNodeItem] multi-value node
|
*
|
||||||
|
* * Same name siblings are supported via elements with the same [Name] but different queries
|
||||||
*/
|
*/
|
||||||
interface Meta {
|
interface Meta {
|
||||||
val items: Map<String, MetaItem<out Meta>>
|
val items: Map<NameToken, MetaItem<out Meta>>
|
||||||
}
|
|
||||||
|
|
||||||
operator fun Meta.get(name: Name): MetaItem<out Meta>? {
|
companion object {
|
||||||
return when (name.length) {
|
/**
|
||||||
0 -> error("Can't resolve element from empty name")
|
* A key for single value node
|
||||||
1 -> items[name.first()!!.body]
|
*/
|
||||||
else -> name.first()!!.let { token -> items[token.body]?.nodes?.get(token.query) }?.get(name.cutFirst())
|
const val VALUE_KEY = "@value"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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>? {
|
||||||
|
return name.first()?.let { token ->
|
||||||
|
val tail = name.cutFirst()
|
||||||
|
when (tail.length) {
|
||||||
|
0 -> items[token]
|
||||||
|
else -> items[token]?.node?.get(tail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun Meta.get(token: NameToken): MetaItem<out Meta>? = items[token]
|
||||||
|
|
||||||
//TODO create Java helper for meta operations
|
//TODO create Java helper for meta operations
|
||||||
operator fun Meta.get(key: String): MetaItem<out Meta>? = get(key.toName())
|
operator fun Meta.get(key: String): MetaItem<out Meta>? = get(key.toName())
|
||||||
|
|
||||||
@ -49,13 +59,15 @@ operator fun Meta.get(key: String): MetaItem<out Meta>? = get(key.toName())
|
|||||||
* A meta node that ensures that all of its descendants has at least the same type
|
* A meta node that ensures that all of its descendants has at least the same type
|
||||||
*/
|
*/
|
||||||
abstract class MetaNode<M : MetaNode<M>> : Meta {
|
abstract class MetaNode<M : MetaNode<M>> : Meta {
|
||||||
abstract override val items: Map<String, MetaItem<M>>
|
abstract override val items: Map<NameToken, MetaItem<M>>
|
||||||
|
|
||||||
operator fun get(name: Name): MetaItem<M>? {
|
operator fun get(name: Name): MetaItem<M>? {
|
||||||
return when (name.length) {
|
return name.first()?.let { token ->
|
||||||
0 -> error("Can't resolve element from empty name")
|
val tail = name.cutFirst()
|
||||||
1 -> items[name.first()!!.body]
|
when (tail.length) {
|
||||||
else -> name.first()!!.let { token -> items[token.body]?.nodes?.get(token.query) }?.get(name.cutFirst())
|
0 -> items[token]
|
||||||
|
else -> items[token]?.node?.get(tail)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,34 +90,19 @@ abstract class MetaNode<M : MetaNode<M>> : Meta {
|
|||||||
*
|
*
|
||||||
* If the argument is possibly mutable node, it is copied on creation
|
* If the argument is possibly mutable node, it is copied on creation
|
||||||
*/
|
*/
|
||||||
class SealedMeta internal constructor(override val items: Map<String, MetaItem<SealedMeta>>) : MetaNode<SealedMeta>() {
|
class SealedMeta internal constructor(override val items: Map<NameToken, MetaItem<SealedMeta>>) : MetaNode<SealedMeta>()
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun seal(meta: Meta): SealedMeta {
|
|
||||||
val items = if (meta is SealedMeta) {
|
|
||||||
meta.items
|
|
||||||
} else {
|
|
||||||
meta.items.mapValues { entry ->
|
|
||||||
val item = entry.value
|
|
||||||
when (item) {
|
|
||||||
is MetaItem.ValueItem -> MetaItem.ValueItem(item.value)
|
|
||||||
is MetaItem.SingleNodeItem -> MetaItem.SingleNodeItem(seal(item.node))
|
|
||||||
is MetaItem.MultiNodeItem -> MetaItem.MultiNodeItem(item.nodes.map { seal(it) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return SealedMeta(items)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
*/
|
*/
|
||||||
fun Meta.seal(): SealedMeta = this as? SealedMeta ?: SealedMeta.seal(this)
|
fun Meta.seal(): SealedMeta = this as? SealedMeta ?: SealedMeta(items.mapValues { entry -> entry.value.seal() })
|
||||||
|
|
||||||
|
fun MetaItem<*>.seal(): MetaItem<SealedMeta> = when (this) {
|
||||||
|
is MetaItem.ValueItem -> MetaItem.ValueItem(value)
|
||||||
|
is MetaItem.NodeItem -> MetaItem.NodeItem(node.seal())
|
||||||
|
}
|
||||||
|
|
||||||
object EmptyMeta : Meta {
|
object EmptyMeta : Meta {
|
||||||
override val items: Map<String, MetaItem<out Meta>> = emptyMap()
|
override val items: Map<NameToken, MetaItem<out Meta>> = emptyMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -113,40 +110,23 @@ object EmptyMeta : Meta {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
val MetaItem<*>.value
|
val MetaItem<*>.value
|
||||||
get() = (this as? MetaItem.ValueItem)?.value ?: error("Trying to interpret node meta item as value item")
|
get() = (this as? MetaItem.ValueItem)?.value
|
||||||
|
?: (this.node[VALUE_KEY] as? MetaItem.ValueItem)?.value
|
||||||
|
?: error("Trying to interpret node meta item as value item")
|
||||||
val MetaItem<*>.string get() = value.string
|
val MetaItem<*>.string get() = value.string
|
||||||
val MetaItem<*>.boolean get() = value.boolean
|
val MetaItem<*>.boolean get() = value.boolean
|
||||||
val MetaItem<*>.number get() = value.number
|
val MetaItem<*>.number get() = value.number
|
||||||
val MetaItem<*>.double get() = number.toDouble()
|
val MetaItem<*>.double get() = number.toDouble()
|
||||||
val MetaItem<*>.int get() = number.toInt()
|
val MetaItem<*>.int get() = number.toInt()
|
||||||
val MetaItem<*>.long get() = number.toLong()
|
val MetaItem<*>.long get() = number.toLong()
|
||||||
|
val MetaItem<*>.short get() = number.toShort()
|
||||||
|
|
||||||
val <M : Meta> MetaItem<M>.node: M
|
val <M : Meta> MetaItem<M>.node: M
|
||||||
get() = when (this) {
|
get() = when (this) {
|
||||||
is MetaItem.ValueItem -> error("Trying to interpret value meta item as node item")
|
is MetaItem.ValueItem -> error("Trying to interpret value meta item as node item")
|
||||||
is MetaItem.SingleNodeItem -> node
|
is MetaItem.NodeItem -> node
|
||||||
is MetaItem.MultiNodeItem -> nodes.first()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility method to access item content as list of nodes.
|
|
||||||
* Returns empty list if it is value item.
|
|
||||||
*/
|
|
||||||
val <M : Meta> MetaItem<M>.nodes: List<M>
|
|
||||||
get() = when (this) {
|
|
||||||
is MetaItem.ValueItem -> emptyList()//error("Trying to interpret value meta item as node item")
|
|
||||||
is MetaItem.SingleNodeItem -> listOf(node)
|
|
||||||
is MetaItem.MultiNodeItem -> nodes
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <M : Meta> MetaItem<M>.indexOf(meta: M): Int {
|
|
||||||
return when (this) {
|
|
||||||
is MetaItem.ValueItem -> -1
|
|
||||||
is MetaItem.SingleNodeItem -> if (node == meta) 0 else -1
|
|
||||||
is MetaItem.MultiNodeItem -> nodes.indexOf(meta)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic meta-holder object
|
* Generic meta-holder object
|
||||||
*/
|
*/
|
||||||
|
@ -37,9 +37,8 @@ fun Meta.builder(): MetaBuilder {
|
|||||||
items.mapValues { entry ->
|
items.mapValues { entry ->
|
||||||
val item = entry.value
|
val item = entry.value
|
||||||
builder[entry.key] = when (item) {
|
builder[entry.key] = when (item) {
|
||||||
is MetaItem.ValueItem -> MetaItem.ValueItem(item.value)
|
is MetaItem.ValueItem -> MetaItem.ValueItem<MetaBuilder>(item.value)
|
||||||
is MetaItem.SingleNodeItem -> MetaItem.SingleNodeItem(item.node.builder())
|
is MetaItem.NodeItem -> MetaItem.NodeItem(item.node.builder())
|
||||||
is MetaItem.MultiNodeItem -> MetaItem.MultiNodeItem(item.nodes.map { it.builder() })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package hep.dataforge.meta
|
package hep.dataforge.meta
|
||||||
|
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.NameToken
|
||||||
import hep.dataforge.names.plus
|
import hep.dataforge.names.plus
|
||||||
import hep.dataforge.names.toName
|
import hep.dataforge.names.toName
|
||||||
|
|
||||||
@ -10,14 +11,16 @@ class MetaListener(val owner: Any? = null, val action: (name: Name, oldItem: Met
|
|||||||
|
|
||||||
|
|
||||||
interface MutableMeta<M : MutableMeta<M>> : Meta {
|
interface MutableMeta<M : MutableMeta<M>> : Meta {
|
||||||
override val items: Map<String, MetaItem<M>>
|
override val items: Map<NameToken, MetaItem<M>>
|
||||||
operator fun set(name: Name, item: MetaItem<M>?)
|
operator fun set(name: Name, item: MetaItem<M>?)
|
||||||
fun onChange(owner: Any? = null, action: (Name, MetaItem<*>?, MetaItem<*>?) -> Unit)
|
fun onChange(owner: Any? = null, action: (Name, MetaItem<*>?, MetaItem<*>?) -> Unit)
|
||||||
fun removeListener(owner: Any)
|
fun removeListener(owner: Any)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A mutable meta node with attachable change listener
|
* A mutable meta node with attachable change listener.
|
||||||
|
*
|
||||||
|
* Changes in Meta are not thread safe.
|
||||||
*/
|
*/
|
||||||
abstract class MutableMetaNode<M : MutableMetaNode<M>> : MetaNode<M>(), MutableMeta<M> {
|
abstract class MutableMetaNode<M : MutableMetaNode<M>> : MetaNode<M>(), MutableMeta<M> {
|
||||||
private val listeners = HashSet<MetaListener>()
|
private val listeners = HashSet<MetaListener>()
|
||||||
@ -36,26 +39,24 @@ abstract class MutableMetaNode<M : MutableMetaNode<M>> : MetaNode<M>(), MutableM
|
|||||||
listeners.removeAll { it.owner === owner }
|
listeners.removeAll { it.owner === owner }
|
||||||
}
|
}
|
||||||
|
|
||||||
private val _items: MutableMap<String, MetaItem<M>> = HashMap()
|
private val _items: MutableMap<NameToken, MetaItem<M>> = HashMap()
|
||||||
|
|
||||||
override val items: Map<String, MetaItem<M>>
|
override val items: Map<NameToken, MetaItem<M>>
|
||||||
get() = _items
|
get() = _items
|
||||||
|
|
||||||
protected fun itemChanged(name: Name, oldItem: MetaItem<*>?, newItem: MetaItem<*>?) {
|
protected fun itemChanged(name: Name, oldItem: MetaItem<*>?, newItem: MetaItem<*>?) {
|
||||||
listeners.forEach { it(name, oldItem, newItem) }
|
listeners.forEach { it(name, oldItem, newItem) }
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun replaceItem(key: String, oldItem: MetaItem<M>?, newItem: MetaItem<M>?) {
|
protected open fun replaceItem(key: NameToken, oldItem: MetaItem<M>?, newItem: MetaItem<M>?) {
|
||||||
if (newItem == null) {
|
if (newItem == null) {
|
||||||
_items.remove(key)
|
_items.remove(key)
|
||||||
oldItem?.nodes?.forEach {
|
oldItem?.node?.removeListener(this)
|
||||||
it.removeListener(this)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
_items[key] = newItem
|
_items[key] = newItem
|
||||||
newItem.nodes.forEach {
|
if(newItem is MetaItem.NodeItem) {
|
||||||
it.onChange(this) { name, oldItem, newItem ->
|
newItem.node.onChange(this) { name, oldChild, newChild ->
|
||||||
itemChanged(key.toName() + name, oldItem, newItem)
|
itemChanged(key + name, oldChild, newChild)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -79,14 +80,13 @@ abstract class MutableMetaNode<M : MutableMetaNode<M>> : MetaNode<M>(), MutableM
|
|||||||
0 -> error("Can't set meta item for empty name")
|
0 -> error("Can't set meta item for empty name")
|
||||||
1 -> {
|
1 -> {
|
||||||
val token = name.first()!!
|
val token = name.first()!!
|
||||||
if (token.hasQuery()) TODO("Queries are not supported in set operations on meta")
|
replaceItem(token, get(name), item)
|
||||||
replaceItem(token.body, get(name), item)
|
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val token = name.first()!!
|
val token = name.first()!!
|
||||||
//get existing or create new node. Query is ignored for new node
|
//get existing or create new node. Query is ignored for new node
|
||||||
val child = this.items[token.body]?.nodes?.get(token.query)
|
val child = this.items[token]?.node
|
||||||
?: empty().also { this[token.body.toName()] = MetaItem.SingleNodeItem(it) }
|
?: empty().also { this[token.body.toName()] = MetaItem.NodeItem(it) }
|
||||||
child[name.cutFirst()] = item
|
child[name.cutFirst()] = item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -99,13 +99,11 @@ fun <M : MutableMeta<M>> M.remove(name: Name) = set(name, null)
|
|||||||
fun <M : MutableMeta<M>> M.remove(name: String) = remove(name.toName())
|
fun <M : MutableMeta<M>> M.remove(name: String) = remove(name.toName())
|
||||||
|
|
||||||
operator fun <M : MutableMeta<M>> M.set(name: Name, value: Value) = set(name, MetaItem.ValueItem(value))
|
operator fun <M : MutableMeta<M>> M.set(name: Name, value: Value) = set(name, MetaItem.ValueItem(value))
|
||||||
operator fun <M : MutableMetaNode<M>> M.set(name: Name, meta: Meta) = set(name, MetaItem.SingleNodeItem(wrap(name, meta)))
|
operator fun <M : MutableMetaNode<M>> M.set(name: Name, meta: Meta) = set(name, MetaItem.NodeItem(wrap(name, meta)))
|
||||||
operator fun <M : MutableMetaNode<M>> M.set(name: Name, metas: List<Meta>) = set(name, MetaItem.MultiNodeItem(metas.map { wrap(name, it) }))
|
|
||||||
|
|
||||||
operator fun <M : MutableMeta<M>> M.set(name: String, item: MetaItem<M>) = set(name.toName(), item)
|
operator fun <M : MutableMeta<M>> M.set(name: String, item: MetaItem<M>) = set(name.toName(), item)
|
||||||
operator fun <M : MutableMeta<M>> M.set(name: String, value: Value) = set(name.toName(), MetaItem.ValueItem(value))
|
operator fun <M : MutableMeta<M>> M.set(name: String, value: Value) = set(name.toName(), MetaItem.ValueItem(value))
|
||||||
operator fun <M : MutableMetaNode<M>> M.set(name: String, meta: Meta) = set(name.toName(), meta)
|
operator fun <M : MutableMetaNode<M>> M.set(name: String, meta: Meta) = set(name.toName(), meta)
|
||||||
operator fun <M : MutableMetaNode<M>> M.set(name: String, metas: List<Meta>) = set(name.toName(), metas)
|
operator fun <M : MutableMeta<M>> M.set(token: NameToken, item: MetaItem<M>?) = set(token.toName(), item)
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -129,19 +127,23 @@ fun <M : MutableMetaNode<M>> M.update(meta: Meta) {
|
|||||||
meta.items.forEach { entry ->
|
meta.items.forEach { entry ->
|
||||||
val value = entry.value
|
val value = entry.value
|
||||||
when (value) {
|
when (value) {
|
||||||
is MetaItem.ValueItem -> this[entry.key] = value.value
|
is MetaItem.ValueItem -> this[entry.key.toName()] = value.value
|
||||||
is MetaItem.SingleNodeItem -> (this[entry.key] as? MetaItem.SingleNodeItem)
|
is MetaItem.NodeItem -> (this[entry.key.toName()] as? MetaItem.NodeItem)?.node?.update(value.node)
|
||||||
?.node?.update(value.node) ?: kotlin.run { this[entry.key] = value.node }
|
?: run { this[entry.key.toName()] = value.node }
|
||||||
is MetaItem.MultiNodeItem -> {
|
|
||||||
val existing = this[entry.key]
|
|
||||||
if (existing is MetaItem.MultiNodeItem && existing.nodes.size == value.nodes.size) {
|
|
||||||
existing.nodes.forEachIndexed { index, m ->
|
|
||||||
m.update(value.nodes[index])
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this[entry.key] = value.nodes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Same name siblings generation
|
||||||
|
|
||||||
|
fun <M : MutableMetaNode<M>> M.setIndexed(name: Name, metas: Iterable<Meta>, queryFactory: (Int) -> String = { it.toString() }) {
|
||||||
|
val tokens = name.tokens.toMutableList()
|
||||||
|
val last = tokens.last()
|
||||||
|
metas.forEachIndexed { index, meta ->
|
||||||
|
val indexedToken = NameToken(last.body, last.query + queryFactory(index))
|
||||||
|
tokens[tokens.lastIndex] = indexedToken
|
||||||
|
set(Name(tokens), meta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun <M : MutableMetaNode<M>> M.set(name: Name, metas: Iterable<Meta>) = setIndexed(name, metas)
|
||||||
|
@ -4,7 +4,7 @@ package hep.dataforge.meta
|
|||||||
* Marker interface for specifications
|
* Marker interface for specifications
|
||||||
*/
|
*/
|
||||||
interface Specification: Configurable{
|
interface Specification: Configurable{
|
||||||
operator fun get(name: String): MetaItem<Config>? = config.get(name)
|
operator fun get(name: String): MetaItem<Config>? = config[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package hep.dataforge.meta
|
package hep.dataforge.meta
|
||||||
|
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.NameToken
|
||||||
import hep.dataforge.names.toName
|
import hep.dataforge.names.toName
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -27,12 +28,11 @@ class StyledConfig(val config: Config, style: Meta = EmptyMeta) : Config() {
|
|||||||
when (item) {
|
when (item) {
|
||||||
null -> config.remove(name)
|
null -> config.remove(name)
|
||||||
is MetaItem.ValueItem -> config[name] = item.value
|
is MetaItem.ValueItem -> config[name] = item.value
|
||||||
is MetaItem.SingleNodeItem -> config[name] = item.node
|
is MetaItem.NodeItem -> config[name] = item.node
|
||||||
is MetaItem.MultiNodeItem -> config[name] = item.nodes
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override val items: Map<String, MetaItem<Config>>
|
override val items: Map<NameToken, MetaItem<Config>>
|
||||||
get() = (config.items.keys + style.items.keys).associate { key ->
|
get() = (config.items.keys + style.items.keys).associate { key ->
|
||||||
val value = config.items[key]
|
val value = config.items[key]
|
||||||
val styleValue = style[key]
|
val styleValue = style[key]
|
||||||
@ -40,16 +40,12 @@ class StyledConfig(val config: Config, style: Meta = EmptyMeta) : Config() {
|
|||||||
null -> when (styleValue) {
|
null -> when (styleValue) {
|
||||||
null -> error("Should be unreachable")
|
null -> error("Should be unreachable")
|
||||||
is MetaItem.ValueItem -> MetaItem.ValueItem(styleValue.value)
|
is MetaItem.ValueItem -> MetaItem.ValueItem(styleValue.value)
|
||||||
is MetaItem.SingleNodeItem -> MetaItem.SingleNodeItem<Config>(StyledConfig(config.empty(), styleValue.node))
|
is MetaItem.NodeItem -> MetaItem.NodeItem<Config>(StyledConfig(config.empty(), styleValue.node))
|
||||||
is MetaItem.MultiNodeItem -> MetaItem.MultiNodeItem<Config>(styleValue.nodes.map { StyledConfig(config.empty(), it) })
|
|
||||||
}
|
}
|
||||||
is MetaItem.ValueItem -> MetaItem.ValueItem(value.value)
|
is MetaItem.ValueItem -> MetaItem.ValueItem(value.value)
|
||||||
is MetaItem.SingleNodeItem -> MetaItem.SingleNodeItem(
|
is MetaItem.NodeItem -> MetaItem.NodeItem(
|
||||||
StyledConfig(value.node, styleValue?.node ?: EmptyMeta)
|
StyledConfig(value.node, styleValue?.node ?: EmptyMeta)
|
||||||
)
|
)
|
||||||
is MetaItem.MultiNodeItem -> MetaItem.MultiNodeItem(value.nodes.map {
|
|
||||||
StyledConfig(it, styleValue?.node ?: EmptyMeta)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
key to item
|
key to item
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ class Name internal constructor(val tokens: List<NameToken>) {
|
|||||||
* Following symbols are prohibited in name tokens: `{}.:\`.
|
* Following symbols are prohibited in name tokens: `{}.:\`.
|
||||||
* A name token could have appendix in square brackets called *query*
|
* A name token could have appendix in square brackets called *query*
|
||||||
*/
|
*/
|
||||||
data class NameToken internal constructor(val body: String, val query: String) {
|
data class NameToken(val body: String, val query: String = "") {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (body.isEmpty()) error("Syntax error: Name token body is empty")
|
if (body.isEmpty()) error("Syntax error: Name token body is empty")
|
||||||
@ -108,6 +108,8 @@ fun String.toName(): Name {
|
|||||||
return Name(tokens.toList())
|
return Name(tokens.toList())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
operator fun NameToken.plus(other: Name): Name = Name(listOf(this) + other.tokens)
|
||||||
|
|
||||||
operator fun Name.plus(other: Name): Name = Name(this.tokens + other.tokens)
|
operator fun Name.plus(other: Name): Name = Name(this.tokens + other.tokens)
|
||||||
|
|
||||||
operator fun Name.plus(other: String): Name = this + other.toName()
|
operator fun Name.plus(other: String): Name = this + other.toName()
|
||||||
|
@ -17,7 +17,6 @@ pluginManagement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
maven { url = 'http://dl.bintray.com/kotlin/kotlin-eap' }
|
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven { url = 'https://plugins.gradle.org/m2/' }
|
maven { url = 'https://plugins.gradle.org/m2/' }
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user