Refactor inner workings of descriptors
This commit is contained in:
parent
e835d81183
commit
417c292507
@ -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")
|
||||
|
@ -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"))
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a child item descriptor for this node
|
||||
*/
|
||||
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)
|
||||
}
|
||||
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 }
|
||||
wrap(config)
|
||||
}
|
||||
else -> buildNode(name.first()?.asName()!!).buildNode(name.cutFirst())
|
||||
}
|
||||
}
|
||||
|
||||
fun defineNode(name: Name, block: NodeDescriptor.() -> Unit) {
|
||||
buildNode(name).apply(block)
|
||||
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)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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"))
|
||||
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)
|
||||
}
|
||||
|
||||
private fun buildNode(name: Name): NodeDescriptor {
|
||||
return when (name.length) {
|
||||
0 -> this
|
||||
1 -> {
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a value descriptor using block for
|
||||
* Define a child item descriptor for this node
|
||||
*/
|
||||
fun defineValue(name: String, block: ValueDescriptor.() -> Unit) {
|
||||
defineItem(name, ValueDescriptor(block))
|
||||
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
|
||||
}
|
||||
|
||||
fun defineItem(name: Name, descriptor: ItemDescriptor) {
|
||||
buildNode(name.cutLast()).newItem(name.last().toString(), descriptor)
|
||||
}
|
||||
|
||||
fun defineItem(name: String, descriptor: ItemDescriptor) {
|
||||
defineItem(name.toName(), descriptor)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
@ -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())
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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] }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -26,6 +26,6 @@ class DescriptorTest {
|
||||
@Test
|
||||
fun testAllowedValues() {
|
||||
val allowed = descriptor.nodes["aNode"]?.values?.get("b")?.allowedValues
|
||||
assertEquals(allowed, emptyList())
|
||||
assertEquals(emptyList(), allowed)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
Loading…
Reference in New Issue
Block a user