Refactor Name and NameToken. Breaking change

This commit is contained in:
Alexander Nozik 2020-08-31 12:02:06 +03:00
parent 57b263ec63
commit 6ad5f162a1
6 changed files with 78 additions and 69 deletions

View File

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

View File

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

View File

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

View File

@ -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())
} }
} }

View File

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

View File

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