Moved to K1.3.

Removed MultiNodeItems and replaced same name siblings by queries.
Laminate merge.
This commit is contained in:
Alexander Nozik 2018-11-02 15:11:42 +03:00
parent a7a1f776a7
commit 3033cc6304
15 changed files with 193 additions and 166 deletions

View File

@ -1,12 +1,10 @@
buildscript {
ext.kotlin_version = '1.3.0-rc-190'
ext.serialization_version = '0.8.3-rc13'
ext.kotlinx_io_version = '0.1.0-alpha-24-rc13'
ext.kotlin_version = '1.3.0'
ext.serialization_version = '0.9.0'
ext.kotlinx_io_version = '0.1.0-beta-1'
repositories {
jcenter()
maven {
url = "http://dl.bintray.com/kotlin/kotlin-eap"
}
}
dependencies {

View File

@ -2,10 +2,11 @@ plugins {
id 'kotlin-multiplatform'
//id 'kotlinx-serialization'
}
repositories {
maven { url = 'http://dl.bintray.com/kotlin/kotlin-eap' }
mavenCentral()
jcenter()
}
kotlin {
targets {
fromPreset(presets.jvm, 'jvm')

View File

@ -54,7 +54,7 @@ object BinaryMetaFormat : MetaFormat {
}
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())
@ -115,21 +115,14 @@ object BinaryMetaFormat : MetaFormat {
writeChar('M')
writeInt(meta.items.size)
meta.items.forEach { (key, item) ->
writeString(key)
writeString(key.toString())
when (item) {
is MetaItem.ValueItem -> {
writeValue(item.value)
}
is MetaItem.SingleNodeItem -> {
is MetaItem.NodeItem -> {
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)
}
}
MetaItem.SingleNodeItem(meta)
}
'#' -> {
val length = readInt()
val nodes = (1..length).map { (readMetaItem() as MetaItem.SingleNodeItem).node }
MetaItem.MultiNodeItem(nodes)
MetaItem.NodeItem(meta)
}
else -> error("Unknown serialization key character: $keyChar")
}

View File

@ -3,13 +3,14 @@ package hep.dataforge.meta.io
import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaItem
import hep.dataforge.meta.Value
import hep.dataforge.names.NameToken
/**
* Represent any js object as meta
*/
class JSMeta(val obj: Any) : Meta {
override val items: Map<String, MetaItem<out Meta>>
get() = listKeys(obj).associateWith { convert(js("obj[it]")) }
override val items: Map<NameToken, MetaItem<out Meta>>
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>
@ -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))
isList(obj) -> {
val list = obj as List<*>
//if first value is primitive, treat as value
if (isPrimitive(list.first())) {
MetaItem.ValueItem<JSMeta>(Value.of(list))
} else {
//else treat as meta list
MetaItem.MultiNodeItem(list.map { JSMeta(it!!) })
}
MetaItem.ValueItem<JSMeta>(Value.of(list))
}
else -> MetaItem.SingleNodeItem(JSMeta(obj))
else -> MetaItem.NodeItem(JSMeta(obj))
}
}
}

View File

@ -34,13 +34,8 @@ private fun Meta.toJson(): JsonObject {
val builder = JsonObject()
items.forEach { name, item ->
when (item) {
is MetaItem.ValueItem -> builder[name] = item.value.toJson()
is MetaItem.SingleNodeItem -> builder[name] = item.node.toJson()
is MetaItem.MultiNodeItem -> {
val array = JsonArray()
item.nodes.forEach { array.add(it.toJson()) }
builder[name] = array
}
is MetaItem.ValueItem -> builder[name.toString()] = item.value.toJson()
is MetaItem.NodeItem -> builder[name.toString()] = item.node.toJson()
}
}
return builder

View File

@ -1,10 +1,11 @@
plugins {
id 'kotlin-multiplatform'
}
repositories {
maven { url = 'http://dl.bintray.com/kotlin/kotlin-eap' }
mavenCentral()
jcenter()
}
kotlin {
targets {
fromPreset(presets.jvm, 'jvm')

View File

@ -22,8 +22,7 @@ fun Meta.toConfig(): Config = this as? Config ?: Config().also { builder ->
val item = entry.value
builder[entry.key] = when (item) {
is MetaItem.ValueItem -> MetaItem.ValueItem(item.value)
is MetaItem.SingleNodeItem -> MetaItem.SingleNodeItem(item.node.toConfig())
is MetaItem.MultiNodeItem -> MetaItem.MultiNodeItem(item.nodes.map { it.toConfig() })
is MetaItem.NodeItem -> MetaItem.NodeItem(item.node.toConfig())
}
}
}

View File

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

View File

@ -1,47 +1,57 @@
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.NameToken
import hep.dataforge.names.toName
/**
* A member of the meta tree. Could be represented as one of following:
* * a value
* * a single node
* * a list of nodes
* * a [ValueItem] (leaf)
* * a [NodeItem] (node)
*/
sealed class MetaItem<M : Meta> {
data class ValueItem<M : Meta>(val value: Value) : MetaItem<M>()
data class SingleNodeItem<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())
}
data class NodeItem<M : Meta>(val node: M) : MetaItem<M>()
}
/**
* Generic meta tree representation. Elements are [MetaItem] objects that could be represented by three different entities:
* * [MetaItem.ValueItem] (leaf)
* * [MetaItem.SingleNodeItem] single node
* * [MetaItem.MultiNodeItem] multi-value node
* * [MetaItem.NodeItem] single node
*
* * Same name siblings are supported via elements with the same [Name] but different queries
*/
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>? {
return when (name.length) {
0 -> error("Can't resolve element from empty name")
1 -> items[name.first()!!.body]
else -> name.first()!!.let { token -> items[token.body]?.nodes?.get(token.query) }?.get(name.cutFirst())
companion object {
/**
* A key for single value node
*/
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
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
*/
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>? {
return when (name.length) {
0 -> error("Can't resolve element from empty name")
1 -> items[name.first()!!.body]
else -> name.first()!!.let { token -> items[token.body]?.nodes?.get(token.query) }?.get(name.cutFirst())
return name.first()?.let { token ->
val tail = name.cutFirst()
when (tail.length) {
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
*/
class SealedMeta internal constructor(override val items: Map<String, 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)
}
}
}
class SealedMeta internal constructor(override val items: Map<NameToken, MetaItem<SealedMeta>>) : MetaNode<SealedMeta>()
/**
* 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 {
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
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<*>.boolean get() = value.boolean
val MetaItem<*>.number get() = value.number
val MetaItem<*>.double get() = number.toDouble()
val MetaItem<*>.int get() = number.toInt()
val MetaItem<*>.long get() = number.toLong()
val MetaItem<*>.short get() = number.toShort()
val <M : Meta> MetaItem<M>.node: M
get() = when (this) {
is MetaItem.ValueItem -> error("Trying to interpret value meta item as node item")
is MetaItem.SingleNodeItem -> node
is MetaItem.MultiNodeItem -> nodes.first()
is MetaItem.NodeItem -> node
}
/**
* 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
*/

View File

@ -37,9 +37,8 @@ fun Meta.builder(): MetaBuilder {
items.mapValues { entry ->
val item = entry.value
builder[entry.key] = when (item) {
is MetaItem.ValueItem -> MetaItem.ValueItem(item.value)
is MetaItem.SingleNodeItem -> MetaItem.SingleNodeItem(item.node.builder())
is MetaItem.MultiNodeItem -> MetaItem.MultiNodeItem(item.nodes.map { it.builder() })
is MetaItem.ValueItem -> MetaItem.ValueItem<MetaBuilder>(item.value)
is MetaItem.NodeItem -> MetaItem.NodeItem(item.node.builder())
}
}
}

View File

@ -1,6 +1,7 @@
package hep.dataforge.meta
import hep.dataforge.names.Name
import hep.dataforge.names.NameToken
import hep.dataforge.names.plus
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 {
override val items: Map<String, MetaItem<M>>
override val items: Map<NameToken, MetaItem<M>>
operator fun set(name: Name, item: MetaItem<M>?)
fun onChange(owner: Any? = null, action: (Name, MetaItem<*>?, MetaItem<*>?) -> Unit)
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> {
private val listeners = HashSet<MetaListener>()
@ -36,26 +39,24 @@ abstract class MutableMetaNode<M : MutableMetaNode<M>> : MetaNode<M>(), MutableM
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
protected fun itemChanged(name: Name, oldItem: MetaItem<*>?, newItem: MetaItem<*>?) {
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) {
_items.remove(key)
oldItem?.nodes?.forEach {
it.removeListener(this)
}
oldItem?.node?.removeListener(this)
} else {
_items[key] = newItem
newItem.nodes.forEach {
it.onChange(this) { name, oldItem, newItem ->
itemChanged(key.toName() + name, oldItem, newItem)
if(newItem is MetaItem.NodeItem) {
newItem.node.onChange(this) { name, oldChild, newChild ->
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")
1 -> {
val token = name.first()!!
if (token.hasQuery()) TODO("Queries are not supported in set operations on meta")
replaceItem(token.body, get(name), item)
replaceItem(token, get(name), item)
}
else -> {
val token = name.first()!!
//get existing or create new node. Query is ignored for new node
val child = this.items[token.body]?.nodes?.get(token.query)
?: empty().also { this[token.body.toName()] = MetaItem.SingleNodeItem(it) }
val child = this.items[token]?.node
?: empty().also { this[token.body.toName()] = MetaItem.NodeItem(it) }
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())
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, metas: List<Meta>) = set(name, MetaItem.MultiNodeItem(metas.map { wrap(name, it) }))
operator fun <M : MutableMetaNode<M>> M.set(name: Name, meta: Meta) = set(name, MetaItem.NodeItem(wrap(name, meta)))
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 : 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 ->
val value = entry.value
when (value) {
is MetaItem.ValueItem -> this[entry.key] = value.value
is MetaItem.SingleNodeItem -> (this[entry.key] as? MetaItem.SingleNodeItem)
?.node?.update(value.node) ?: kotlin.run { this[entry.key] = 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
}
}
is MetaItem.ValueItem -> this[entry.key.toName()] = value.value
is MetaItem.NodeItem -> (this[entry.key.toName()] as? MetaItem.NodeItem)?.node?.update(value.node)
?: run { this[entry.key.toName()] = value.node }
}
}
}
}
// 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)

View File

@ -4,7 +4,7 @@ package hep.dataforge.meta
* Marker interface for specifications
*/
interface Specification: Configurable{
operator fun get(name: String): MetaItem<Config>? = config.get(name)
operator fun get(name: String): MetaItem<Config>? = config[name]
}
/**

View File

@ -1,6 +1,7 @@
package hep.dataforge.meta
import hep.dataforge.names.Name
import hep.dataforge.names.NameToken
import hep.dataforge.names.toName
/**
@ -27,12 +28,11 @@ class StyledConfig(val config: Config, style: Meta = EmptyMeta) : Config() {
when (item) {
null -> config.remove(name)
is MetaItem.ValueItem -> config[name] = item.value
is MetaItem.SingleNodeItem -> config[name] = item.node
is MetaItem.MultiNodeItem -> config[name] = item.nodes
is MetaItem.NodeItem -> config[name] = item.node
}
}
override val items: Map<String, MetaItem<Config>>
override val items: Map<NameToken, MetaItem<Config>>
get() = (config.items.keys + style.items.keys).associate { key ->
val value = config.items[key]
val styleValue = style[key]
@ -40,16 +40,12 @@ class StyledConfig(val config: Config, style: Meta = EmptyMeta) : Config() {
null -> when (styleValue) {
null -> error("Should be unreachable")
is MetaItem.ValueItem -> MetaItem.ValueItem(styleValue.value)
is MetaItem.SingleNodeItem -> MetaItem.SingleNodeItem<Config>(StyledConfig(config.empty(), styleValue.node))
is MetaItem.MultiNodeItem -> MetaItem.MultiNodeItem<Config>(styleValue.nodes.map { StyledConfig(config.empty(), it) })
is MetaItem.NodeItem -> MetaItem.NodeItem<Config>(StyledConfig(config.empty(), styleValue.node))
}
is MetaItem.ValueItem -> MetaItem.ValueItem(value.value)
is MetaItem.SingleNodeItem -> MetaItem.SingleNodeItem(
is MetaItem.NodeItem -> MetaItem.NodeItem(
StyledConfig(value.node, styleValue?.node ?: EmptyMeta)
)
is MetaItem.MultiNodeItem -> MetaItem.MultiNodeItem(value.nodes.map {
StyledConfig(it, styleValue?.node ?: EmptyMeta)
})
}
key to item
}

View File

@ -58,7 +58,7 @@ class Name internal constructor(val tokens: List<NameToken>) {
* Following symbols are prohibited in name tokens: `{}.:\`.
* 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 {
if (body.isEmpty()) error("Syntax error: Name token body is empty")
@ -108,6 +108,8 @@ fun String.toName(): Name {
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: String): Name = this + other.toName()

View File

@ -17,7 +17,6 @@ pluginManagement {
}
repositories {
maven { url = 'http://dl.bintray.com/kotlin/kotlin-eap' }
mavenCentral()
maven { url = 'https://plugins.gradle.org/m2/' }
}