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)
[![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)
[![DOI](https://zenodo.org/badge/148831678.svg)](https://zenodo.org/badge/latestdoi/148831678)
# Questions and Answers #

View File

@ -6,7 +6,7 @@ plugins {
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 githubProject by extra("dataforge-core")

View File

@ -4,7 +4,7 @@ import hep.dataforge.meta.*
import hep.dataforge.names.toName
class DataFilter(override val config: Config) : Specific {
class DataFilter : Scheme() {
/**
* A source node for the filter
*/
@ -22,9 +22,7 @@ class DataFilter(override val config: Config) : Specific {
fun isEmpty(): Boolean = config.isEmpty()
companion object : Specification<DataFilter> {
override fun wrap(config: Config): DataFilter = DataFilter(config)
}
companion object : SchemeSpec<DataFilter>(::DataFilter)
}
/**

View File

@ -133,7 +133,7 @@ object ConfigSerializer : KSerializer<Config> {
override val descriptor: SerialDescriptor = MetaSerializer.descriptor
override fun deserialize(decoder: Decoder): Config {
return MetaSerializer.deserialize(decoder).toConfig()
return MetaSerializer.deserialize(decoder).asConfig()
}
override fun serialize(encoder: Encoder, obj: Config) {

View File

@ -1,29 +1,24 @@
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
*/
interface Described {
val descriptor: NodeDescriptor
val descriptor: NodeDescriptor?
companion object {
const val DESCRIPTOR_NODE = "@descriptor"
}
}
/**
* If meta node supplies explicit descriptor, return it, otherwise try to use descriptor node from meta itself
*/
val MetaRepr.descriptor: NodeDescriptor?
get() {
return if (this is Described) {
descriptor
} else {
toMeta()[DESCRIPTOR_NODE].node?.let { NodeDescriptor.wrap(it) }
}
}
///**
// * If meta node supplies explicit descriptor, return it, otherwise try to use descriptor node from meta itself
// */
//val MetaRepr.descriptor: NodeDescriptor?
// get() {
// return if (this is Described) {
// descriptor
// } else {
// 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.NameToken
import hep.dataforge.names.asName
import hep.dataforge.names.isEmpty
import hep.dataforge.values.False
import hep.dataforge.values.True
import hep.dataforge.values.Value
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
@ -30,7 +31,7 @@ sealed class ItemDescriptor(override val config: Config) : Specific {
*
* @return
*/
var attributes by child()
var attributes by config()
/**
* True if the item is required
@ -46,7 +47,7 @@ sealed class ItemDescriptor(override val config: Config) : Specific {
*
* @author Alexander Nozik
*/
class NodeDescriptor(config: Config) : ItemDescriptor(config) {
class NodeDescriptor : ItemDescriptor() {
/**
* True if the node is required
@ -60,7 +61,7 @@ class NodeDescriptor(config: Config) : ItemDescriptor(config) {
*
* @return
*/
var default: Config? by child()
var default: Config? by nullableConfig()
/**
* The map of children node descriptors
@ -134,18 +135,28 @@ class NodeDescriptor(config: Config) : ItemDescriptor(config) {
//override val descriptor: NodeDescriptor = empty("descriptor")
companion object : Specification<NodeDescriptor> {
companion object : SchemeSpec<NodeDescriptor>(::NodeDescriptor) {
// const val ITEM_KEY = "item"
const val NODE_KEY = "node"
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
}
}
/**
* 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
@ -154,7 +165,7 @@ class NodeDescriptor(config: Config) : ItemDescriptor(config) {
*
* @author Alexander Nozik
*/
class ValueDescriptor(config: Config) : ItemDescriptor(config) {
class ValueDescriptor : ItemDescriptor() {
/**
@ -180,8 +191,8 @@ class ValueDescriptor(config: Config) : ItemDescriptor(config) {
*
* @return
*/
var type: List<ValueType> by value {
it?.list?.map { v -> ValueType.valueOf(v.string) } ?: emptyList()
var type: List<ValueType> by item {
it?.value?.list?.map { v -> ValueType.valueOf(v.string) } ?: emptyList()
}
fun type(vararg t: ValueType) {
@ -222,10 +233,7 @@ class ValueDescriptor(config: Config) : ItemDescriptor(config) {
this.allowedValues = v.map { Value.of(it) }
}
companion object : Specification<ValueDescriptor> {
override fun wrap(config: Config): ValueDescriptor = ValueDescriptor(config)
companion object : SchemeSpec<ValueDescriptor>(::ValueDescriptor) {
inline fun <reified E : Enum<E>> enum(name: String) = ValueDescriptor {
type(ValueType.STRING)
this.allowedValues = enumValues<E>().map { Value.of(it.name) }
@ -289,4 +297,4 @@ class ValueDescriptor(config: Config) : ItemDescriptor(config) {
// 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>?) {
if (newItem == null) {
_items.remove(key)
if(oldItem!= null && oldItem is MetaItem.NodeItem<Config>) {
if (oldItem != null && oldItem is MetaItem.NodeItem<Config>) {
oldItem.node.removeListener(this)
}
} else {
@ -57,7 +57,7 @@ class Config : AbstractMutableMeta<Config>() {
/**
* 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()
@ -68,22 +68,12 @@ class Config : AbstractMutableMeta<Config>() {
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 ->
val item = entry.value
builder[entry.key.asName()] = when (item) {
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
import hep.dataforge.names.Name
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() {
@ -17,10 +18,11 @@ class Laminate(layers: List<Meta>) : MetaBase() {
constructor(vararg layers: Meta?) : this(layers.filterNotNull())
override val items: Map<NameToken, MetaItem<Meta>>
get() = layers.map { it.items.keys }.flatten().associateWith { key ->
override val items: Map<NameToken, MetaItem<Meta>> by lazy {
layers.map { it.items.keys }.flatten().associateWith { key ->
layers.asSequence().map { it.items[key] }.filterNotNull().let(replaceRule)
}
}
/**
* 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
*/

View File

@ -5,6 +5,7 @@ import hep.dataforge.meta.MetaItem.NodeItem
import hep.dataforge.meta.MetaItem.ValueItem
import hep.dataforge.names.*
import hep.dataforge.values.EnumValue
import hep.dataforge.values.Null
import hep.dataforge.values.Value
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>() {
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<*>>
override fun toMeta(): Meta = this
override fun toMeta(): Meta = seal()
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.
*
* 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<*>? {
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
*/
operator fun <M : MetaNode<M>> M?.get(name: Name): MetaItem<M>? {
if (this == null) return null
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)
}
}
}
@Suppress("UNCHECKED_CAST")
operator fun <M : MetaNode<M>> M?.get(name: Name): MetaItem<M>? = (this as Meta)[name] as MetaItem<M>?
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)
is MetaItem<*> -> setItem(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))
}
}

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>(
val delegate: ReadWriteProperty<Any?, T>,
val reader: (T) -> R,

View File

@ -22,6 +22,7 @@ val Value.boolean
val Value.int get() = number.toInt()
val Value.double get() = number.toDouble()
val Value.float get() = number.toFloat()
val Value.short get() = number.toShort()
val Value.long get() = number.toLong()
val Value.stringList: List<String> get() = list.map { it.string }

View File

@ -13,14 +13,13 @@ class MetaDelegateTest {
@Test
fun delegateTest() {
class InnerSpec(override val config: Config) : Specific {
class InnerSpec : Scheme() {
var innerValue by string()
}
val innerSpec = specification(::InnerSpec)
val innerSpec = object : SchemeSpec<InnerSpec>(::InnerSpec){}
val testObject = object : Specific {
override val config: Config = Config()
val testObject = object : Scheme(Config()) {
var myValue by string()
var safeValue by double(2.2)
var enumValue by enum(TestEnum.YES)

View File

@ -14,7 +14,7 @@ class MutableMetaTest{
"b" put 22
"c" put "StringValue"
}
}.toConfig()
}.asConfig()
meta.remove("aNode.c")
assertEquals(meta["aNode.c"], null)

View File

@ -4,19 +4,22 @@ import kotlin.test.Test
import kotlin.test.assertEquals
class StyledTest{
class SchemeTest{
@Test
fun testSNS(){
val meta = buildMeta {
fun testMetaScheme(){
val styled = buildMeta {
repeat(10){
"b.a[$it]" put {
"d" put it
}
}
}.seal().withStyle()
}.toScheme()
val meta = styled.toMeta()
assertEquals(10, meta.values().count())
val bNode = meta["b"].node
val bNode = styled.getProperty("b").node
val aNodes = bNode?.getIndexed("a")

View File

@ -1,21 +1,26 @@
package hep.dataforge.meta
import hep.dataforge.names.Name
import kotlin.test.Test
import kotlin.test.assertEquals
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)
companion object : Specification<TestSpecific> {
override fun wrap(config: Config): TestSpecific = TestSpecific(config)
companion object : Specification<TestStyled> {
override fun wrap(
config: Config,
defaultProvider: (Name) -> MetaItem<*>?
): TestStyled = TestStyled(config, defaultProvider)
}
}
@Test
fun testSpecific(){
val testObject = TestSpecific {
fun testSpecific() {
val testObject = TestStyled {
list = emptyList()
}
assertEquals(emptyList(), testObject.list)