Configurable and Scheme revision

This commit is contained in:
Alexander Nozik 2020-01-08 21:41:27 +03:00
parent 736ec621b0
commit b83821af51
24 changed files with 548 additions and 413 deletions

View File

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

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-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")

View File

@ -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)
}
} }
/** /**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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