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) {
|
||||
0 -> error("Empty name")
|
||||
1 -> items[name.first()]
|
||||
else -> get(name.first()!!.asName()).node?.get(name.cutFirst())
|
||||
1 -> items[name.firstOrNull()]
|
||||
else -> get(name.firstOrNull()!!.asName()).node?.get(name.cutFirst())
|
||||
}
|
||||
|
||||
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> {
|
||||
return when (name.length) {
|
||||
0 -> this
|
||||
1 -> buildNode(name.first()!!)
|
||||
else -> buildNode(name.first()!!).buildNode(name.cutFirst())
|
||||
1 -> buildNode(name.firstOrNull()!!)
|
||||
else -> buildNode(name.firstOrNull()!!).buildNode(name.cutFirst())
|
||||
}
|
||||
}
|
||||
|
||||
operator fun set(name: Name, data: Data<T>) {
|
||||
when (name.length) {
|
||||
0 -> error("Can't add data with empty name")
|
||||
1 -> set(name.first()!!, data)
|
||||
2 -> buildNode(name.cutLast())[name.last()!!] = data
|
||||
1 -> set(name.firstOrNull()!!, data)
|
||||
2 -> buildNode(name.cutLast())[name.lastOrNull()!!] = data
|
||||
}
|
||||
}
|
||||
|
||||
operator fun set(name: Name, node: DataTreeBuilder<out T>) {
|
||||
when (name.length) {
|
||||
0 -> error("Can't add data with empty name")
|
||||
1 -> set(name.first()!!, node)
|
||||
2 -> buildNode(name.cutLast())[name.last()!!] = node
|
||||
1 -> set(name.firstOrNull()!!, node)
|
||||
2 -> buildNode(name.cutLast())[name.lastOrNull()!!] = node
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,7 +115,7 @@ public interface Meta : MetaRepr, ItemProvider {
|
||||
|
||||
override fun getItem(name: Name): MetaItem<*>? {
|
||||
if (name.isEmpty()) return NodeItem(this)
|
||||
return name.first()?.let { token ->
|
||||
return name.firstOrNull()?.let { token ->
|
||||
val tail = name.cutFirst()
|
||||
when (tail.length) {
|
||||
0 -> items[token]
|
||||
|
@ -56,12 +56,12 @@ abstract class AbstractMutableMeta<M : MutableMeta<M>> : AbstractMetaNode<M>(),
|
||||
when (name.length) {
|
||||
0 -> error("Can't setValue meta item for empty name")
|
||||
1 -> {
|
||||
val token = name.first()!!
|
||||
val token = name.firstOrNull()!!
|
||||
@Suppress("UNCHECKED_CAST") val oldItem: MetaItem<M>? = get(name) as? MetaItem<M>
|
||||
replaceItem(token, oldItem, wrapItem(item))
|
||||
}
|
||||
else -> {
|
||||
val token = name.first()!!
|
||||
val token = name.firstOrNull()!!
|
||||
//get existing or create new node. Query is ignored for new node
|
||||
if (items[token] == null) {
|
||||
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?) {
|
||||
require(!name.isEmpty()) { "Name could not be empty for append operation" }
|
||||
val newIndex = name.last()!!.index
|
||||
val newIndex = name.lastOrNull()!!.index
|
||||
if (newIndex != null) {
|
||||
set(name, value)
|
||||
} else {
|
||||
|
@ -141,7 +141,7 @@ class NodeDescriptor(config: Config = Config()) : ItemDescriptor(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) {
|
||||
buildNode(name.cutLast()).newItem(name.last().toString(), descriptor)
|
||||
buildNode(name.cutLast()).newItem(name.lastOrNull().toString(), descriptor)
|
||||
}
|
||||
|
||||
fun item(name: String, descriptor: ItemDescriptor) {
|
||||
@ -199,7 +199,7 @@ operator fun ItemDescriptor.get(name: Name): ItemDescriptor? {
|
||||
if (name.isEmpty()) return this
|
||||
return when (this) {
|
||||
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
|
||||
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.names.*
|
||||
|
||||
/**
|
||||
* 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()
|
||||
}
|
||||
|
||||
val (body, index) = name.last()!!
|
||||
val (body, index) = name.lastOrNull()!!
|
||||
return if (index == null) {
|
||||
root.items.filter { it.key.body == body }.mapKeys { it.key.index }
|
||||
} else {
|
||||
|
@ -1,5 +1,6 @@
|
||||
package hep.dataforge.names
|
||||
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Serializer
|
||||
@ -16,31 +17,8 @@ import kotlinx.serialization.encoding.Encoder
|
||||
* Each token could contain additional index in square brackets.
|
||||
*/
|
||||
@Serializable
|
||||
class Name(val tokens: List<NameToken>) {
|
||||
|
||||
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]
|
||||
public class Name(public val tokens: List<NameToken>) {
|
||||
//TODO to be transformed into inline class after they are supported with serialization
|
||||
|
||||
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)
|
||||
companion object : KSerializer<Name> {
|
||||
const val NAME_SEPARATOR = "."
|
||||
public companion object : KSerializer<Name> {
|
||||
public const val NAME_SEPARATOR: String = "."
|
||||
|
||||
val EMPTY = Name(emptyList())
|
||||
public val EMPTY: Name = Name(emptyList())
|
||||
|
||||
override val descriptor: SerialDescriptor =
|
||||
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.
|
||||
* Following symbols are prohibited in name tokens: `{}.:\`.
|
||||
* A name token could have appendix in square brackets called *index*
|
||||
*/
|
||||
@Serializable
|
||||
data class NameToken(val body: String, val index: String? = null) {
|
||||
public data class NameToken(val body: String, val index: String? = null) {
|
||||
|
||||
init {
|
||||
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()
|
||||
}
|
||||
|
||||
fun hasIndex() = index != null
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
@Serializer(NameToken::class)
|
||||
companion object : KSerializer<NameToken> {
|
||||
public companion object : KSerializer<NameToken> {
|
||||
override val descriptor: SerialDescriptor =
|
||||
PrimitiveSerialDescriptor("hep.dataforge.names.NameToken", PrimitiveKind.STRING)
|
||||
|
||||
override fun deserialize(decoder: Decoder): NameToken {
|
||||
return decoder.decodeString().toName().first()!!
|
||||
return decoder.decodeString().toName().firstOrNull()!!
|
||||
}
|
||||
|
||||
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.
|
||||
* 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
|
||||
val tokens = sequence {
|
||||
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.
|
||||
* 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
|
||||
*/
|
||||
fun Name.withIndex(index: String): Name {
|
||||
public fun Name.withIndex(index: String): Name {
|
||||
val last = NameToken(tokens.last().body, index)
|
||||
if (length == 0) error("Can't add index to empty name")
|
||||
if (length == 1) {
|
||||
@ -215,19 +225,19 @@ fun Name.withIndex(index: String): Name {
|
||||
/**
|
||||
* 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())
|
||||
operator fun <T> MutableMap<Name, T>.set(name: String, value: T) = set(name.toName(), value)
|
||||
public operator fun <T> Map<Name, T>.get(name: String): T? = get(name.toName())
|
||||
public operator fun <T> MutableMap<Name, T>.set(name: String, value: T): Unit = set(name.toName(), value)
|
||||
|
||||
/* 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
|
||||
|
||||
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
|
Loading…
Reference in New Issue
Block a user