Configurable and Scheme revision
This commit is contained in:
parent
736ec621b0
commit
b83821af51
@ -1,8 +1,11 @@
|
|||||||
[![JetBrains Research](https://jb.gg/badges/research.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
|
[![JetBrains Research](https://jb.gg/badges/research.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
|
||||||
|
[![DOI](https://zenodo.org/badge/148831678.svg)](https://zenodo.org/badge/latestdoi/148831678)
|
||||||
|
|
||||||
|
![Gradle build](https://github.com/mipt-npm/dataforge-core/workflows/Gradle%20build/badge.svg)
|
||||||
|
|
||||||
[ ![Download](https://api.bintray.com/packages/mipt-npm/dataforge/dataforge-meta/images/download.svg) ](https://bintray.com/mipt-npm/dataforge/dataforge-meta/_latestVersion)
|
[ ![Download](https://api.bintray.com/packages/mipt-npm/dataforge/dataforge-meta/images/download.svg) ](https://bintray.com/mipt-npm/dataforge/dataforge-meta/_latestVersion)
|
||||||
|
|
||||||
[![DOI](https://zenodo.org/badge/148831678.svg)](https://zenodo.org/badge/latestdoi/148831678)
|
|
||||||
|
|
||||||
# Questions and Answers #
|
# Questions and Answers #
|
||||||
|
|
||||||
|
@ -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-dev-6")
|
val dataforgeVersion by extra("0.1.5-dev-7")
|
||||||
|
|
||||||
val bintrayRepo by extra("dataforge")
|
val bintrayRepo by extra("dataforge")
|
||||||
val githubProject by extra("dataforge-core")
|
val githubProject by extra("dataforge-core")
|
||||||
|
@ -4,7 +4,7 @@ import hep.dataforge.meta.*
|
|||||||
import hep.dataforge.names.toName
|
import hep.dataforge.names.toName
|
||||||
|
|
||||||
|
|
||||||
class DataFilter(override val config: Config) : Specific {
|
class DataFilter : Scheme() {
|
||||||
/**
|
/**
|
||||||
* A source node for the filter
|
* A source node for the filter
|
||||||
*/
|
*/
|
||||||
@ -22,9 +22,7 @@ class DataFilter(override val config: Config) : Specific {
|
|||||||
|
|
||||||
fun isEmpty(): Boolean = config.isEmpty()
|
fun isEmpty(): Boolean = config.isEmpty()
|
||||||
|
|
||||||
companion object : Specification<DataFilter> {
|
companion object : SchemeSpec<DataFilter>(::DataFilter)
|
||||||
override fun wrap(config: Config): DataFilter = DataFilter(config)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -133,7 +133,7 @@ object ConfigSerializer : KSerializer<Config> {
|
|||||||
override val descriptor: SerialDescriptor = MetaSerializer.descriptor
|
override val descriptor: SerialDescriptor = MetaSerializer.descriptor
|
||||||
|
|
||||||
override fun deserialize(decoder: Decoder): Config {
|
override fun deserialize(decoder: Decoder): Config {
|
||||||
return MetaSerializer.deserialize(decoder).toConfig()
|
return MetaSerializer.deserialize(decoder).asConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, obj: Config) {
|
override fun serialize(encoder: Encoder, obj: Config) {
|
||||||
|
@ -1,29 +1,24 @@
|
|||||||
package hep.dataforge.descriptors
|
package hep.dataforge.descriptors
|
||||||
|
|
||||||
import hep.dataforge.descriptors.Described.Companion.DESCRIPTOR_NODE
|
|
||||||
import hep.dataforge.meta.MetaRepr
|
|
||||||
import hep.dataforge.meta.get
|
|
||||||
import hep.dataforge.meta.node
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An object which provides its descriptor
|
* An object which provides its descriptor
|
||||||
*/
|
*/
|
||||||
interface Described {
|
interface Described {
|
||||||
val descriptor: NodeDescriptor
|
val descriptor: NodeDescriptor?
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val DESCRIPTOR_NODE = "@descriptor"
|
const val DESCRIPTOR_NODE = "@descriptor"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
///**
|
||||||
* If meta node supplies explicit descriptor, return it, otherwise try to use descriptor node from meta itself
|
// * If meta node supplies explicit descriptor, return it, otherwise try to use descriptor node from meta itself
|
||||||
*/
|
// */
|
||||||
val MetaRepr.descriptor: NodeDescriptor?
|
//val MetaRepr.descriptor: NodeDescriptor?
|
||||||
get() {
|
// get() {
|
||||||
return if (this is Described) {
|
// return if (this is Described) {
|
||||||
descriptor
|
// descriptor
|
||||||
} else {
|
// } else {
|
||||||
toMeta()[DESCRIPTOR_NODE].node?.let { NodeDescriptor.wrap(it) }
|
// toMeta()[DESCRIPTOR_NODE].node?.let { NodeDescriptor.wrap(it) }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
@ -0,0 +1,28 @@
|
|||||||
|
package hep.dataforge.descriptors
|
||||||
|
|
||||||
|
import hep.dataforge.meta.MetaBase
|
||||||
|
import hep.dataforge.meta.MetaItem
|
||||||
|
import hep.dataforge.names.NameToken
|
||||||
|
import hep.dataforge.values.Null
|
||||||
|
|
||||||
|
class DescriptorMeta(val descriptor: NodeDescriptor) : MetaBase() {
|
||||||
|
override val items: Map<NameToken, MetaItem<*>>
|
||||||
|
get() = descriptor.items.entries.associate { entry ->
|
||||||
|
NameToken(entry.key) to entry.value.defaultItem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun NodeDescriptor.defaultItem(): MetaItem.NodeItem<*> =
|
||||||
|
MetaItem.NodeItem(default ?: DescriptorMeta(this))
|
||||||
|
|
||||||
|
fun ValueDescriptor.defaultItem(): MetaItem.ValueItem = MetaItem.ValueItem(default ?: Null)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a default [MetaItem] from descriptor.
|
||||||
|
*/
|
||||||
|
fun ItemDescriptor.defaultItem(): MetaItem<*> {
|
||||||
|
return when (this) {
|
||||||
|
is ValueDescriptor -> defaultItem()
|
||||||
|
is NodeDescriptor -> defaultItem()
|
||||||
|
}
|
||||||
|
}
|
@ -4,12 +4,13 @@ import hep.dataforge.meta.*
|
|||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.names.NameToken
|
import hep.dataforge.names.NameToken
|
||||||
import hep.dataforge.names.asName
|
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
|
||||||
|
|
||||||
sealed class ItemDescriptor(override val config: Config) : Specific {
|
sealed class ItemDescriptor : Scheme() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if same name siblings with this name are allowed
|
* True if same name siblings with this name are allowed
|
||||||
@ -30,7 +31,7 @@ sealed class ItemDescriptor(override val config: Config) : Specific {
|
|||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
var attributes by child()
|
var attributes by config()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if the item is required
|
* True if the item is required
|
||||||
@ -46,7 +47,7 @@ sealed class ItemDescriptor(override val config: Config) : Specific {
|
|||||||
*
|
*
|
||||||
* @author Alexander Nozik
|
* @author Alexander Nozik
|
||||||
*/
|
*/
|
||||||
class NodeDescriptor(config: Config) : ItemDescriptor(config) {
|
class NodeDescriptor : ItemDescriptor() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if the node is required
|
* True if the node is required
|
||||||
@ -60,7 +61,7 @@ class NodeDescriptor(config: Config) : ItemDescriptor(config) {
|
|||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
var default: Config? by child()
|
var default: Config? by nullableConfig()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The map of children node descriptors
|
* The map of children node descriptors
|
||||||
@ -134,18 +135,28 @@ class NodeDescriptor(config: Config) : ItemDescriptor(config) {
|
|||||||
|
|
||||||
//override val descriptor: NodeDescriptor = empty("descriptor")
|
//override val descriptor: NodeDescriptor = empty("descriptor")
|
||||||
|
|
||||||
companion object : Specification<NodeDescriptor> {
|
companion object : SchemeSpec<NodeDescriptor>(::NodeDescriptor) {
|
||||||
|
|
||||||
// const val ITEM_KEY = "item"
|
// const val ITEM_KEY = "item"
|
||||||
const val NODE_KEY = "node"
|
const val NODE_KEY = "node"
|
||||||
const val VALUE_KEY = "value"
|
const val VALUE_KEY = "value"
|
||||||
|
|
||||||
override fun wrap(config: Config): NodeDescriptor = NodeDescriptor(config)
|
//override fun wrap(config: Config): NodeDescriptor = NodeDescriptor(config)
|
||||||
|
|
||||||
//TODO infer descriptor from spec
|
//TODO infer descriptor from spec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a descriptor item associated with given name or null if item for given name not provided
|
||||||
|
*/
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A descriptor for meta value
|
* A descriptor for meta value
|
||||||
@ -154,7 +165,7 @@ class NodeDescriptor(config: Config) : ItemDescriptor(config) {
|
|||||||
*
|
*
|
||||||
* @author Alexander Nozik
|
* @author Alexander Nozik
|
||||||
*/
|
*/
|
||||||
class ValueDescriptor(config: Config) : ItemDescriptor(config) {
|
class ValueDescriptor : ItemDescriptor() {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -180,8 +191,8 @@ class ValueDescriptor(config: Config) : ItemDescriptor(config) {
|
|||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
var type: List<ValueType> by value {
|
var type: List<ValueType> by item {
|
||||||
it?.list?.map { v -> ValueType.valueOf(v.string) } ?: emptyList()
|
it?.value?.list?.map { v -> ValueType.valueOf(v.string) } ?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun type(vararg t: ValueType) {
|
fun type(vararg t: ValueType) {
|
||||||
@ -222,10 +233,7 @@ class ValueDescriptor(config: Config) : ItemDescriptor(config) {
|
|||||||
this.allowedValues = v.map { Value.of(it) }
|
this.allowedValues = v.map { Value.of(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object : Specification<ValueDescriptor> {
|
companion object : SchemeSpec<ValueDescriptor>(::ValueDescriptor) {
|
||||||
|
|
||||||
override fun wrap(config: Config): ValueDescriptor = ValueDescriptor(config)
|
|
||||||
|
|
||||||
inline fun <reified E : Enum<E>> enum(name: String) = ValueDescriptor {
|
inline fun <reified E : Enum<E>> enum(name: String) = ValueDescriptor {
|
||||||
type(ValueType.STRING)
|
type(ValueType.STRING)
|
||||||
this.allowedValues = enumValues<E>().map { Value.of(it.name) }
|
this.allowedValues = enumValues<E>().map { Value.of(it.name) }
|
||||||
@ -289,4 +297,4 @@ class ValueDescriptor(config: Config) : ItemDescriptor(config) {
|
|||||||
// return ValueDescriptor(Laminate(primary.meta, secondary.meta))
|
// return ValueDescriptor(Laminate(primary.meta, secondary.meta))
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -40,7 +40,7 @@ class Config : AbstractMutableMeta<Config>() {
|
|||||||
override fun replaceItem(key: NameToken, oldItem: MetaItem<Config>?, newItem: MetaItem<Config>?) {
|
override fun replaceItem(key: NameToken, oldItem: MetaItem<Config>?, newItem: MetaItem<Config>?) {
|
||||||
if (newItem == null) {
|
if (newItem == null) {
|
||||||
_items.remove(key)
|
_items.remove(key)
|
||||||
if(oldItem!= null && oldItem is MetaItem.NodeItem<Config>) {
|
if (oldItem != null && oldItem is MetaItem.NodeItem<Config>) {
|
||||||
oldItem.node.removeListener(this)
|
oldItem.node.removeListener(this)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -57,7 +57,7 @@ class Config : AbstractMutableMeta<Config>() {
|
|||||||
/**
|
/**
|
||||||
* Attach configuration node instead of creating one
|
* Attach configuration node instead of creating one
|
||||||
*/
|
*/
|
||||||
override fun wrapNode(meta: Meta): Config = meta.toConfig()
|
override fun wrapNode(meta: Meta): Config = meta.asConfig()
|
||||||
|
|
||||||
override fun empty(): Config = Config()
|
override fun empty(): Config = Config()
|
||||||
|
|
||||||
@ -68,22 +68,12 @@ class Config : AbstractMutableMeta<Config>() {
|
|||||||
|
|
||||||
operator fun Config.get(token: NameToken): MetaItem<Config>? = items[token]
|
operator fun Config.get(token: NameToken): MetaItem<Config>? = items[token]
|
||||||
|
|
||||||
fun Meta.toConfig(): Config = this as? Config ?: Config().also { builder ->
|
fun Meta.asConfig(): Config = this as? Config ?: Config().also { builder ->
|
||||||
this.items.mapValues { entry ->
|
this.items.mapValues { entry ->
|
||||||
val item = entry.value
|
val item = entry.value
|
||||||
builder[entry.key.asName()] = when (item) {
|
builder[entry.key.asName()] = when (item) {
|
||||||
is MetaItem.ValueItem -> item.value
|
is MetaItem.ValueItem -> item.value
|
||||||
is MetaItem.NodeItem -> MetaItem.NodeItem(item.node.toConfig())
|
is MetaItem.NodeItem -> MetaItem.NodeItem(item.node.asConfig())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Configurable {
|
|
||||||
val config: Config
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T : Configurable> T.configure(meta: Meta): T = this.apply { config.update(meta) }
|
|
||||||
|
|
||||||
fun <T : Configurable> T.configure(action: MetaBuilder.() -> Unit): T = configure(buildMeta(action))
|
|
||||||
|
|
||||||
open class SimpleConfigurable(override val config: Config) : Configurable
|
|
@ -0,0 +1,44 @@
|
|||||||
|
package hep.dataforge.meta
|
||||||
|
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.toName
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A container that holds a [Config] and a default item provider.
|
||||||
|
* Default item provider could be use for example to reference parent configuration.
|
||||||
|
* It is not possible to know if some property is declared by provider just by looking on [Configurable],
|
||||||
|
* this information should be provided externally.
|
||||||
|
*/
|
||||||
|
interface Configurable {
|
||||||
|
/**
|
||||||
|
* Backing config
|
||||||
|
*/
|
||||||
|
val config: Config
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default meta item provider
|
||||||
|
*/
|
||||||
|
fun getDefaultItem(name: Name): MetaItem<*>? = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a property with default
|
||||||
|
*/
|
||||||
|
fun Configurable.getProperty(name: Name): MetaItem<*>? = config[name] ?: getDefaultItem(name)
|
||||||
|
|
||||||
|
fun Configurable.getProperty(key: String) = getProperty(key.toName())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a configurable property
|
||||||
|
*/
|
||||||
|
fun Configurable.setProperty(name: Name, item: MetaItem<*>?) {
|
||||||
|
config[name] = item
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Configurable.setProperty(key: String, item: MetaItem<*>?) {
|
||||||
|
setProperty(key.toName(), item)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T : Configurable> T.configure(meta: Meta): T = this.apply { config.update(meta) }
|
||||||
|
|
||||||
|
fun <T : Configurable> T.configure(action: Config.() -> Unit): T = apply { config.apply(action) }
|
@ -1,9 +1,10 @@
|
|||||||
package hep.dataforge.meta
|
package hep.dataforge.meta
|
||||||
|
|
||||||
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.names.NameToken
|
import hep.dataforge.names.NameToken
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A meta laminate consisting of multiple immutable meta layers. For mutable front layer, use [Styled].
|
* A meta laminate consisting of multiple immutable meta layers. For mutable front layer, use [Scheme].
|
||||||
*/
|
*/
|
||||||
class Laminate(layers: List<Meta>) : MetaBase() {
|
class Laminate(layers: List<Meta>) : MetaBase() {
|
||||||
|
|
||||||
@ -17,10 +18,11 @@ class Laminate(layers: List<Meta>) : MetaBase() {
|
|||||||
|
|
||||||
constructor(vararg layers: Meta?) : this(layers.filterNotNull())
|
constructor(vararg layers: Meta?) : this(layers.filterNotNull())
|
||||||
|
|
||||||
override val items: Map<NameToken, MetaItem<Meta>>
|
override val items: Map<NameToken, MetaItem<Meta>> by lazy {
|
||||||
get() = layers.map { it.items.keys }.flatten().associateWith { key ->
|
layers.map { it.items.keys }.flatten().associateWith { key ->
|
||||||
layers.asSequence().map { it.items[key] }.filterNotNull().let(replaceRule)
|
layers.asSequence().map { it.items[key] }.filterNotNull().let(replaceRule)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate sealed meta using [mergeRule]
|
* Generate sealed meta using [mergeRule]
|
||||||
@ -77,6 +79,16 @@ class Laminate(layers: List<Meta>) : MetaBase() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performance optimized version of get method
|
||||||
|
*/
|
||||||
|
fun Laminate.getFirst(name: Name): MetaItem<*>? {
|
||||||
|
layers.forEach { layer ->
|
||||||
|
layer[name]?.let { return it }
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new [Laminate] adding given layer to the top
|
* Create a new [Laminate] adding given layer to the top
|
||||||
*/
|
*/
|
||||||
|
@ -5,6 +5,7 @@ import hep.dataforge.meta.MetaItem.NodeItem
|
|||||||
import hep.dataforge.meta.MetaItem.ValueItem
|
import hep.dataforge.meta.MetaItem.ValueItem
|
||||||
import hep.dataforge.names.*
|
import hep.dataforge.names.*
|
||||||
import hep.dataforge.values.EnumValue
|
import hep.dataforge.values.EnumValue
|
||||||
|
import hep.dataforge.values.Null
|
||||||
import hep.dataforge.values.Value
|
import hep.dataforge.values.Value
|
||||||
import hep.dataforge.values.boolean
|
import hep.dataforge.values.boolean
|
||||||
|
|
||||||
@ -22,6 +23,17 @@ sealed class MetaItem<out M : Meta> {
|
|||||||
data class NodeItem<M : Meta>(val node: M) : MetaItem<M>() {
|
data class NodeItem<M : Meta>(val node: M) : MetaItem<M>() {
|
||||||
override fun toString(): String = node.toString()
|
override fun toString(): String = node.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun of(arg: Any?): MetaItem<*> {
|
||||||
|
return when (arg) {
|
||||||
|
null -> ValueItem(Null)
|
||||||
|
is MetaItem<*> -> arg
|
||||||
|
is Meta -> NodeItem(arg)
|
||||||
|
else -> ValueItem(Value.of(arg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -45,7 +57,7 @@ interface Meta : MetaRepr {
|
|||||||
*/
|
*/
|
||||||
val items: Map<NameToken, MetaItem<*>>
|
val items: Map<NameToken, MetaItem<*>>
|
||||||
|
|
||||||
override fun toMeta(): Meta = this
|
override fun toMeta(): Meta = seal()
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean
|
override fun equals(other: Any?): Boolean
|
||||||
|
|
||||||
@ -69,7 +81,7 @@ interface Meta : MetaRepr {
|
|||||||
/**
|
/**
|
||||||
* Perform recursive item search using given [name]. Each [NameToken] is treated as a name in [Meta.items] of a parent node.
|
* Perform recursive item search using given [name]. Each [NameToken] is treated as a name in [Meta.items] of a parent node.
|
||||||
*
|
*
|
||||||
* If [name] is empty reture current [Meta] as a [NodeItem]
|
* If [name] is empty return current [Meta] as a [NodeItem]
|
||||||
*/
|
*/
|
||||||
operator fun Meta?.get(name: Name): MetaItem<*>? {
|
operator fun Meta?.get(name: Name): MetaItem<*>? {
|
||||||
if (this == null) return null
|
if (this == null) return null
|
||||||
@ -129,17 +141,8 @@ interface MetaNode<out M : MetaNode<M>> : Meta {
|
|||||||
/**
|
/**
|
||||||
* The same as [Meta.get], but with specific node type
|
* The same as [Meta.get], but with specific node type
|
||||||
*/
|
*/
|
||||||
operator fun <M : MetaNode<M>> M?.get(name: Name): MetaItem<M>? {
|
@Suppress("UNCHECKED_CAST")
|
||||||
if (this == null) return null
|
operator fun <M : MetaNode<M>> M?.get(name: Name): MetaItem<M>? = (this as Meta)[name] as MetaItem<M>?
|
||||||
if (name.isEmpty()) return NodeItem(this)
|
|
||||||
return name.first()?.let { token ->
|
|
||||||
val tail = name.cutFirst()
|
|
||||||
when (tail.length) {
|
|
||||||
0 -> items[token]
|
|
||||||
else -> items[token]?.node?.get(tail)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun <M : MetaNode<M>> M?.get(key: String): MetaItem<M>? = this[key.toName()]
|
operator fun <M : MetaNode<M>> M?.get(key: String): MetaItem<M>? = this[key.toName()]
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ operator fun MutableMeta<*>.set(name: Name, value: Any?) {
|
|||||||
null -> remove(name)
|
null -> remove(name)
|
||||||
is MetaItem<*> -> setItem(name, value)
|
is MetaItem<*> -> setItem(name, value)
|
||||||
is Meta -> setNode(name, value)
|
is Meta -> setNode(name, value)
|
||||||
is Specific -> setNode(name, value.config)
|
is Configurable -> setNode(name, value.config)
|
||||||
else -> setValue(name, Value.of(value))
|
else -> setValue(name, Value.of(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,78 @@
|
|||||||
|
package hep.dataforge.meta
|
||||||
|
|
||||||
|
import hep.dataforge.descriptors.*
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.NameToken
|
||||||
|
import hep.dataforge.names.plus
|
||||||
|
|
||||||
|
open class Scheme() : Configurable, Described {
|
||||||
|
constructor(config: Config, defaultProvider: (Name) -> MetaItem<*>?) : this() {
|
||||||
|
this.config = config
|
||||||
|
this.defaultProvider = defaultProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
//constructor(config: Config, default: Meta) : this(config, { default[it] })
|
||||||
|
constructor(config: Config) : this(config, { null })
|
||||||
|
|
||||||
|
final override lateinit var config: Config
|
||||||
|
internal set
|
||||||
|
|
||||||
|
lateinit var defaultProvider: (Name) -> MetaItem<*>?
|
||||||
|
internal set
|
||||||
|
|
||||||
|
override val descriptor: NodeDescriptor? = null
|
||||||
|
|
||||||
|
override fun getDefaultItem(name: Name): MetaItem<*>? {
|
||||||
|
return defaultProvider(name) ?: descriptor?.get(name)?.defaultItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide a default layer which returns items from [defaultProvider] and falls back to descriptor
|
||||||
|
* values if default value is unavailable.
|
||||||
|
* Values from [defaultProvider] completely replace
|
||||||
|
*/
|
||||||
|
open val defaultLayer: Meta get() = DefaultLayer(Name.EMPTY)
|
||||||
|
|
||||||
|
private inner class DefaultLayer(val path: Name) : MetaBase() {
|
||||||
|
override val items: Map<NameToken, MetaItem<*>> =
|
||||||
|
(descriptor?.get(path) as? NodeDescriptor)?.items?.entries?.associate { (key, descriptor) ->
|
||||||
|
val token = NameToken(key)
|
||||||
|
val fullName = path + token
|
||||||
|
val item: MetaItem<*> = when (descriptor) {
|
||||||
|
is ValueDescriptor -> getDefaultItem(fullName) ?: descriptor.defaultItem()
|
||||||
|
is NodeDescriptor -> MetaItem.NodeItem(DefaultLayer(fullName))
|
||||||
|
}
|
||||||
|
token to item
|
||||||
|
} ?: emptyMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A specification for simplified generation of wrappers
|
||||||
|
*/
|
||||||
|
open class SchemeSpec<T : Scheme>(val builder: () -> T) : Specification<T> {
|
||||||
|
override fun wrap(config: Config, defaultProvider: (Name) -> MetaItem<*>?): T {
|
||||||
|
return builder().apply {
|
||||||
|
this.config = config
|
||||||
|
this.defaultProvider = defaultProvider
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open class MetaScheme(
|
||||||
|
val meta: Meta,
|
||||||
|
override val descriptor: NodeDescriptor? = null,
|
||||||
|
config: Config = Config()
|
||||||
|
) : Scheme(config, meta::get) {
|
||||||
|
override val defaultLayer: Meta get() = meta
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Meta.toScheme() = MetaScheme(this)
|
||||||
|
|
||||||
|
fun <T : Configurable> Meta.toScheme(spec: Specification<T>, block: T.() -> Unit) = spec.wrap(this).apply(block)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a snapshot laminate
|
||||||
|
*/
|
||||||
|
fun Scheme.toMeta(): Laminate = Laminate(config, defaultLayer)
|
@ -1,101 +0,0 @@
|
|||||||
package hep.dataforge.meta
|
|
||||||
|
|
||||||
import hep.dataforge.names.Name
|
|
||||||
import hep.dataforge.names.asName
|
|
||||||
import kotlin.jvm.JvmName
|
|
||||||
import kotlin.properties.ReadWriteProperty
|
|
||||||
import kotlin.reflect.KProperty
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Marker interface for classes with specifications
|
|
||||||
*/
|
|
||||||
interface Specific : Configurable
|
|
||||||
|
|
||||||
//TODO separate mutable config from immutable meta to allow free wrapping of meta
|
|
||||||
|
|
||||||
operator fun Specific.get(name: String): MetaItem<*>? = config[name]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Editor for specific objects
|
|
||||||
*/
|
|
||||||
inline operator fun <S : Specific> S.invoke(block: S.() -> Unit): Unit {
|
|
||||||
run(block)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows to apply custom configuration in a type safe way to simple untyped configuration.
|
|
||||||
* By convention [Specific] companion should inherit this class
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
interface Specification<T : Specific> {
|
|
||||||
/**
|
|
||||||
* Update given configuration using given type as a builder
|
|
||||||
*/
|
|
||||||
fun update(config: Config, action: T.() -> Unit): T {
|
|
||||||
return wrap(config).apply(action)
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun invoke(action: T.() -> Unit) = update(Config(), action)
|
|
||||||
|
|
||||||
fun empty() = wrap(Config())
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wrap generic configuration producing instance of desired type
|
|
||||||
*/
|
|
||||||
fun wrap(config: Config): T
|
|
||||||
|
|
||||||
//TODO replace by free wrapper
|
|
||||||
fun wrap(meta: Meta): T = wrap(meta.toConfig())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T : Specific> specification(wrapper: (Config) -> T): Specification<T> =
|
|
||||||
object : Specification<T> {
|
|
||||||
override fun wrap(config: Config): T = wrapper(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply specified configuration to configurable
|
|
||||||
*/
|
|
||||||
fun <T : Configurable, C : Specific, S : Specification<C>> T.configure(spec: S, action: C.() -> Unit) =
|
|
||||||
apply { spec.update(config, action) }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update configuration using given specification
|
|
||||||
*/
|
|
||||||
fun <C : Specific, S : Specification<C>> Specific.update(spec: S, action: C.() -> Unit) =
|
|
||||||
apply { spec.update(config, action) }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a style based on given specification
|
|
||||||
*/
|
|
||||||
fun <C : Specific, S : Specification<C>> S.createStyle(action: C.() -> Unit): Meta =
|
|
||||||
Config().also { update(it, action) }
|
|
||||||
|
|
||||||
class SpecDelegate<T : Specific, S : Specification<T>>(
|
|
||||||
val target: Specific,
|
|
||||||
val spec: S,
|
|
||||||
val key: Name? = null
|
|
||||||
) : ReadWriteProperty<Any?, T> {
|
|
||||||
|
|
||||||
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
|
|
||||||
val name = key ?: property.name.asName()
|
|
||||||
return target.config[name]?.node?.let { spec.wrap(it) } ?: (spec.empty().also {
|
|
||||||
target.config[name] = it.config
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
|
||||||
target.config[key ?: property.name.asName()] = value.config
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T : Specific, S : Specification<T>> Specific.spec(
|
|
||||||
spec: S,
|
|
||||||
key: Name? = null
|
|
||||||
): SpecDelegate<T, S> = SpecDelegate(this, spec, key)
|
|
||||||
|
|
||||||
fun <T : Specific> MetaItem<*>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(it) }
|
|
||||||
|
|
||||||
@JvmName("configSpec")
|
|
||||||
fun <T : Specific> MetaItem<Config>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(it) }
|
|
||||||
|
|
@ -0,0 +1,60 @@
|
|||||||
|
package hep.dataforge.meta
|
||||||
|
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import kotlin.jvm.JvmName
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows to apply custom configuration in a type safe way to simple untyped configuration.
|
||||||
|
* By convention [Scheme] companion should inherit this class
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
interface Specification<T : Configurable> {
|
||||||
|
/**
|
||||||
|
* Update given configuration using given type as a builder
|
||||||
|
*/
|
||||||
|
fun update(config: Config, action: T.() -> Unit): T {
|
||||||
|
return wrap(config).apply(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun invoke(action: T.() -> Unit) = update(Config(), action)
|
||||||
|
|
||||||
|
fun empty() = wrap()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap generic configuration producing instance of desired type
|
||||||
|
*/
|
||||||
|
fun wrap(config: Config = Config(), defaultProvider: (Name) -> MetaItem<*>? = { null }): T
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap a configuration using static meta as default
|
||||||
|
*/
|
||||||
|
fun wrap(config: Config = Config(), default: Meta): T = wrap(config){default[it]}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap a configuration using static meta as default
|
||||||
|
*/
|
||||||
|
fun wrap(default: Meta): T = wrap(Config()){default[it]}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply specified configuration to configurable
|
||||||
|
*/
|
||||||
|
fun <T : Configurable, C : Configurable, S : Specification<C>> T.configure(spec: S, action: C.() -> Unit) =
|
||||||
|
apply { spec.update(config, action) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update configuration using given specification
|
||||||
|
*/
|
||||||
|
fun <C : Configurable, S : Specification<C>> Configurable.update(spec: S, action: C.() -> Unit) =
|
||||||
|
apply { spec.update(config, action) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a style based on given specification
|
||||||
|
*/
|
||||||
|
fun <C : Configurable, S : Specification<C>> S.createStyle(action: C.() -> Unit): Meta =
|
||||||
|
Config().also { update(it, action) }
|
||||||
|
|
||||||
|
fun <T : Configurable> MetaItem<*>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(Config(), it) }
|
||||||
|
|
||||||
|
@JvmName("configSpec")
|
||||||
|
fun <T : Configurable> MetaItem<Config>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(it) }
|
@ -1,72 +0,0 @@
|
|||||||
package hep.dataforge.meta
|
|
||||||
|
|
||||||
import hep.dataforge.names.Name
|
|
||||||
import hep.dataforge.names.NameToken
|
|
||||||
import kotlin.properties.ReadWriteProperty
|
|
||||||
import kotlin.reflect.KProperty
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A meta object with read-only meta base and changeable configuration on top of it
|
|
||||||
* @param base - unchangeable base
|
|
||||||
* @param style - the style
|
|
||||||
*/
|
|
||||||
class Styled(val base: Meta, val style: Config = Config().empty()) : AbstractMutableMeta<Styled>() {
|
|
||||||
override fun wrapNode(meta: Meta): Styled = Styled(meta)
|
|
||||||
|
|
||||||
override fun empty(): Styled = Styled(EmptyMeta)
|
|
||||||
|
|
||||||
override val items: Map<NameToken, MetaItem<Styled>>
|
|
||||||
get() = (base.items.keys + style.items.keys).associate { key ->
|
|
||||||
val value = base.items[key]
|
|
||||||
val styleValue = style[key]
|
|
||||||
val item: MetaItem<Styled> = when (value) {
|
|
||||||
null -> when (styleValue) {
|
|
||||||
null -> error("Should be unreachable")
|
|
||||||
is MetaItem.NodeItem -> MetaItem.NodeItem(Styled(style.empty(), styleValue.node))
|
|
||||||
is MetaItem.ValueItem -> styleValue
|
|
||||||
}
|
|
||||||
is MetaItem.ValueItem -> value
|
|
||||||
is MetaItem.NodeItem -> MetaItem.NodeItem(
|
|
||||||
Styled(value.node, styleValue?.node ?: Config.empty())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
key to item
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun set(name: Name, item: MetaItem<*>?) {
|
|
||||||
if (item == null) {
|
|
||||||
style.remove(name)
|
|
||||||
} else {
|
|
||||||
style[name] = item
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onChange(owner: Any?, action: (Name, before: MetaItem<*>?, after: MetaItem<*>?) -> Unit) {
|
|
||||||
//TODO test correct behavior
|
|
||||||
style.onChange(owner) { name, before, after -> action(name, before ?: base[name], after ?: base[name]) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removeListener(owner: Any?) {
|
|
||||||
style.removeListener(owner)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Styled.configure(meta: Meta) = apply { style.update(meta) }
|
|
||||||
|
|
||||||
fun Meta.withStyle(style: Meta = EmptyMeta) = if (this is Styled) {
|
|
||||||
this.apply { this.configure(style) }
|
|
||||||
} else {
|
|
||||||
Styled(this, style.toConfig())
|
|
||||||
}
|
|
||||||
|
|
||||||
class StyledNodeDelegate(val owner: Styled, val key: String?) : ReadWriteProperty<Any?, Meta> {
|
|
||||||
override fun getValue(thisRef: Any?, property: KProperty<*>): Meta {
|
|
||||||
return owner[key ?: property.name]?.node ?: EmptyMeta
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Meta) {
|
|
||||||
owner.style[key ?: property.name] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,137 +0,0 @@
|
|||||||
package hep.dataforge.meta
|
|
||||||
|
|
||||||
import hep.dataforge.names.Name
|
|
||||||
import hep.dataforge.values.DoubleArrayValue
|
|
||||||
import hep.dataforge.values.Null
|
|
||||||
import hep.dataforge.values.Value
|
|
||||||
import kotlin.jvm.JvmName
|
|
||||||
|
|
||||||
|
|
||||||
//Configurable delegates
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A property delegate that uses custom key
|
|
||||||
*/
|
|
||||||
fun Configurable.value(default: Any = Null, key: Name? = null): MutableValueDelegate<Config> =
|
|
||||||
MutableValueDelegate(config, key, Value.of(default))
|
|
||||||
|
|
||||||
fun <T> Configurable.value(
|
|
||||||
default: T? = null,
|
|
||||||
key: Name? = null,
|
|
||||||
writer: (T) -> Value = { Value.of(it) },
|
|
||||||
reader: (Value?) -> T
|
|
||||||
): ReadWriteDelegateWrapper<Value?, T> =
|
|
||||||
MutableValueDelegate(config, key, default?.let { Value.of(it) }).transform(reader = reader, writer = writer)
|
|
||||||
|
|
||||||
fun Configurable.string(default: String? = null, key: Name? = null): MutableStringDelegate<Config> =
|
|
||||||
MutableStringDelegate(config, key, default)
|
|
||||||
|
|
||||||
fun Configurable.boolean(default: Boolean? = null, key: Name? = null): MutableBooleanDelegate<Config> =
|
|
||||||
MutableBooleanDelegate(config, key, default)
|
|
||||||
|
|
||||||
fun Configurable.number(default: Number? = null, key: Name? = null): MutableNumberDelegate<Config> =
|
|
||||||
MutableNumberDelegate(config, key, default)
|
|
||||||
|
|
||||||
/* Number delegates*/
|
|
||||||
|
|
||||||
fun Configurable.int(default: Int? = null, key: Name? = null) =
|
|
||||||
number(default, key).int
|
|
||||||
|
|
||||||
fun Configurable.double(default: Double? = null, key: Name? = null) =
|
|
||||||
number(default, key).double
|
|
||||||
|
|
||||||
fun Configurable.long(default: Long? = null, key: Name? = null) =
|
|
||||||
number(default, key).long
|
|
||||||
|
|
||||||
fun Configurable.short(default: Short? = null, key: Name? = null) =
|
|
||||||
number(default, key).short
|
|
||||||
|
|
||||||
fun Configurable.float(default: Float? = null, key: Name? = null) =
|
|
||||||
number(default, key).float
|
|
||||||
|
|
||||||
|
|
||||||
@JvmName("safeString")
|
|
||||||
fun Configurable.string(default: String, key: Name? = null) =
|
|
||||||
MutableSafeStringDelegate(config, key) { default }
|
|
||||||
|
|
||||||
@JvmName("safeBoolean")
|
|
||||||
fun Configurable.boolean(default: Boolean, key: Name? = null) =
|
|
||||||
MutableSafeBooleanDelegate(config, key) { default }
|
|
||||||
|
|
||||||
@JvmName("safeNumber")
|
|
||||||
fun Configurable.number(default: Number, key: Name? = null) =
|
|
||||||
MutableSafeNumberDelegate(config, key) { default }
|
|
||||||
|
|
||||||
@JvmName("safeString")
|
|
||||||
fun Configurable.string(key: Name? = null, default: () -> String) =
|
|
||||||
MutableSafeStringDelegate(config, key, default)
|
|
||||||
|
|
||||||
@JvmName("safeBoolean")
|
|
||||||
fun Configurable.boolean(key: Name? = null, default: () -> Boolean) =
|
|
||||||
MutableSafeBooleanDelegate(config, key, default)
|
|
||||||
|
|
||||||
@JvmName("safeNumber")
|
|
||||||
fun Configurable.number(key: Name? = null, default: () -> Number) =
|
|
||||||
MutableSafeNumberDelegate(config, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
/* Safe number delegates*/
|
|
||||||
|
|
||||||
@JvmName("safeInt")
|
|
||||||
fun Configurable.int(default: Int, key: Name? = null) =
|
|
||||||
number(default, key).int
|
|
||||||
|
|
||||||
@JvmName("safeDouble")
|
|
||||||
fun Configurable.double(default: Double, key: Name? = null) =
|
|
||||||
number(default, key).double
|
|
||||||
|
|
||||||
@JvmName("safeLong")
|
|
||||||
fun Configurable.long(default: Long, key: Name? = null) =
|
|
||||||
number(default, key).long
|
|
||||||
|
|
||||||
@JvmName("safeShort")
|
|
||||||
fun Configurable.short(default: Short, key: Name? = null) =
|
|
||||||
number(default, key).short
|
|
||||||
|
|
||||||
@JvmName("safeFloat")
|
|
||||||
fun Configurable.float(default: Float, key: Name? = null) =
|
|
||||||
number(default, key).float
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enum delegate
|
|
||||||
*/
|
|
||||||
inline fun <reified E : Enum<E>> Configurable.enum(default: E, key: Name? = null) =
|
|
||||||
MutableSafeEnumvDelegate(config, key, default) { enumValueOf(it) }
|
|
||||||
|
|
||||||
/* Node delegates */
|
|
||||||
|
|
||||||
fun Configurable.child(key: Name? = null): MutableNodeDelegate<Config> = MutableNodeDelegate(config, key)
|
|
||||||
|
|
||||||
fun <T : Specific> Configurable.spec(spec: Specification<T>, key: Name? = null) =
|
|
||||||
MutableMorphDelegate(config, key) { spec.wrap(it) }
|
|
||||||
|
|
||||||
fun <T : Specific> Configurable.spec(builder: (Config) -> T, key: Name? = null) =
|
|
||||||
MutableMorphDelegate(config, key) { specification(builder).wrap(it) }
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Extra delegates for special cases
|
|
||||||
*/
|
|
||||||
|
|
||||||
fun Configurable.stringList(vararg strings: String, key: Name? = null): ReadWriteDelegateWrapper<Value?, List<String>> =
|
|
||||||
value(strings.asList(), key) { it?.list?.map { value -> value.string } ?: emptyList() }
|
|
||||||
|
|
||||||
fun Configurable.numberList(vararg numbers: Number, key: Name? = null): ReadWriteDelegateWrapper<Value?, List<Number>> =
|
|
||||||
value(numbers.asList(), key) { it?.list?.map { value -> value.number } ?: emptyList() }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A special delegate for double arrays
|
|
||||||
*/
|
|
||||||
fun Configurable.doubleArray(key: Name? = null): ReadWriteDelegateWrapper<Value?, DoubleArray> =
|
|
||||||
value(doubleArrayOf(), key) {
|
|
||||||
(it as? DoubleArrayValue)?.value
|
|
||||||
?: it?.list?.map { value -> value.number.toDouble() }?.toDoubleArray()
|
|
||||||
?: doubleArrayOf()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T : Configurable> Configurable.child(key: Name? = null, converter: (Meta) -> T) =
|
|
||||||
MutableMorphDelegate(config, key, converter)
|
|
@ -0,0 +1,236 @@
|
|||||||
|
package hep.dataforge.meta
|
||||||
|
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.asName
|
||||||
|
import hep.dataforge.values.*
|
||||||
|
import kotlin.jvm.JvmName
|
||||||
|
import kotlin.properties.ReadWriteProperty
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
|
||||||
|
//delegates
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A delegate that uses a [Configurable] object and delegate read and write operations to its properties
|
||||||
|
*/
|
||||||
|
open class ConfigurableDelegate(
|
||||||
|
val owner: Configurable,
|
||||||
|
val key: Name? = null,
|
||||||
|
open val default: MetaItem<*>? = null
|
||||||
|
) : ReadWriteProperty<Any?, MetaItem<*>?> {
|
||||||
|
|
||||||
|
override fun getValue(thisRef: Any?, property: KProperty<*>): MetaItem<*>? {
|
||||||
|
val name = key ?: property.name.asName()
|
||||||
|
return owner.getProperty(name) ?: default
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setValue(thisRef: Any?, property: KProperty<*>, value: MetaItem<*>?) {
|
||||||
|
val name = key ?: property.name.asName()
|
||||||
|
owner.setProperty(name, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> transform(
|
||||||
|
writer: (T) -> MetaItem<*>? = { MetaItem.of(it) },
|
||||||
|
reader: (MetaItem<*>?) -> T
|
||||||
|
): ReadWriteProperty<Any?, T> = object : ReadWriteProperty<Any?, T> {
|
||||||
|
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
|
||||||
|
return reader(this@ConfigurableDelegate.getValue(thisRef, property))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
||||||
|
this@ConfigurableDelegate.setValue(thisRef, property, writer(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LazyConfigurableDelegate(
|
||||||
|
configurable: Configurable,
|
||||||
|
key: Name? = null,
|
||||||
|
defaultProvider: () -> MetaItem<*>? = { null }
|
||||||
|
) : ConfigurableDelegate(configurable, key) {
|
||||||
|
override val default by lazy(defaultProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A property delegate that uses custom key
|
||||||
|
*/
|
||||||
|
fun Configurable.item(default: Any?, key: Name? = null): ConfigurableDelegate =
|
||||||
|
ConfigurableDelegate(this, key, MetaItem.of(default))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generation of item delegate with lazy default.
|
||||||
|
* Lazy default could be used also for validation
|
||||||
|
*/
|
||||||
|
fun Configurable.lazyItem(key: Name? = null, default: () -> Any?): ConfigurableDelegate =
|
||||||
|
LazyConfigurableDelegate(this, key) { default()?.let { MetaItem.of(it) } }
|
||||||
|
|
||||||
|
fun <T> Configurable.item(
|
||||||
|
default: T? = null,
|
||||||
|
key: Name? = null,
|
||||||
|
writer: (T) -> MetaItem<*>? = { MetaItem.of(it) },
|
||||||
|
reader: (MetaItem<*>?) -> T
|
||||||
|
): ReadWriteProperty<Any?, T> =
|
||||||
|
ConfigurableDelegate(this, key, default?.let { MetaItem.of(it) }).transform(reader = reader, writer = writer)
|
||||||
|
|
||||||
|
fun Configurable.value(default: Any? = null, key: Name? = null): ReadWriteProperty<Any?, Value?> =
|
||||||
|
item(default, key).transform { it.value }
|
||||||
|
|
||||||
|
fun <T> Configurable.value(
|
||||||
|
default: T? = null,
|
||||||
|
key: Name? = null,
|
||||||
|
writer: (T) -> Value? = { Value.of(it) },
|
||||||
|
reader: (Value?) -> T
|
||||||
|
): ReadWriteProperty<Any?, T> =
|
||||||
|
ConfigurableDelegate(this, key, default?.let { MetaItem.of(it) }).transform(
|
||||||
|
reader = { reader(it.value) },
|
||||||
|
writer = { writer(it)?.let { MetaItem.ValueItem(it) } }
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Configurable.string(default: String? = null, key: Name? = null): ReadWriteProperty<Any?, String?> =
|
||||||
|
item(default, key).transform { it.value?.string }
|
||||||
|
|
||||||
|
fun Configurable.boolean(default: Boolean? = null, key: Name? = null): ReadWriteProperty<Any?, Boolean?> =
|
||||||
|
item(default, key).transform { it.value?.boolean }
|
||||||
|
|
||||||
|
fun Configurable.number(default: Number? = null, key: Name? = null): ReadWriteProperty<Any?, Number?> =
|
||||||
|
item(default, key).transform { it.value?.number }
|
||||||
|
|
||||||
|
/* Number delegates*/
|
||||||
|
|
||||||
|
fun Configurable.int(default: Int? = null, key: Name? = null): ReadWriteProperty<Any?, Int?> =
|
||||||
|
item(default, key).transform { it.value?.int }
|
||||||
|
|
||||||
|
fun Configurable.double(default: Double? = null, key: Name? = null): ReadWriteProperty<Any?, Double?> =
|
||||||
|
item(default, key).transform { it.value?.double }
|
||||||
|
|
||||||
|
fun Configurable.long(default: Long? = null, key: Name? = null): ReadWriteProperty<Any?, Long?> =
|
||||||
|
item(default, key).transform { it.value?.long }
|
||||||
|
|
||||||
|
fun Configurable.short(default: Short? = null, key: Name? = null): ReadWriteProperty<Any?, Short?> =
|
||||||
|
item(default, key).transform { it.value?.short }
|
||||||
|
|
||||||
|
fun Configurable.float(default: Float? = null, key: Name? = null): ReadWriteProperty<Any?, Float?> =
|
||||||
|
item(default, key).transform { it.value?.float }
|
||||||
|
|
||||||
|
|
||||||
|
@JvmName("safeString")
|
||||||
|
fun Configurable.string(default: String, key: Name? = null): ReadWriteProperty<Any?, String> =
|
||||||
|
item(default, key).transform { it.value!!.string }
|
||||||
|
|
||||||
|
@JvmName("safeBoolean")
|
||||||
|
fun Configurable.boolean(default: Boolean, key: Name? = null): ReadWriteProperty<Any?, Boolean> =
|
||||||
|
item(default, key).transform { it.value!!.boolean }
|
||||||
|
|
||||||
|
@JvmName("safeNumber")
|
||||||
|
fun Configurable.number(default: Number, key: Name? = null): ReadWriteProperty<Any?, Number> =
|
||||||
|
item(default, key).transform { it.value!!.number }
|
||||||
|
|
||||||
|
/* Lazy initializers for values */
|
||||||
|
|
||||||
|
@JvmName("lazyString")
|
||||||
|
fun Configurable.string(key: Name? = null, default: () -> String): ReadWriteProperty<Any?, String> =
|
||||||
|
lazyItem(key, default).transform { it.value!!.string }
|
||||||
|
|
||||||
|
@JvmName("lazyBoolean")
|
||||||
|
fun Configurable.boolean(key: Name? = null, default: () -> Boolean): ReadWriteProperty<Any?, Boolean> =
|
||||||
|
lazyItem(key, default).transform { it.value!!.boolean }
|
||||||
|
|
||||||
|
@JvmName("lazyNumber")
|
||||||
|
fun Configurable.number(key: Name? = null, default: () -> Number): ReadWriteProperty<Any?, Number> =
|
||||||
|
lazyItem(key, default).transform { it.value!!.number }
|
||||||
|
|
||||||
|
/* Safe number delegates*/
|
||||||
|
|
||||||
|
@JvmName("safeInt")
|
||||||
|
fun Configurable.int(default: Int, key: Name? = null): ReadWriteProperty<Any?, Int> =
|
||||||
|
item(default, key).transform { it.value!!.int }
|
||||||
|
|
||||||
|
@JvmName("safeDouble")
|
||||||
|
fun Configurable.double(default: Double, key: Name? = null): ReadWriteProperty<Any?, Double> =
|
||||||
|
item(default, key).transform { it.value!!.double }
|
||||||
|
|
||||||
|
@JvmName("safeLong")
|
||||||
|
fun Configurable.long(default: Long, key: Name? = null): ReadWriteProperty<Any?, Long> =
|
||||||
|
item(default, key).transform { it.value!!.long }
|
||||||
|
|
||||||
|
@JvmName("safeShort")
|
||||||
|
fun Configurable.short(default: Short, key: Name? = null): ReadWriteProperty<Any?, Short> =
|
||||||
|
item(default, key).transform { it.value!!.short }
|
||||||
|
|
||||||
|
@JvmName("safeFloat")
|
||||||
|
fun Configurable.float(default: Float, key: Name? = null): ReadWriteProperty<Any?, Float> =
|
||||||
|
item(default, key).transform { it.value!!.float }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum delegate
|
||||||
|
*/
|
||||||
|
inline fun <reified E : Enum<E>> Configurable.enum(default: E, key: Name? = null): ReadWriteProperty<Any?, E?> =
|
||||||
|
item(default, key).transform { it.enum<E>() }
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Extra delegates for special cases
|
||||||
|
*/
|
||||||
|
|
||||||
|
fun Configurable.stringList(vararg strings: String, key: Name? = null): ReadWriteProperty<Any?, List<String>> =
|
||||||
|
item(listOf(*strings), key) {
|
||||||
|
it?.value?.stringList ?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Configurable.numberList(vararg numbers: Number, key: Name? = null): ReadWriteProperty<Any?, List<Number>> =
|
||||||
|
item(listOf(*numbers), key) { item ->
|
||||||
|
item?.value?.list?.map { it.number } ?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A special delegate for double arrays
|
||||||
|
*/
|
||||||
|
fun Configurable.doubleArray(vararg doubles: Double, key: Name? = null): ReadWriteProperty<Any?, DoubleArray> =
|
||||||
|
item(doubleArrayOf(*doubles), key) {
|
||||||
|
(it.value as? DoubleArrayValue)?.value
|
||||||
|
?: it?.value?.list?.map { value -> value.number.toDouble() }?.toDoubleArray()
|
||||||
|
?: doubleArrayOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Node delegates */
|
||||||
|
|
||||||
|
fun Configurable.nullableConfig(key: Name? = null): ReadWriteProperty<Any?, Config?> =
|
||||||
|
object : ReadWriteProperty<Any?, Config?> {
|
||||||
|
override fun getValue(thisRef: Any?, property: KProperty<*>): Config? {
|
||||||
|
val name = key ?: property.name.asName()
|
||||||
|
return config[name].node
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Config?) {
|
||||||
|
val name = key ?: property.name.asName()
|
||||||
|
config[name] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Configurable.config(key: Name? = null, default: Config.() -> Unit = {}): ReadWriteProperty<Any?, Config> =
|
||||||
|
object : ReadWriteProperty<Any?, Config> {
|
||||||
|
override fun getValue(thisRef: Any?, property: KProperty<*>): Config {
|
||||||
|
val name = key ?: property.name.asName()
|
||||||
|
return config[name].node ?: Config().apply(default).also { config[name] = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Config) {
|
||||||
|
val name = key ?: property.name.asName()
|
||||||
|
config[name] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T : Configurable> Configurable.spec(spec: Specification<T>, key: Name? = null): 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) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
|
||||||
|
val name = key ?: property.name.asName()
|
||||||
|
config[name] = value?.config
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -350,24 +350,6 @@ class MutableNodeDelegate<M : MutableMeta<M>>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MutableMorphDelegate<T : Configurable>(
|
|
||||||
val meta: MutableMeta<*>,
|
|
||||||
private val key: Name? = null,
|
|
||||||
private val converter: (Meta) -> T
|
|
||||||
) : ReadWriteProperty<Any?, T?> {
|
|
||||||
override fun getValue(thisRef: Any?, property: KProperty<*>): T? {
|
|
||||||
return meta[key ?: property.name.asName()]?.node?.let(converter)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
|
|
||||||
if (value == null) {
|
|
||||||
meta.remove(key ?: property.name.asName())
|
|
||||||
} else {
|
|
||||||
meta[key ?: property.name.asName()] = value.config
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ReadWriteDelegateWrapper<T, R>(
|
class ReadWriteDelegateWrapper<T, R>(
|
||||||
val delegate: ReadWriteProperty<Any?, T>,
|
val delegate: ReadWriteProperty<Any?, T>,
|
||||||
val reader: (T) -> R,
|
val reader: (T) -> R,
|
||||||
|
@ -22,6 +22,7 @@ val Value.boolean
|
|||||||
val Value.int get() = number.toInt()
|
val Value.int get() = number.toInt()
|
||||||
val Value.double get() = number.toDouble()
|
val Value.double get() = number.toDouble()
|
||||||
val Value.float get() = number.toFloat()
|
val Value.float get() = number.toFloat()
|
||||||
|
val Value.short get() = number.toShort()
|
||||||
val Value.long get() = number.toLong()
|
val Value.long get() = number.toLong()
|
||||||
|
|
||||||
val Value.stringList: List<String> get() = list.map { it.string }
|
val Value.stringList: List<String> get() = list.map { it.string }
|
||||||
|
@ -13,14 +13,13 @@ class MetaDelegateTest {
|
|||||||
@Test
|
@Test
|
||||||
fun delegateTest() {
|
fun delegateTest() {
|
||||||
|
|
||||||
class InnerSpec(override val config: Config) : Specific {
|
class InnerSpec : Scheme() {
|
||||||
var innerValue by string()
|
var innerValue by string()
|
||||||
}
|
}
|
||||||
|
|
||||||
val innerSpec = specification(::InnerSpec)
|
val innerSpec = object : SchemeSpec<InnerSpec>(::InnerSpec){}
|
||||||
|
|
||||||
val testObject = object : Specific {
|
val testObject = object : Scheme(Config()) {
|
||||||
override val config: Config = Config()
|
|
||||||
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)
|
var enumValue by enum(TestEnum.YES)
|
||||||
|
@ -14,7 +14,7 @@ class MutableMetaTest{
|
|||||||
"b" put 22
|
"b" put 22
|
||||||
"c" put "StringValue"
|
"c" put "StringValue"
|
||||||
}
|
}
|
||||||
}.toConfig()
|
}.asConfig()
|
||||||
|
|
||||||
meta.remove("aNode.c")
|
meta.remove("aNode.c")
|
||||||
assertEquals(meta["aNode.c"], null)
|
assertEquals(meta["aNode.c"], null)
|
||||||
|
@ -4,19 +4,22 @@ import kotlin.test.Test
|
|||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
|
||||||
class StyledTest{
|
class SchemeTest{
|
||||||
@Test
|
@Test
|
||||||
fun testSNS(){
|
fun testMetaScheme(){
|
||||||
val meta = buildMeta {
|
val styled = buildMeta {
|
||||||
repeat(10){
|
repeat(10){
|
||||||
"b.a[$it]" put {
|
"b.a[$it]" put {
|
||||||
"d" put it
|
"d" put it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.seal().withStyle()
|
}.toScheme()
|
||||||
|
|
||||||
|
val meta = styled.toMeta()
|
||||||
|
|
||||||
assertEquals(10, meta.values().count())
|
assertEquals(10, meta.values().count())
|
||||||
|
|
||||||
val bNode = meta["b"].node
|
val bNode = styled.getProperty("b").node
|
||||||
|
|
||||||
val aNodes = bNode?.getIndexed("a")
|
val aNodes = bNode?.getIndexed("a")
|
||||||
|
|
@ -1,21 +1,26 @@
|
|||||||
package hep.dataforge.meta
|
package hep.dataforge.meta
|
||||||
|
|
||||||
|
import hep.dataforge.names.Name
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class SpecificationTest {
|
class SpecificationTest {
|
||||||
class TestSpecific(override val config: Config) : Specific {
|
class TestStyled(config: Config, defaultProvider: (Name) -> MetaItem<*>?) :
|
||||||
|
Scheme(config, defaultProvider) {
|
||||||
var list by numberList(1, 2, 3)
|
var list by numberList(1, 2, 3)
|
||||||
|
|
||||||
companion object : Specification<TestSpecific> {
|
companion object : Specification<TestStyled> {
|
||||||
override fun wrap(config: Config): TestSpecific = TestSpecific(config)
|
override fun wrap(
|
||||||
|
config: Config,
|
||||||
|
defaultProvider: (Name) -> MetaItem<*>?
|
||||||
|
): TestStyled = TestStyled(config, defaultProvider)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testSpecific(){
|
fun testSpecific() {
|
||||||
val testObject = TestSpecific {
|
val testObject = TestStyled {
|
||||||
list = emptyList()
|
list = emptyList()
|
||||||
}
|
}
|
||||||
assertEquals(emptyList(), testObject.list)
|
assertEquals(emptyList(), testObject.list)
|
||||||
|
Loading…
Reference in New Issue
Block a user