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 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 bintrayRepo by extra("dataforge")
val githubProject by extra("dataforge-core") 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.*
import hep.dataforge.meta.scheme.* import hep.dataforge.meta.scheme.*
import hep.dataforge.names.Name import hep.dataforge.names.*
import hep.dataforge.names.NameToken
import hep.dataforge.names.asName
import hep.dataforge.names.isEmpty
import hep.dataforge.values.False import hep.dataforge.values.False
import hep.dataforge.values.True import hep.dataforge.values.True
import hep.dataforge.values.Value import hep.dataforge.values.Value
import hep.dataforge.values.ValueType import hep.dataforge.values.ValueType
@DFBuilder
sealed class ItemDescriptor : Scheme() { sealed class ItemDescriptor : Scheme() {
/** /**
@ -68,8 +66,7 @@ fun ItemDescriptor.validateItem(item: MetaItem<*>?): Boolean {
* @author Alexander Nozik * @author Alexander Nozik
*/ */
@DFBuilder @DFBuilder
class NodeDescriptor : ItemDescriptor() { class NodeDescriptor private constructor() : ItemDescriptor() {
/** /**
* True if the node is required * True if the node is required
* *
@ -84,86 +81,98 @@ class NodeDescriptor : ItemDescriptor() {
*/ */
var default by node() 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 * The map of children node descriptors
*/ */
@Suppress("UNCHECKED_CAST")
val nodes: Map<String, NodeDescriptor> val nodes: Map<String, NodeDescriptor>
get() = config.getIndexed(NODE_KEY.asName()).entries.associate { (name, node) -> get() = config.getIndexed(ITEM_KEY).entries.filter {
name to wrap(node.node ?: error("Node descriptor must be a node")) 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) { val values: Map<String, ValueDescriptor>
if (items.keys.contains(name)) error("The key $name already exists in descriptor") get() = config.getIndexed(ITEM_KEY).entries.filter {
val token = when (descriptor) { it.value.node[IS_NODE_KEY].boolean != true
is NodeDescriptor -> NameToken(NODE_KEY, name) }.associate { (name, item) ->
is ValueDescriptor -> NameToken(VALUE_KEY, name) 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 { private fun buildNode(name: Name): NodeDescriptor {
return when (name.length) { return when (name.length) {
0 -> this 0 -> this
1 -> { 1 -> {
val token = NameToken(NODE_KEY, name.toString()) val token = NameToken(ITEM_KEY.toString(), name.toString())
val config: Config = config[token].node ?: Config().also { config[token] = it } val config: Config = config[token].node ?: Config().also {
it[IS_NODE_KEY] = true
config[token] = it
}
wrap(config) wrap(config)
} }
else -> buildNode(name.first()?.asName()!!).buildNode(name.cutFirst()) 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
} }
/** fun defineItem(name: Name, descriptor: ItemDescriptor) {
* The list of value descriptors buildNode(name.cutLast()).newItem(name.last().toString(), descriptor)
*/ }
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: String, descriptor: ItemDescriptor) {
defineItem(name.toName(), descriptor)
}
/** fun defineNode(name: Name, block: NodeDescriptor.() -> Unit) {
* Add a value descriptor using block for defineItem(name, NodeDescriptor(block))
*/ }
fun defineValue(name: String, block: ValueDescriptor.() -> Unit) {
defineItem(name, ValueDescriptor(block)) fun defineNode(name: String, block: NodeDescriptor.() -> Unit) {
defineNode(name.toName(), block)
} }
fun defineValue(name: Name, block: ValueDescriptor.() -> Unit) { fun defineValue(name: Name, block: ValueDescriptor.() -> Unit) {
require(name.length >= 1) { "Name length for value descriptor must be non-empty" } 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 fun defineValue(name: String, block: ValueDescriptor.() -> Unit) {
defineValue(name.toName(), block)
}
//override val descriptor: NodeDescriptor = empty("descriptor")
companion object : SchemeSpec<NodeDescriptor>(::NodeDescriptor) { companion object : SchemeSpec<NodeDescriptor>(::NodeDescriptor) {
// const val ITEM_KEY = "item" val ITEM_KEY = "item".asName()
const val NODE_KEY = "node" val IS_NODE_KEY = "@isNode".asName()
const val VALUE_KEY = "value"
//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 //TODO infer descriptor from spec
} }
@ -187,9 +196,9 @@ operator fun ItemDescriptor.get(name: Name): ItemDescriptor? {
* *
* @author Alexander Nozik * @author Alexander Nozik
*/ */
@DFBuilder
class ValueDescriptor : ItemDescriptor() { class ValueDescriptor : ItemDescriptor() {
/** /**
* True if the value is required * True if the value is required
* *
@ -255,68 +264,5 @@ class ValueDescriptor : ItemDescriptor() {
this.allowedValues = v.map { Value.of(it) } this.allowedValues = v.map { Value.of(it) }
} }
companion object : SchemeSpec<ValueDescriptor>(::ValueDescriptor) { 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))
// }
}
} }

View File

@ -6,12 +6,11 @@ import hep.dataforge.names.toName
/** /**
* Get all items matching given name. * Get all items matching given name.
*/ */
@DFExperimental
fun Meta.getIndexed(name: Name): Map<String, MetaItem<*>> { fun Meta.getIndexed(name: Name): Map<String, MetaItem<*>> {
val root = when (name.length) { 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 1 -> this
else -> (this[name.cutLast()] as? MetaItem.NodeItem<*>)?.node else -> this[name.cutLast()].node
} }
val (body, index) = name.last()!! val (body, index) = name.last()!!
@ -23,16 +22,13 @@ fun Meta.getIndexed(name: Name): Map<String, MetaItem<*>> {
?: emptyMap() ?: emptyMap()
} }
@DFExperimental
fun Meta.getIndexed(name: String): Map<String, MetaItem<*>> = this@getIndexed.getIndexed(name.toName()) fun Meta.getIndexed(name: String): Map<String, MetaItem<*>> = this@getIndexed.getIndexed(name.toName())
/** /**
* Get all items matching given name. * Get all items matching given name.
*/ */
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
@DFExperimental
fun <M : MetaNode<M>> M.getIndexed(name: Name): Map<String, MetaItem<M>> = fun <M : MetaNode<M>> M.getIndexed(name: Name): Map<String, MetaItem<M>> =
(this as Meta).getIndexed(name) as 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()) 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 * Enum delegate
*/ */
fun <E : Enum<E>> Configurable.enum( inline fun <reified E : Enum<E>> Configurable.enum(
default: E, key: Name? = null, resolve: MetaItem<*>.() -> E? default: E, key: Name? = null
): ReadWriteProperty<Any?, E> = item(default, key).transform { it?.resolve() ?: default } ): ReadWriteProperty<Any?, E> =
item(default, key).transform { item -> item?.string?.let { enumValueOf<E>(it) } ?: default }
/* /*
* Extra delegates for special cases * Extra delegates for special cases
@ -225,7 +226,7 @@ fun <T : Configurable> Configurable.spec(
): ReadWriteProperty<Any?, T> = object : ReadWriteProperty<Any?, T> { ): ReadWriteProperty<Any?, T> = object : ReadWriteProperty<Any?, T> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T { override fun getValue(thisRef: Any?, property: KProperty<*>): T {
val name = key ?: property.name.asName() 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) { 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 * A specification for simplified generation of wrappers
*/ */
open class SchemeSpec<T : Scheme>(val builder: () -> T) : Specification<T> { open class SchemeSpec<T : Scheme>(val builder: () -> T) : Specification<T> {
override fun empty(): T = builder()
override fun wrap(config: Config, defaultProvider: (Name) -> MetaItem<*>?): T { override fun wrap(config: Config, defaultProvider: (Name) -> MetaItem<*>?): T {
return builder().apply { return empty().apply {
this.config = config this.config = config
this.defaultProvider = defaultProvider this.defaultProvider = defaultProvider
} }

View File

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

View File

@ -59,7 +59,8 @@ class Name(val tokens: List<NameToken>) {
val EMPTY = Name(emptyList()) 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 { override fun deserialize(decoder: Decoder): Name {
return decoder.decodeString().toName() return decoder.decodeString().toName()
@ -99,7 +100,8 @@ data class NameToken(val body: String, val index: String = "") {
@Serializer(NameToken::class) @Serializer(NameToken::class)
companion object : KSerializer<NameToken> { 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 { override fun deserialize(decoder: Decoder): NameToken {
return decoder.decodeString().toName().first()!! return decoder.decodeString().toName().first()!!
@ -188,8 +190,12 @@ 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 { fun Name.withIndex(index: String): Name {
val tokens = ArrayList(tokens)
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 == 1) {
return last.asName()
}
val tokens = ArrayList(tokens)
tokens.removeAt(tokens.size - 1) tokens.removeAt(tokens.size - 1)
tokens.add(last) tokens.add(last)
return Name(tokens) return Name(tokens)

View File

@ -20,7 +20,7 @@ class MetaDelegateTest {
class TestScheme : Scheme() { class TestScheme : Scheme() {
var myValue by string() var myValue by string()
var safeValue by double(2.2) 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) var inner by spec(InnerSpec)
companion object : SchemeSpec<TestScheme>(::TestScheme) companion object : SchemeSpec<TestScheme>(::TestScheme)

View File

@ -26,6 +26,6 @@ class DescriptorTest {
@Test @Test
fun testAllowedValues() { fun testAllowedValues() {
val allowed = descriptor.nodes["aNode"]?.values?.get("b")?.allowedValues 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 package hep.dataforge.tables
import hep.dataforge.meta.enum
import hep.dataforge.meta.scheme.Scheme import hep.dataforge.meta.scheme.Scheme
import hep.dataforge.meta.scheme.SchemeSpec import hep.dataforge.meta.scheme.SchemeSpec
import hep.dataforge.meta.scheme.enum import hep.dataforge.meta.scheme.enum
@ -14,5 +13,5 @@ open class ColumnScheme : Scheme() {
} }
class ValueColumnScheme : ColumnScheme() { class ValueColumnScheme : ColumnScheme() {
var valueType by enum(ValueType.STRING){enum<ValueType>()} var valueType by enum(ValueType.STRING)
} }