diff --git a/README.md b/README.md index 626ee576..a6e6ed99 100644 --- a/README.md +++ b/README.md @@ -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 # diff --git a/build.gradle.kts b/build.gradle.kts index cfa22253..53a45364 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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") diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataFilter.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataFilter.kt index 3770c9a8..08a0ea87 100644 --- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataFilter.kt +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataFilter.kt @@ -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 { - override fun wrap(config: Config): DataFilter = DataFilter(config) - } + companion object : SchemeSpec(::DataFilter) } /** diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt index 31925c9f..a1ac33a5 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt @@ -133,7 +133,7 @@ object ConfigSerializer : KSerializer { 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) { diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/Described.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/Described.kt index f2ab0c00..4bf6c9dd 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/Described.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/Described.kt @@ -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) } - } - } \ No newline at end of file +///** +// * 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) } +// } +// } \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/DescriptorMeta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/DescriptorMeta.kt new file mode 100644 index 00000000..49aef28e --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/DescriptorMeta.kt @@ -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> + 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() + } +} \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt index 93765dd4..99b3e850 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt @@ -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 { + companion object : SchemeSpec(::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 by value { - it?.list?.map { v -> ValueType.valueOf(v.string) } ?: emptyList() + var type: List 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 { - - override fun wrap(config: Config): ValueDescriptor = ValueDescriptor(config) - + companion object : SchemeSpec(::ValueDescriptor) { inline fun > enum(name: String) = ValueDescriptor { type(ValueType.STRING) this.allowedValues = enumValues().map { Value.of(it.name) } @@ -289,4 +297,4 @@ class ValueDescriptor(config: Config) : ItemDescriptor(config) { // return ValueDescriptor(Laminate(primary.meta, secondary.meta)) // } } -} +} \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt index f47d3bcd..087cb077 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt @@ -40,7 +40,7 @@ class Config : AbstractMutableMeta() { override fun replaceItem(key: NameToken, oldItem: MetaItem?, newItem: MetaItem?) { if (newItem == null) { _items.remove(key) - if(oldItem!= null && oldItem is MetaItem.NodeItem) { + if (oldItem != null && oldItem is MetaItem.NodeItem) { oldItem.node.removeListener(this) } } else { @@ -57,7 +57,7 @@ class Config : AbstractMutableMeta() { /** * 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() { operator fun Config.get(token: NameToken): MetaItem? = 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.configure(meta: Meta): T = this.apply { config.update(meta) } - -fun T.configure(action: MetaBuilder.() -> Unit): T = configure(buildMeta(action)) - -open class SimpleConfigurable(override val config: Config) : Configurable \ No newline at end of file +} \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Configurable.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Configurable.kt new file mode 100644 index 00000000..c13a6214 --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Configurable.kt @@ -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.configure(meta: Meta): T = this.apply { config.update(meta) } + +fun T.configure(action: Config.() -> Unit): T = apply { config.apply(action) } diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt index b403544c..c6d69967 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt @@ -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) : MetaBase() { @@ -17,10 +18,11 @@ class Laminate(layers: List) : MetaBase() { constructor(vararg layers: Meta?) : this(layers.filterNotNull()) - override val items: Map> - get() = layers.map { it.items.keys }.flatten().associateWith { key -> + override val items: Map> 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) : 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 */ diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt index 7b980137..d917559d 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt @@ -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 { data class NodeItem(val node: M) : MetaItem() { 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> - 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> : Meta { /** * The same as [Meta.get], but with specific node type */ -operator fun > M?.get(name: Name): MetaItem? { - 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?.get(name: Name): MetaItem? = (this as Meta)[name] as MetaItem? operator fun > M?.get(key: String): MetaItem? = this[key.toName()] diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt index 7a379f24..950df7d6 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt @@ -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)) } } diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Scheme.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Scheme.kt new file mode 100644 index 00000000..2de46dc0 --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Scheme.kt @@ -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> = + (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(val builder: () -> T) : Specification { + 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 Meta.toScheme(spec: Specification, block: T.() -> Unit) = spec.wrap(this).apply(block) + +/** + * Create a snapshot laminate + */ +fun Scheme.toMeta(): Laminate = Laminate(config, defaultLayer) \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt deleted file mode 100644 index 865114e5..00000000 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt +++ /dev/null @@ -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.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 { - /** - * 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 specification(wrapper: (Config) -> T): Specification = - object : Specification { - override fun wrap(config: Config): T = wrapper(config) - } - -/** - * Apply specified configuration to configurable - */ -fun > T.configure(spec: S, action: C.() -> Unit) = - apply { spec.update(config, action) } - -/** - * Update configuration using given specification - */ -fun > Specific.update(spec: S, action: C.() -> Unit) = - apply { spec.update(config, action) } - -/** - * Create a style based on given specification - */ -fun > S.createStyle(action: C.() -> Unit): Meta = - Config().also { update(it, action) } - -class SpecDelegate>( - val target: Specific, - val spec: S, - val key: Name? = null -) : ReadWriteProperty { - - 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 > Specific.spec( - spec: S, - key: Name? = null -): SpecDelegate = SpecDelegate(this, spec, key) - -fun MetaItem<*>.spec(spec: Specification): T? = node?.let { spec.wrap(it) } - -@JvmName("configSpec") -fun MetaItem.spec(spec: Specification): T? = node?.let { spec.wrap(it) } - diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specification.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specification.kt new file mode 100644 index 00000000..9c8b740a --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specification.kt @@ -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 { + /** + * 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.configure(spec: S, action: C.() -> Unit) = + apply { spec.update(config, action) } + +/** + * Update configuration using given specification + */ +fun > Configurable.update(spec: S, action: C.() -> Unit) = + apply { spec.update(config, action) } + +/** + * Create a style based on given specification + */ +fun > S.createStyle(action: C.() -> Unit): Meta = + Config().also { update(it, action) } + +fun MetaItem<*>.spec(spec: Specification): T? = node?.let { spec.wrap(Config(), it) } + +@JvmName("configSpec") +fun MetaItem.spec(spec: Specification): T? = node?.let { spec.wrap(it) } diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Styled.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Styled.kt deleted file mode 100644 index 55d652aa..00000000 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Styled.kt +++ /dev/null @@ -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() { - override fun wrapNode(meta: Meta): Styled = Styled(meta) - - override fun empty(): Styled = Styled(EmptyMeta) - - override val items: Map> - get() = (base.items.keys + style.items.keys).associate { key -> - val value = base.items[key] - val styleValue = style[key] - val item: MetaItem = 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 { - 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 - } - -} \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configDelegates.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configDelegates.kt deleted file mode 100644 index e3dbc9aa..00000000 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configDelegates.kt +++ /dev/null @@ -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 = - MutableValueDelegate(config, key, Value.of(default)) - -fun Configurable.value( - default: T? = null, - key: Name? = null, - writer: (T) -> Value = { Value.of(it) }, - reader: (Value?) -> T -): ReadWriteDelegateWrapper = - MutableValueDelegate(config, key, default?.let { Value.of(it) }).transform(reader = reader, writer = writer) - -fun Configurable.string(default: String? = null, key: Name? = null): MutableStringDelegate = - MutableStringDelegate(config, key, default) - -fun Configurable.boolean(default: Boolean? = null, key: Name? = null): MutableBooleanDelegate = - MutableBooleanDelegate(config, key, default) - -fun Configurable.number(default: Number? = null, key: Name? = null): MutableNumberDelegate = - 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 > Configurable.enum(default: E, key: Name? = null) = - MutableSafeEnumvDelegate(config, key, default) { enumValueOf(it) } - -/* Node delegates */ - -fun Configurable.child(key: Name? = null): MutableNodeDelegate = MutableNodeDelegate(config, key) - -fun Configurable.spec(spec: Specification, key: Name? = null) = - MutableMorphDelegate(config, key) { spec.wrap(it) } - -fun 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(strings.asList(), key) { it?.list?.map { value -> value.string } ?: emptyList() } - -fun Configurable.numberList(vararg numbers: Number, key: Name? = null): ReadWriteDelegateWrapper> = - 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(doubleArrayOf(), key) { - (it as? DoubleArrayValue)?.value - ?: it?.list?.map { value -> value.number.toDouble() }?.toDoubleArray() - ?: doubleArrayOf() - } - -fun Configurable.child(key: Name? = null, converter: (Meta) -> T) = - MutableMorphDelegate(config, key, converter) diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configurableDelegates.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configurableDelegates.kt new file mode 100644 index 00000000..313ddd0b --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configurableDelegates.kt @@ -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?> { + + 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 transform( + writer: (T) -> MetaItem<*>? = { MetaItem.of(it) }, + reader: (MetaItem<*>?) -> T + ): ReadWriteProperty = object : ReadWriteProperty { + 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 Configurable.item( + default: T? = null, + key: Name? = null, + writer: (T) -> MetaItem<*>? = { MetaItem.of(it) }, + reader: (MetaItem<*>?) -> T +): ReadWriteProperty = + ConfigurableDelegate(this, key, default?.let { MetaItem.of(it) }).transform(reader = reader, writer = writer) + +fun Configurable.value(default: Any? = null, key: Name? = null): ReadWriteProperty = + item(default, key).transform { it.value } + +fun Configurable.value( + default: T? = null, + key: Name? = null, + writer: (T) -> Value? = { Value.of(it) }, + reader: (Value?) -> T +): ReadWriteProperty = + 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 = + item(default, key).transform { it.value?.string } + +fun Configurable.boolean(default: Boolean? = null, key: Name? = null): ReadWriteProperty = + item(default, key).transform { it.value?.boolean } + +fun Configurable.number(default: Number? = null, key: Name? = null): ReadWriteProperty = + item(default, key).transform { it.value?.number } + +/* Number delegates*/ + +fun Configurable.int(default: Int? = null, key: Name? = null): ReadWriteProperty = + item(default, key).transform { it.value?.int } + +fun Configurable.double(default: Double? = null, key: Name? = null): ReadWriteProperty = + item(default, key).transform { it.value?.double } + +fun Configurable.long(default: Long? = null, key: Name? = null): ReadWriteProperty = + item(default, key).transform { it.value?.long } + +fun Configurable.short(default: Short? = null, key: Name? = null): ReadWriteProperty = + item(default, key).transform { it.value?.short } + +fun Configurable.float(default: Float? = null, key: Name? = null): ReadWriteProperty = + item(default, key).transform { it.value?.float } + + +@JvmName("safeString") +fun Configurable.string(default: String, key: Name? = null): ReadWriteProperty = + item(default, key).transform { it.value!!.string } + +@JvmName("safeBoolean") +fun Configurable.boolean(default: Boolean, key: Name? = null): ReadWriteProperty = + item(default, key).transform { it.value!!.boolean } + +@JvmName("safeNumber") +fun Configurable.number(default: Number, key: Name? = null): ReadWriteProperty = + item(default, key).transform { it.value!!.number } + +/* Lazy initializers for values */ + +@JvmName("lazyString") +fun Configurable.string(key: Name? = null, default: () -> String): ReadWriteProperty = + lazyItem(key, default).transform { it.value!!.string } + +@JvmName("lazyBoolean") +fun Configurable.boolean(key: Name? = null, default: () -> Boolean): ReadWriteProperty = + lazyItem(key, default).transform { it.value!!.boolean } + +@JvmName("lazyNumber") +fun Configurable.number(key: Name? = null, default: () -> Number): ReadWriteProperty = + lazyItem(key, default).transform { it.value!!.number } + +/* Safe number delegates*/ + +@JvmName("safeInt") +fun Configurable.int(default: Int, key: Name? = null): ReadWriteProperty = + item(default, key).transform { it.value!!.int } + +@JvmName("safeDouble") +fun Configurable.double(default: Double, key: Name? = null): ReadWriteProperty = + item(default, key).transform { it.value!!.double } + +@JvmName("safeLong") +fun Configurable.long(default: Long, key: Name? = null): ReadWriteProperty = + item(default, key).transform { it.value!!.long } + +@JvmName("safeShort") +fun Configurable.short(default: Short, key: Name? = null): ReadWriteProperty = + item(default, key).transform { it.value!!.short } + +@JvmName("safeFloat") +fun Configurable.float(default: Float, key: Name? = null): ReadWriteProperty = + item(default, key).transform { it.value!!.float } + +/** + * Enum delegate + */ +inline fun > Configurable.enum(default: E, key: Name? = null): ReadWriteProperty = + item(default, key).transform { it.enum() } + +/* + * Extra delegates for special cases + */ + +fun Configurable.stringList(vararg strings: String, key: Name? = null): ReadWriteProperty> = + item(listOf(*strings), key) { + it?.value?.stringList ?: emptyList() + } + +fun Configurable.numberList(vararg numbers: Number, key: Name? = null): ReadWriteProperty> = + 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 = + 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 = + object : ReadWriteProperty { + 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 = + object : ReadWriteProperty { + 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 Configurable.spec(spec: Specification, key: Name? = null): ReadWriteProperty = + object : ReadWriteProperty { + 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 + } + + } + diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt index 02caa9fa..7c8d2026 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt @@ -350,24 +350,6 @@ class MutableNodeDelegate>( } } -class MutableMorphDelegate( - val meta: MutableMeta<*>, - private val key: Name? = null, - private val converter: (Meta) -> T -) : ReadWriteProperty { - 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( val delegate: ReadWriteProperty, val reader: (T) -> R, diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/valueExtensions.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/valueExtensions.kt index a63d5ec1..f9137dca 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/valueExtensions.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/valueExtensions.kt @@ -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 get() = list.map { it.string } diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt index 162a3852..93aad8e5 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt @@ -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){} - 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) diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MutableMetaTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MutableMetaTest.kt index 9057782f..194c77e3 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MutableMetaTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MutableMetaTest.kt @@ -14,7 +14,7 @@ class MutableMetaTest{ "b" put 22 "c" put "StringValue" } - }.toConfig() + }.asConfig() meta.remove("aNode.c") assertEquals(meta["aNode.c"], null) diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/StyledTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt similarity index 71% rename from dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/StyledTest.kt rename to dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt index a4cbe18e..c7703a47 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/StyledTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt @@ -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") diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SpecificationTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SpecificationTest.kt index c11c137b..f21c5b2c 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SpecificationTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SpecificationTest.kt @@ -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 { - override fun wrap(config: Config): TestSpecific = TestSpecific(config) + companion object : Specification { + 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)