Refactor Name and NameToken. Breaking change
This commit is contained in:
parent
57b263ec63
commit
6ad5f162a1
@ -89,8 +89,8 @@ val <T : Any> DataItem<T>?.data: Data<T>? get() = (this as? DataItem.Leaf<T>)?.d
|
|||||||
|
|
||||||
operator fun <T : Any> DataNode<T>.get(name: Name): DataItem<T>? = when (name.length) {
|
operator fun <T : Any> DataNode<T>.get(name: Name): DataItem<T>? = when (name.length) {
|
||||||
0 -> error("Empty name")
|
0 -> error("Empty name")
|
||||||
1 -> items[name.first()]
|
1 -> items[name.firstOrNull()]
|
||||||
else -> get(name.first()!!.asName()).node?.get(name.cutFirst())
|
else -> get(name.firstOrNull()!!.asName()).node?.get(name.cutFirst())
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun <T : Any> DataNode<T>.get(name: String): DataItem<T>? = get(name.toName())
|
operator fun <T : Any> DataNode<T>.get(name: String): DataItem<T>? = get(name.toName())
|
||||||
@ -168,24 +168,24 @@ class DataTreeBuilder<T : Any>(val type: KClass<out T>) {
|
|||||||
private fun buildNode(name: Name): DataTreeBuilder<T> {
|
private fun buildNode(name: Name): DataTreeBuilder<T> {
|
||||||
return when (name.length) {
|
return when (name.length) {
|
||||||
0 -> this
|
0 -> this
|
||||||
1 -> buildNode(name.first()!!)
|
1 -> buildNode(name.firstOrNull()!!)
|
||||||
else -> buildNode(name.first()!!).buildNode(name.cutFirst())
|
else -> buildNode(name.firstOrNull()!!).buildNode(name.cutFirst())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun set(name: Name, data: Data<T>) {
|
operator fun set(name: Name, data: Data<T>) {
|
||||||
when (name.length) {
|
when (name.length) {
|
||||||
0 -> error("Can't add data with empty name")
|
0 -> error("Can't add data with empty name")
|
||||||
1 -> set(name.first()!!, data)
|
1 -> set(name.firstOrNull()!!, data)
|
||||||
2 -> buildNode(name.cutLast())[name.last()!!] = data
|
2 -> buildNode(name.cutLast())[name.lastOrNull()!!] = data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun set(name: Name, node: DataTreeBuilder<out T>) {
|
operator fun set(name: Name, node: DataTreeBuilder<out T>) {
|
||||||
when (name.length) {
|
when (name.length) {
|
||||||
0 -> error("Can't add data with empty name")
|
0 -> error("Can't add data with empty name")
|
||||||
1 -> set(name.first()!!, node)
|
1 -> set(name.firstOrNull()!!, node)
|
||||||
2 -> buildNode(name.cutLast())[name.last()!!] = node
|
2 -> buildNode(name.cutLast())[name.lastOrNull()!!] = node
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@ public interface Meta : MetaRepr, ItemProvider {
|
|||||||
|
|
||||||
override fun getItem(name: Name): MetaItem<*>? {
|
override fun getItem(name: Name): MetaItem<*>? {
|
||||||
if (name.isEmpty()) return NodeItem(this)
|
if (name.isEmpty()) return NodeItem(this)
|
||||||
return name.first()?.let { token ->
|
return name.firstOrNull()?.let { token ->
|
||||||
val tail = name.cutFirst()
|
val tail = name.cutFirst()
|
||||||
when (tail.length) {
|
when (tail.length) {
|
||||||
0 -> items[token]
|
0 -> items[token]
|
||||||
|
@ -56,12 +56,12 @@ abstract class AbstractMutableMeta<M : MutableMeta<M>> : AbstractMetaNode<M>(),
|
|||||||
when (name.length) {
|
when (name.length) {
|
||||||
0 -> error("Can't setValue meta item for empty name")
|
0 -> error("Can't setValue meta item for empty name")
|
||||||
1 -> {
|
1 -> {
|
||||||
val token = name.first()!!
|
val token = name.firstOrNull()!!
|
||||||
@Suppress("UNCHECKED_CAST") val oldItem: MetaItem<M>? = get(name) as? MetaItem<M>
|
@Suppress("UNCHECKED_CAST") val oldItem: MetaItem<M>? = get(name) as? MetaItem<M>
|
||||||
replaceItem(token, oldItem, wrapItem(item))
|
replaceItem(token, oldItem, wrapItem(item))
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val token = name.first()!!
|
val token = name.firstOrNull()!!
|
||||||
//get existing or create new node. Query is ignored for new node
|
//get existing or create new node. Query is ignored for new node
|
||||||
if (items[token] == null) {
|
if (items[token] == null) {
|
||||||
replaceItem(token, null, MetaItem.NodeItem(empty()))
|
replaceItem(token, null, MetaItem.NodeItem(empty()))
|
||||||
@ -159,7 +159,7 @@ operator fun MutableMeta<*>.set(name: String, metas: Iterable<Meta>): Unit = set
|
|||||||
*/
|
*/
|
||||||
fun <M : MutableMeta<M>> M.append(name: Name, value: Any?) {
|
fun <M : MutableMeta<M>> M.append(name: Name, value: Any?) {
|
||||||
require(!name.isEmpty()) { "Name could not be empty for append operation" }
|
require(!name.isEmpty()) { "Name could not be empty for append operation" }
|
||||||
val newIndex = name.last()!!.index
|
val newIndex = name.lastOrNull()!!.index
|
||||||
if (newIndex != null) {
|
if (newIndex != null) {
|
||||||
set(name, value)
|
set(name, value)
|
||||||
} else {
|
} else {
|
||||||
|
@ -141,7 +141,7 @@ class NodeDescriptor(config: Config = Config()) : ItemDescriptor(config) {
|
|||||||
}
|
}
|
||||||
NodeDescriptor(config)
|
NodeDescriptor(config)
|
||||||
}
|
}
|
||||||
else -> buildNode(name.first()?.asName()!!).buildNode(name.cutFirst())
|
else -> buildNode(name.firstOrNull()?.asName()!!).buildNode(name.cutFirst())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,7 +155,7 @@ class NodeDescriptor(config: Config = Config()) : ItemDescriptor(config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun item(name: Name, descriptor: ItemDescriptor) {
|
fun item(name: Name, descriptor: ItemDescriptor) {
|
||||||
buildNode(name.cutLast()).newItem(name.last().toString(), descriptor)
|
buildNode(name.cutLast()).newItem(name.lastOrNull().toString(), descriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun item(name: String, descriptor: ItemDescriptor) {
|
fun item(name: String, descriptor: ItemDescriptor) {
|
||||||
@ -199,7 +199,7 @@ operator fun ItemDescriptor.get(name: Name): ItemDescriptor? {
|
|||||||
if (name.isEmpty()) return this
|
if (name.isEmpty()) return this
|
||||||
return when (this) {
|
return when (this) {
|
||||||
is ValueDescriptor -> null // empty name already checked
|
is ValueDescriptor -> null // empty name already checked
|
||||||
is NodeDescriptor -> items[name.first()!!.toString()]?.get(name.cutFirst())
|
is NodeDescriptor -> items[name.firstOrNull()!!.toString()]?.get(name.cutFirst())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package hep.dataforge.meta
|
package hep.dataforge.meta
|
||||||
|
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.*
|
||||||
import hep.dataforge.names.toName
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all items matching given name. The index of the last element, if present is used as a [Regex],
|
* Get all items matching given name. The index of the last element, if present is used as a [Regex],
|
||||||
@ -14,7 +13,7 @@ fun Meta.getIndexed(name: Name): Map<String?, MetaItem<*>> {
|
|||||||
else -> this[name.cutLast()].node ?: return emptyMap()
|
else -> this[name.cutLast()].node ?: return emptyMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
val (body, index) = name.last()!!
|
val (body, index) = name.lastOrNull()!!
|
||||||
return if (index == null) {
|
return if (index == null) {
|
||||||
root.items.filter { it.key.body == body }.mapKeys { it.key.index }
|
root.items.filter { it.key.body == body }.mapKeys { it.key.index }
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package hep.dataforge.names
|
package hep.dataforge.names
|
||||||
|
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.KSerializer
|
import kotlinx.serialization.KSerializer
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.Serializer
|
import kotlinx.serialization.Serializer
|
||||||
@ -16,31 +17,8 @@ import kotlinx.serialization.encoding.Encoder
|
|||||||
* Each token could contain additional index in square brackets.
|
* Each token could contain additional index in square brackets.
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
class Name(val tokens: List<NameToken>) {
|
public class Name(public val tokens: List<NameToken>) {
|
||||||
|
//TODO to be transformed into inline class after they are supported with serialization
|
||||||
val length get() = tokens.size
|
|
||||||
|
|
||||||
/**
|
|
||||||
* First token of the name or null if it is empty
|
|
||||||
*/
|
|
||||||
fun first(): NameToken? = tokens.firstOrNull()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Last token of the name or null if it is empty
|
|
||||||
*/
|
|
||||||
fun last(): NameToken? = tokens.lastOrNull()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The reminder of the name after first element is cut. For empty name return itself.
|
|
||||||
*/
|
|
||||||
fun cutFirst(): Name = Name(tokens.drop(1))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The reminder of the name after last element is cut. For empty name return itself.
|
|
||||||
*/
|
|
||||||
fun cutLast(): Name = Name(tokens.dropLast(1))
|
|
||||||
|
|
||||||
operator fun get(i: Int): NameToken = tokens[i]
|
|
||||||
|
|
||||||
override fun toString(): String = tokens.joinToString(separator = NAME_SEPARATOR) { it.toString() }
|
override fun toString(): String = tokens.joinToString(separator = NAME_SEPARATOR) { it.toString() }
|
||||||
|
|
||||||
@ -60,11 +38,12 @@ class Name(val tokens: List<NameToken>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
@Serializer(Name::class)
|
@Serializer(Name::class)
|
||||||
companion object : KSerializer<Name> {
|
public companion object : KSerializer<Name> {
|
||||||
const val NAME_SEPARATOR = "."
|
public const val NAME_SEPARATOR: String = "."
|
||||||
|
|
||||||
val EMPTY = Name(emptyList())
|
public val EMPTY: Name = Name(emptyList())
|
||||||
|
|
||||||
override val descriptor: SerialDescriptor =
|
override val descriptor: SerialDescriptor =
|
||||||
PrimitiveSerialDescriptor("hep.dataforge.names.Name", PrimitiveKind.STRING)
|
PrimitiveSerialDescriptor("hep.dataforge.names.Name", PrimitiveKind.STRING)
|
||||||
@ -79,13 +58,37 @@ class Name(val tokens: List<NameToken>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public operator fun Name.get(i: Int): NameToken = tokens[i]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The reminder of the name after last element is cut. For empty name return itself.
|
||||||
|
*/
|
||||||
|
public fun Name.cutLast(): Name = Name(tokens.dropLast(1))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The reminder of the name after first element is cut. For empty name return itself.
|
||||||
|
*/
|
||||||
|
public fun Name.cutFirst(): Name = Name(tokens.drop(1))
|
||||||
|
|
||||||
|
public val Name.length: Int get() = tokens.size
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Last token of the name or null if it is empty
|
||||||
|
*/
|
||||||
|
public fun Name.lastOrNull(): NameToken? = tokens.lastOrNull()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* First token of the name or null if it is empty
|
||||||
|
*/
|
||||||
|
public fun Name.firstOrNull(): NameToken? = tokens.firstOrNull()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A single name token. Body is not allowed to be empty.
|
* A single name token. Body is not allowed to be empty.
|
||||||
* Following symbols are prohibited in name tokens: `{}.:\`.
|
* Following symbols are prohibited in name tokens: `{}.:\`.
|
||||||
* A name token could have appendix in square brackets called *index*
|
* A name token could have appendix in square brackets called *index*
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
data class NameToken(val body: String, val index: String? = null) {
|
public data class NameToken(val body: String, val index: String? = null) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (body.isEmpty()) error("Syntax error: Name token body is empty")
|
if (body.isEmpty()) error("Syntax error: Name token body is empty")
|
||||||
@ -103,15 +106,14 @@ data class NameToken(val body: String, val index: String? = null) {
|
|||||||
body.escape()
|
body.escape()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasIndex() = index != null
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
|
|
||||||
@Serializer(NameToken::class)
|
@Serializer(NameToken::class)
|
||||||
companion object : KSerializer<NameToken> {
|
public companion object : KSerializer<NameToken> {
|
||||||
override val descriptor: SerialDescriptor =
|
override val descriptor: SerialDescriptor =
|
||||||
PrimitiveSerialDescriptor("hep.dataforge.names.NameToken", PrimitiveKind.STRING)
|
PrimitiveSerialDescriptor("hep.dataforge.names.NameToken", PrimitiveKind.STRING)
|
||||||
|
|
||||||
override fun deserialize(decoder: Decoder): NameToken {
|
override fun deserialize(decoder: Decoder): NameToken {
|
||||||
return decoder.decodeString().toName().first()!!
|
return decoder.decodeString().toName().firstOrNull()!!
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, value: NameToken) {
|
override fun serialize(encoder: Encoder, value: NameToken) {
|
||||||
@ -120,13 +122,21 @@ data class NameToken(val body: String, val index: String? = null) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun NameToken.withIndex(newIndex: String) = NameToken(body, newIndex)
|
/**
|
||||||
|
* Check if index is defined for this token
|
||||||
|
*/
|
||||||
|
public fun NameToken.hasIndex(): Boolean = index != null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add or replace index part of this token
|
||||||
|
*/
|
||||||
|
public fun NameToken.withIndex(newIndex: String): NameToken = NameToken(body, newIndex)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a [String] to name parsing it and extracting name tokens and index syntax.
|
* Convert a [String] to name parsing it and extracting name tokens and index syntax.
|
||||||
* This operation is rather heavy so it should be used with care in high performance code.
|
* This operation is rather heavy so it should be used with care in high performance code.
|
||||||
*/
|
*/
|
||||||
fun String.toName(): Name {
|
public fun String.toName(): Name {
|
||||||
if (isBlank()) return Name.EMPTY
|
if (isBlank()) return Name.EMPTY
|
||||||
val tokens = sequence {
|
val tokens = sequence {
|
||||||
var bodyBuilder = StringBuilder()
|
var bodyBuilder = StringBuilder()
|
||||||
@ -181,26 +191,26 @@ fun String.toName(): Name {
|
|||||||
* Convert the [String] to a [Name] by simply wrapping it in a single name token without parsing.
|
* Convert the [String] to a [Name] by simply wrapping it in a single name token without parsing.
|
||||||
* The input string could contain dots and braces, but they are just escaped, not parsed.
|
* The input string could contain dots and braces, but they are just escaped, not parsed.
|
||||||
*/
|
*/
|
||||||
fun String.asName(): Name = if (isBlank()) Name.EMPTY else NameToken(this).asName()
|
public fun String.asName(): Name = if (isBlank()) Name.EMPTY else NameToken(this).asName()
|
||||||
|
|
||||||
operator fun NameToken.plus(other: Name): Name = Name(listOf(this) + other.tokens)
|
public operator fun NameToken.plus(other: Name): Name = Name(listOf(this) + other.tokens)
|
||||||
|
|
||||||
operator fun Name.plus(other: Name): Name = Name(this.tokens + other.tokens)
|
public operator fun Name.plus(other: Name): Name = Name(this.tokens + other.tokens)
|
||||||
|
|
||||||
operator fun Name.plus(other: String): Name = this + other.toName()
|
public operator fun Name.plus(other: String): Name = this + other.toName()
|
||||||
|
|
||||||
operator fun Name.plus(other: NameToken): Name = Name(tokens + other)
|
public operator fun Name.plus(other: NameToken): Name = Name(tokens + other)
|
||||||
|
|
||||||
fun Name.appendLeft(other: String): Name = NameToken(other) + this
|
public fun Name.appendLeft(other: String): Name = NameToken(other) + this
|
||||||
|
|
||||||
fun NameToken.asName() = Name(listOf(this))
|
public fun NameToken.asName(): Name = Name(listOf(this))
|
||||||
|
|
||||||
fun Name.isEmpty(): Boolean = this.length == 0
|
public fun Name.isEmpty(): Boolean = this.length == 0
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set or replace last token index
|
* Set or replace last token index
|
||||||
*/
|
*/
|
||||||
fun Name.withIndex(index: String): Name {
|
public fun Name.withIndex(index: String): Name {
|
||||||
val last = NameToken(tokens.last().body, index)
|
val last = NameToken(tokens.last().body, index)
|
||||||
if (length == 0) error("Can't add index to empty name")
|
if (length == 0) error("Can't add index to empty name")
|
||||||
if (length == 1) {
|
if (length == 1) {
|
||||||
@ -215,19 +225,19 @@ fun Name.withIndex(index: String): Name {
|
|||||||
/**
|
/**
|
||||||
* Fast [String]-based accessor for item map
|
* Fast [String]-based accessor for item map
|
||||||
*/
|
*/
|
||||||
operator fun <T> Map<NameToken, T>.get(body: String, query: String? = null): T? = get(NameToken(body, query))
|
public operator fun <T> Map<NameToken, T>.get(body: String, query: String? = null): T? = get(NameToken(body, query))
|
||||||
|
|
||||||
operator fun <T> Map<Name, T>.get(name: String) = get(name.toName())
|
public operator fun <T> Map<Name, T>.get(name: String): T? = get(name.toName())
|
||||||
operator fun <T> MutableMap<Name, T>.set(name: String, value: T) = set(name.toName(), value)
|
public operator fun <T> MutableMap<Name, T>.set(name: String, value: T): Unit = set(name.toName(), value)
|
||||||
|
|
||||||
/* Name comparison operations */
|
/* Name comparison operations */
|
||||||
|
|
||||||
fun Name.startsWith(token: NameToken): Boolean = first() == token
|
public fun Name.startsWith(token: NameToken): Boolean = firstOrNull() == token
|
||||||
|
|
||||||
fun Name.endsWith(token: NameToken): Boolean = last() == token
|
public fun Name.endsWith(token: NameToken): Boolean = lastOrNull() == token
|
||||||
|
|
||||||
fun Name.startsWith(name: Name): Boolean =
|
public fun Name.startsWith(name: Name): Boolean =
|
||||||
this.length >= name.length && tokens.subList(0, name.length) == name.tokens
|
this.length >= name.length && tokens.subList(0, name.length) == name.tokens
|
||||||
|
|
||||||
fun Name.endsWith(name: Name): Boolean =
|
public fun Name.endsWith(name: Name): Boolean =
|
||||||
this.length >= name.length && tokens.subList(length - name.length, length) == name.tokens
|
this.length >= name.length && tokens.subList(length - name.length, length) == name.tokens
|
Loading…
Reference in New Issue
Block a user