Refactor inner workings of descriptors

This commit is contained in:
Alexander Nozik 2020-03-25 10:41:27 +03:00
parent e835d81183
commit 417c292507
10 changed files with 88 additions and 140 deletions

View File

@ -6,7 +6,7 @@ plugins {
id("scientifik.publish") version toolsVersion apply false
}
val dataforgeVersion by extra("0.1.5")
val dataforgeVersion by extra("0.1.6-dev")
val bintrayRepo by extra("dataforge")
val githubProject by extra("dataforge-core")

View File

@ -2,15 +2,13 @@ package hep.dataforge.meta.descriptors
import hep.dataforge.meta.*
import hep.dataforge.meta.scheme.*
import hep.dataforge.names.Name
import hep.dataforge.names.NameToken
import hep.dataforge.names.asName
import hep.dataforge.names.isEmpty
import hep.dataforge.names.*
import hep.dataforge.values.False
import hep.dataforge.values.True
import hep.dataforge.values.Value
import hep.dataforge.values.ValueType
@DFBuilder
sealed class ItemDescriptor : Scheme() {
/**
@ -68,8 +66,7 @@ fun ItemDescriptor.validateItem(item: MetaItem<*>?): Boolean {
* @author Alexander Nozik
*/
@DFBuilder
class NodeDescriptor : ItemDescriptor() {
class NodeDescriptor private constructor() : ItemDescriptor() {
/**
* True if the node is required
*
@ -84,86 +81,98 @@ class NodeDescriptor : ItemDescriptor() {
*/
var default by node()
val items: Map<String, ItemDescriptor>
get() = config.getIndexed(ITEM_KEY).mapValues { (_, item) ->
val node = item.node ?: error("Node descriptor must be a node")
if (node[IS_NODE_KEY].boolean == true) {
NodeDescriptor.wrap(node)
} else {
ValueDescriptor.wrap(node)
}
}
/**
* The map of children node descriptors
*/
@Suppress("UNCHECKED_CAST")
val nodes: Map<String, NodeDescriptor>
get() = config.getIndexed(NODE_KEY.asName()).entries.associate { (name, node) ->
name to wrap(node.node ?: error("Node descriptor must be a node"))
get() = config.getIndexed(ITEM_KEY).entries.filter {
it.value.node[IS_NODE_KEY].boolean == true
}.associate { (name, item) ->
val node = item.node ?: error("Node descriptor must be a node")
name to NodeDescriptor.wrap(node)
}
/**
* Define a child item descriptor for this node
* The list of value descriptors
*/
fun defineItem(name: String, descriptor: ItemDescriptor) {
if (items.keys.contains(name)) error("The key $name already exists in descriptor")
val token = when (descriptor) {
is NodeDescriptor -> NameToken(NODE_KEY, name)
is ValueDescriptor -> NameToken(VALUE_KEY, name)
val values: Map<String, ValueDescriptor>
get() = config.getIndexed(ITEM_KEY).entries.filter {
it.value.node[IS_NODE_KEY].boolean != true
}.associate { (name, item) ->
val node = item.node ?: error("Node descriptor must be a node")
name to ValueDescriptor.wrap(node)
}
config[token] = descriptor.config
}
fun defineNode(name: String, block: NodeDescriptor.() -> Unit) {
val token = NameToken(NODE_KEY, name)
if (config[token] == null) {
config[token] = NodeDescriptor(block)
} else {
NodeDescriptor.update(config[token].node ?: error("Node expected"), block)
}
}
private fun buildNode(name: Name): NodeDescriptor {
return when (name.length) {
0 -> this
1 -> {
val token = NameToken(NODE_KEY, name.toString())
val config: Config = config[token].node ?: Config().also { config[token] = it }
val token = NameToken(ITEM_KEY.toString(), name.toString())
val config: Config = config[token].node ?: Config().also {
it[IS_NODE_KEY] = true
config[token] = it
}
wrap(config)
}
else -> buildNode(name.first()?.asName()!!).buildNode(name.cutFirst())
}
}
fun defineNode(name: Name, block: NodeDescriptor.() -> Unit) {
buildNode(name).apply(block)
/**
* Define a child item descriptor for this node
*/
private fun newItem(key: String, descriptor: ItemDescriptor) {
if (items.keys.contains(key)) error("The key $key already exists in descriptor")
val token = ITEM_KEY.withIndex(key)
config[token] = descriptor.config
}
/**
* The list of value descriptors
*/
val values: Map<String, ValueDescriptor>
get() = config.getIndexed(VALUE_KEY.asName()).entries.associate { (name, node) ->
name to ValueDescriptor.wrap(node.node ?: error("Value descriptor must be a node"))
}
fun defineItem(name: Name, descriptor: ItemDescriptor) {
buildNode(name.cutLast()).newItem(name.last().toString(), descriptor)
}
fun defineItem(name: String, descriptor: ItemDescriptor) {
defineItem(name.toName(), descriptor)
}
/**
* Add a value descriptor using block for
*/
fun defineValue(name: String, block: ValueDescriptor.() -> Unit) {
defineItem(name, ValueDescriptor(block))
fun defineNode(name: Name, block: NodeDescriptor.() -> Unit) {
defineItem(name, NodeDescriptor(block))
}
fun defineNode(name: String, block: NodeDescriptor.() -> Unit) {
defineNode(name.toName(), block)
}
fun defineValue(name: Name, block: ValueDescriptor.() -> Unit) {
require(name.length >= 1) { "Name length for value descriptor must be non-empty" }
buildNode(name.cutLast()).defineValue(name.last().toString(), block)
defineItem(name, ValueDescriptor(block))
}
val items: Map<String, ItemDescriptor> get() = nodes + values
//override val descriptor: NodeDescriptor = empty("descriptor")
fun defineValue(name: String, block: ValueDescriptor.() -> Unit) {
defineValue(name.toName(), block)
}
companion object : SchemeSpec<NodeDescriptor>(::NodeDescriptor) {
// const val ITEM_KEY = "item"
const val NODE_KEY = "node"
const val VALUE_KEY = "value"
val ITEM_KEY = "item".asName()
val IS_NODE_KEY = "@isNode".asName()
//override fun wrap(config: Config): NodeDescriptor = NodeDescriptor(config)
override fun empty(): NodeDescriptor {
return super.empty().apply {
config[IS_NODE_KEY] = true
}
}
//TODO infer descriptor from spec
}
@ -187,9 +196,9 @@ operator fun ItemDescriptor.get(name: Name): ItemDescriptor? {
*
* @author Alexander Nozik
*/
@DFBuilder
class ValueDescriptor : ItemDescriptor() {
/**
* True if the value is required
*
@ -255,68 +264,5 @@ class ValueDescriptor : ItemDescriptor() {
this.allowedValues = v.map { Value.of(it) }
}
companion object : SchemeSpec<ValueDescriptor>(::ValueDescriptor) {
// inline fun <reified E : Enum<E>> enum(name: String) = ValueDescriptor {
// type(ValueType.STRING)
// this.allowedValues = enumValues<E>().map { Value.of(it.name) }
// }
// /**
// * Build a value descriptor from annotation
// */
// fun build(def: ValueDef): ValueDescriptor {
// val builder = MetaBuilder("value")
// .setValue("name", def.key)
//
// if (def.type.isNotEmpty()) {
// builder.setValue("type", def.type)
// }
//
// if (def.multiple) {
// builder.setValue("multiple", def.multiple)
// }
//
// if (!def.info.isEmpty()) {
// builder.setValue("info", def.info)
// }
//
// if (def.allowed.isNotEmpty()) {
// builder.setValue("allowedValues", def.allowed)
// } else if (def.enumeration != Any::class) {
// if (def.enumeration.java.isEnum) {
// val values = def.enumeration.java.enumConstants
// builder.setValue("allowedValues", values.map { it.toString() })
// } else {
// throw RuntimeException("Only enumeration classes are allowed in 'enumeration' annotation property")
// }
// }
//
// if (def.def.isNotEmpty()) {
// builder.setValue("default", def.def)
// } else if (!def.required) {
// builder.setValue("required", def.required)
// }
//
// if (def.tags.isNotEmpty()) {
// builder.setValue("tags", def.tags)
// }
// return ValueDescriptor(builder)
// }
//
// /**
// * Build empty value descriptor
// */
// fun empty(valueName: String): ValueDescriptor {
// val builder = MetaBuilder("value")
// .setValue("name", valueName)
// return ValueDescriptor(builder)
// }
//
// /**
// * Merge two separate value descriptors
// */
// fun merge(primary: ValueDescriptor, secondary: ValueDescriptor): ValueDescriptor {
// return ValueDescriptor(Laminate(primary.meta, secondary.meta))
// }
}
companion object : SchemeSpec<ValueDescriptor>(::ValueDescriptor)
}

View File

@ -6,12 +6,11 @@ import hep.dataforge.names.toName
/**
* Get all items matching given name.
*/
@DFExperimental
fun Meta.getIndexed(name: Name): Map<String, MetaItem<*>> {
val root = when (name.length) {
0 -> error("Can't use empty name for that")
0 -> error("Can't use empty name for 'getIndexed'")
1 -> this
else -> (this[name.cutLast()] as? MetaItem.NodeItem<*>)?.node
else -> this[name.cutLast()].node
}
val (body, index) = name.last()!!
@ -23,16 +22,13 @@ fun Meta.getIndexed(name: Name): Map<String, MetaItem<*>> {
?: emptyMap()
}
@DFExperimental
fun Meta.getIndexed(name: String): Map<String, MetaItem<*>> = this@getIndexed.getIndexed(name.toName())
/**
* Get all items matching given name.
*/
@Suppress("UNCHECKED_CAST")
@DFExperimental
fun <M : MetaNode<M>> M.getIndexed(name: Name): Map<String, MetaItem<M>> =
(this as Meta).getIndexed(name) as Map<String, MetaItem<M>>
@DFExperimental
fun <M : MetaNode<M>> M.getIndexed(name: String): Map<String, MetaItem<M>> = getIndexed(name.toName())

View File

@ -168,9 +168,10 @@ fun Configurable.float(default: Float, key: Name? = null): ReadWriteProperty<Any
/**
* Enum delegate
*/
fun <E : Enum<E>> Configurable.enum(
default: E, key: Name? = null, resolve: MetaItem<*>.() -> E?
): ReadWriteProperty<Any?, E> = item(default, key).transform { it?.resolve() ?: default }
inline fun <reified E : Enum<E>> Configurable.enum(
default: E, key: Name? = null
): ReadWriteProperty<Any?, E> =
item(default, key).transform { item -> item?.string?.let { enumValueOf<E>(it) } ?: default }
/*
* Extra delegates for special cases
@ -225,7 +226,7 @@ fun <T : Configurable> Configurable.spec(
): ReadWriteProperty<Any?, T> = object : ReadWriteProperty<Any?, T> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
val name = key ?: property.name.asName()
return config[name].node?.let { spec.wrap(it) }?:default
return config[name].node?.let { spec.wrap(it) } ?: default
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {

View File

@ -60,8 +60,10 @@ inline operator fun <T : Scheme> T.invoke(block: T.() -> Unit) = apply(block)
* A specification for simplified generation of wrappers
*/
open class SchemeSpec<T : Scheme>(val builder: () -> T) : Specification<T> {
override fun empty(): T = builder()
override fun wrap(config: Config, defaultProvider: (Name) -> MetaItem<*>?): T {
return builder().apply {
return empty().apply {
this.config = config
this.defaultProvider = defaultProvider
}

View File

@ -17,7 +17,7 @@ interface Specification<T : Configurable> {
return wrap(config).apply(action)
}
operator fun invoke(action: T.() -> Unit) = update(Config(), action)
operator fun invoke(action: T.() -> Unit) = empty().apply(action)
fun empty() = wrap()
@ -34,9 +34,7 @@ interface Specification<T : Configurable> {
/**
* Wrap a configuration using static meta as default
*/
fun wrap(default: Meta): T = wrap(
Config()
) { default[it] }
fun wrap(default: Meta): T = wrap(Config()) { default[it] }
}
/**

View File

@ -59,7 +59,8 @@ class Name(val tokens: List<NameToken>) {
val EMPTY = Name(emptyList())
override val descriptor: SerialDescriptor = PrimitiveDescriptor("hep.dataforge.names.Name", PrimitiveKind.STRING)
override val descriptor: SerialDescriptor =
PrimitiveDescriptor("hep.dataforge.names.Name", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): Name {
return decoder.decodeString().toName()
@ -99,7 +100,8 @@ data class NameToken(val body: String, val index: String = "") {
@Serializer(NameToken::class)
companion object : KSerializer<NameToken> {
override val descriptor: SerialDescriptor = PrimitiveDescriptor("hep.dataforge.names.NameToken", PrimitiveKind.STRING)
override val descriptor: SerialDescriptor =
PrimitiveDescriptor("hep.dataforge.names.NameToken", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): NameToken {
return decoder.decodeString().toName().first()!!
@ -188,8 +190,12 @@ fun Name.isEmpty(): Boolean = this.length == 0
* Set or replace last token index
*/
fun Name.withIndex(index: String): Name {
val tokens = ArrayList(tokens)
val last = NameToken(tokens.last().body, index)
if (length == 0) error("Can't add index to empty name")
if (length == 1) {
return last.asName()
}
val tokens = ArrayList(tokens)
tokens.removeAt(tokens.size - 1)
tokens.add(last)
return Name(tokens)

View File

@ -20,7 +20,7 @@ class MetaDelegateTest {
class TestScheme : Scheme() {
var myValue by string()
var safeValue by double(2.2)
var enumValue by enum(TestEnum.YES) { enum<TestEnum>() }
var enumValue by enum(TestEnum.YES)
var inner by spec(InnerSpec)
companion object : SchemeSpec<TestScheme>(::TestScheme)

View File

@ -26,6 +26,6 @@ class DescriptorTest {
@Test
fun testAllowedValues() {
val allowed = descriptor.nodes["aNode"]?.values?.get("b")?.allowedValues
assertEquals(allowed, emptyList())
assertEquals(emptyList(), allowed)
}
}

View File

@ -1,6 +1,5 @@
package hep.dataforge.tables
import hep.dataforge.meta.enum
import hep.dataforge.meta.scheme.Scheme
import hep.dataforge.meta.scheme.SchemeSpec
import hep.dataforge.meta.scheme.enum
@ -14,5 +13,5 @@ open class ColumnScheme : Scheme() {
}
class ValueColumnScheme : ColumnScheme() {
var valueType by enum(ValueType.STRING){enum<ValueType>()}
var valueType by enum(ValueType.STRING)
}