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) {
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
}
}

View File

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

View File

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

View File

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

View File

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

View File

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