From a02a5fbc3f6e1c657c1f6847196adcb864a9a7ae Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Tue, 12 Mar 2019 18:48:29 +0300 Subject: [PATCH] Lazy safe delegates for meta values --- .../kotlin/hep/dataforge/data/Action.kt | 3 +- .../kotlin/hep/dataforge/data/Goal.kt | 6 +- .../dataforge/descriptors/NodeDescriptor.kt | 191 +++++++++++++++++ .../dataforge/descriptors/ValueDescriptor.kt | 199 ++++++++++++++++++ .../kotlin/hep/dataforge/meta/Delegates.kt | 86 ++++++-- 5 files changed, 468 insertions(+), 17 deletions(-) create mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/NodeDescriptor.kt create mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ValueDescriptor.kt diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Action.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Action.kt index 599101c0..c5fcfcfc 100644 --- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Action.kt +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Action.kt @@ -20,9 +20,10 @@ interface Action { } /** - * Action composition. The result is terminal if one of parts is terminal + * Action composition. The result is terminal if one of its parts is terminal */ infix fun Action.then(action: Action): Action { + // TODO introduce composite action and add optimize by adding action to the list return object : Action { override fun invoke(node: DataNode, meta: Meta): DataNode { return action(this@then.invoke(node, meta), meta) diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Goal.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Goal.kt index 104a9037..1441a40e 100644 --- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Goal.kt +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Goal.kt @@ -28,7 +28,9 @@ interface Goal : Deferred, CoroutineScope { /** * A monitor of goal state that could be accessed only form inside the goal */ -class GoalMonitor { +class GoalMonitor : CoroutineContext.Element { + override val key: CoroutineContext.Key<*> get() = GoalMonitor + var totalWork: Double = 1.0 var workDone: Double = 0.0 var status: String = "" @@ -46,6 +48,8 @@ class GoalMonitor { fun finish() { workDone = totalWork } + + companion object : CoroutineContext.Key } private class GoalImpl( diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/NodeDescriptor.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/NodeDescriptor.kt new file mode 100644 index 00000000..dd6532a0 --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/NodeDescriptor.kt @@ -0,0 +1,191 @@ +/* + * Copyright 2018 Alexander Nozik. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package hep.dataforge.descriptors + +import hep.dataforge.Named +import hep.dataforge.description.ValueDescriptor +import hep.dataforge.meta.* +import hep.dataforge.names.Name +import java.util.* + +/** + * Descriptor for meta node. Could contain additional information for viewing + * and editing. + * + * @author Alexander Nozik + */ +open class NodeDescriptor(val meta: Meta) : MetaRepr { + + /** + * True if multiple children with this nodes name are allowed. Anonymous + * nodes are always single + * + * @return + */ + val multiple: Boolean by meta.boolean(false) + + /** + * True if the node is required + * + * @return + */ + val required: Boolean by meta.boolean(false) + + /** + * The node description + * + * @return + */ + open val info: String by meta.string("") + + /** + * A list of tags for this node. Tags used to customize node usage + * + * @return + */ + val tags: List by customValue(def = emptyList()) { it.list.map { it.string } } + + /** + * The name of this node + * + * @return + */ + override val name: String by stringValue(def = meta.name) + + /** + * The list of value descriptors + * + * @return + */ + fun valueDescriptors(): Map { + val map = HashMap() + if (meta.hasMeta("value")) { + for (valueNode in meta.getMetaList("value")) { + val vd = ValueDescriptor(valueNode) + map[vd.name] = vd + } + } + return map + } + + /** + * The child node descriptor for given name. Name syntax is supported. + * + * @param name + * @return + */ + fun getNodeDescriptor(name: String): NodeDescriptor? { + return getNodeDescriptor(Name.of(name)) + } + + fun getNodeDescriptor(name: Name): NodeDescriptor? { + return if (name.length == 1) { + childrenDescriptors()[name.unescaped] + } else { + getNodeDescriptor(name.cutLast())?.getNodeDescriptor(name.last) + } + } + + /** + * The value descriptor for given value name. Name syntax is supported. + * + * @param name + * @return + */ + fun getValueDescriptor(name: String): ValueDescriptor? { + return getValueDescriptor(Name.of(name)) + } + + fun getValueDescriptor(name: Name): ValueDescriptor? { + return if (name.length == 1) { + valueDescriptors()[name.unescaped] + } else { + getNodeDescriptor(name.cutLast())?.getValueDescriptor(name.last) + } + } + + /** + * The map of children node descriptors + * + * @return + */ + fun childrenDescriptors(): Map { + val map = HashMap() + if (meta.hasMeta("node")) { + for (node in meta.getMetaList("node")) { + val nd = NodeDescriptor(node) + map[nd.name] = nd + } + } + return map + } + + /** + * Check if this node has default + * + * @return + */ + fun hasDefault(): Boolean { + return meta.hasMeta("default") + } + + /** + * The default meta for this node (could be multiple). Null if not defined + * + * @return + */ + val default: List by nodeList(def = emptyList()) + + /** + * Identify if this descriptor has child value descriptor with default + * + * @param name + * @return + */ + fun hasDefaultForValue(name: String): Boolean { + return getValueDescriptor(name)?.hasDefault() ?: false + } + + /** + * The key of the value which is used to display this node in case it is + * multiple. By default, the key is empty which means that node index is + * used. + * + * @return + */ + val key: String by stringValue(def = "") + + override fun toMeta(): Meta { + return meta + } + + fun builder(): DescriptorBuilder = DescriptorBuilder(this.name, Configuration(this.meta)) + + //override val descriptor: NodeDescriptor = empty("descriptor") + + companion object { + + fun empty(nodeName: String): NodeDescriptor { + return NodeDescriptor(Meta.buildEmpty(nodeName)) + } + } +} diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ValueDescriptor.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ValueDescriptor.kt new file mode 100644 index 00000000..4ee4e351 --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ValueDescriptor.kt @@ -0,0 +1,199 @@ +/* + * Copyright 2018 Alexander Nozik. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package hep.dataforge.description + +import hep.dataforge.Named +import hep.dataforge.meta.* +import hep.dataforge.names.AnonymousNotAlowed +import hep.dataforge.values.BooleanValue +import hep.dataforge.values.Value +import hep.dataforge.values.ValueType + +/** + * A descriptor for meta value + * + * Descriptor can have non-atomic path. It is resolved when descriptor is added to the node + * + * @author Alexander Nozik + */ +class ValueDescriptor(val meta: Meta) : MetaRepr { + + /** + * The default for this value. Null if there is no default. + * + * @return + */ + val default by meta.value() + + /** + * True if multiple values with this name are allowed. + * + * @return + */ + val multiple: Boolean by meta.boolean(false) + + /** + * True if the value is required + * + * @return + */ + val required: Boolean by meta.boolean(default == null) + + /** + * Value name + * + * @return + */ + val name: String by meta.string{ error("Anonimous descriptors are not allowed")} + + /** + * The value info + * + * @return + */ + val info: String by stringValue(def = "") + + /** + * A list of allowed ValueTypes. Empty if any value type allowed + * + * @return + */ + val type: List by customValue(def = emptyList()) { + it.list.map { v -> ValueType.valueOf(v.string) } + } + + val tags: List by customValue(def = emptyList()) { + meta.getStringArray("tags").toList() + } + + /** + * Check if given value is allowed for here. The type should be allowed and + * if it is value should be within allowed values + * + * @param value + * @return + */ + fun isValueAllowed(value: Value): Boolean { + return (type.isEmpty() || type.contains(ValueType.STRING) || type.contains(value.type)) && (allowedValues.isEmpty() || allowedValues.contains( + value + )) + } + + /** + * A list of allowed values with descriptions. If empty than any value is + * allowed. + * + * @return + */ + val allowedValues: List by customValue( + def = if (type.size == 1 && type[0] === ValueType.BOOLEAN) { + listOf(BooleanValue.TRUE, BooleanValue.FALSE) + } else { + emptyList() + } + ) { it.list } + + companion object { + + /** + * Build a value descriptor from annotation + */ + fun build(def: ValueDef): ValueDescriptor { + val builder = MetaBuilder("value") + .setValue("name", def.key) + + if (def.type.isNotEmpty()) { + builder.setValue("type", def.type) + } + + if (def.multiple) { + builder.setValue("multiple", def.multiple) + } + + if (!def.info.isEmpty()) { + builder.setValue("info", def.info) + } + + if (def.allowed.isNotEmpty()) { + builder.setValue("allowedValues", def.allowed) + } else if (def.enumeration != Any::class) { + if (def.enumeration.java.isEnum) { + val values = def.enumeration.java.enumConstants + builder.setValue("allowedValues", values.map { it.toString() }) + } else { + throw RuntimeException("Only enumeration classes are allowed in 'enumeration' annotation property") + } + } + + if (def.def.isNotEmpty()) { + builder.setValue("default", def.def) + } else if (!def.required) { + builder.setValue("required", def.required) + } + + if (def.tags.isNotEmpty()) { + builder.setValue("tags", def.tags) + } + return ValueDescriptor(builder) + } + + /** + * Build a value descriptor from its fields + */ + fun build( + name: String, + info: String = "", + defaultValue: Any? = null, + required: Boolean = false, + multiple: Boolean = false, + types: List = emptyList(), + allowedValues: List = emptyList() + ): ValueDescriptor { + val valueBuilder = buildMeta("value") { + "name" to name + if (!types.isEmpty()) "type" to types + if (required) "required" to required + if (multiple) "multiple" to multiple + if (!info.isEmpty()) "info" to info + if (defaultValue != null) "default" to defaultValue + if (!allowedValues.isEmpty()) "allowedValues" to allowedValues + }.build() + return ValueDescriptor(valueBuilder) + } + + /** + * Build empty value descriptor + */ + fun empty(valueName: String): ValueDescriptor { + val builder = MetaBuilder("value") + .setValue("name", valueName) + return ValueDescriptor(builder) + } + + /** + * Merge two separate value descriptors + */ + fun merge(primary: ValueDescriptor, secondary: ValueDescriptor): ValueDescriptor { + return ValueDescriptor(Laminate(primary.meta, secondary.meta)) + } + } +} diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Delegates.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Delegates.kt index 5672d2cf..5a6c8aea 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Delegates.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Delegates.kt @@ -56,22 +56,40 @@ class DelegateWrapper(val delegate: ReadOnlyProperty, val reader: //Delegates with non-null values -class SafeStringDelegate(val meta: Meta, private val key: String? = null, private val default: String) : - ReadOnlyProperty { +class SafeStringDelegate( + val meta: Meta, + private val key: String? = null, + default: () -> String +) : ReadOnlyProperty { + + private val default: String by lazy(default) + override fun getValue(thisRef: Any?, property: KProperty<*>): String { return meta[key ?: property.name]?.string ?: default } } -class SafeBooleanDelegate(val meta: Meta, private val key: String? = null, private val default: Boolean) : - ReadOnlyProperty { +class SafeBooleanDelegate( + val meta: Meta, + private val key: String? = null, + default: () -> Boolean +) : ReadOnlyProperty { + + private val default: Boolean by lazy(default) + override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean { return meta[key ?: property.name]?.boolean ?: default } } -class SafeNumberDelegate(val meta: Meta, private val key: String? = null, private val default: Number) : - ReadOnlyProperty { +class SafeNumberDelegate( + val meta: Meta, + private val key: String? = null, + default: () -> Number +) : ReadOnlyProperty { + + private val default: Number by lazy(default) + override fun getValue(thisRef: Any?, property: KProperty<*>): Number { return meta[key ?: property.name]?.number ?: default } @@ -118,13 +136,29 @@ fun Meta.number(default: Number? = null, key: String? = null) = NumberDelegate(t fun Meta.child(key: String? = null) = ChildDelegate(this, key) { it } @JvmName("safeString") -fun Meta.string(default: String, key: String? = null) = SafeStringDelegate(this, key, default) +fun Meta.string(default: String, key: String? = null) = + SafeStringDelegate(this, key) { default } @JvmName("safeBoolean") -fun Meta.boolean(default: Boolean, key: String? = null) = SafeBooleanDelegate(this, key, default) +fun Meta.boolean(default: Boolean, key: String? = null) = + SafeBooleanDelegate(this, key) { default } @JvmName("safeNumber") -fun Meta.number(default: Number, key: String? = null) = SafeNumberDelegate(this, key, default) +fun Meta.number(default: Number, key: String? = null) = + SafeNumberDelegate(this, key) { default } + +@JvmName("safeString") +fun Meta.string(key: String? = null, default: () -> String) = + SafeStringDelegate(this, key, default) + +@JvmName("safeBoolean") +fun Meta.boolean(key: String? = null, default: () -> Boolean) = + SafeBooleanDelegate(this, key, default) + +@JvmName("safeNumber") +fun Meta.number(key: String? = null, default: () -> Number) = + SafeNumberDelegate(this, key, default) + inline fun > Meta.enum(default: E, key: String? = null) = SafeEnumDelegate(this, key, default) { enumValueOf(it) } @@ -218,8 +252,11 @@ class NumberConfigDelegate>( class SafeStringConfigDelegate>( val config: M, private val key: String? = null, - private val default: String + default: () -> String ) : ReadWriteProperty { + + private val default: String by lazy(default) + override fun getValue(thisRef: Any?, property: KProperty<*>): String { return config[key ?: property.name]?.string ?: default } @@ -232,8 +269,11 @@ class SafeStringConfigDelegate>( class SafeBooleanConfigDelegate>( val config: M, private val key: String? = null, - private val default: Boolean + default: () -> Boolean ) : ReadWriteProperty { + + private val default: Boolean by lazy(default) + override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean { return config[key ?: property.name]?.boolean ?: default } @@ -246,8 +286,11 @@ class SafeBooleanConfigDelegate>( class SafeNumberConfigDelegate>( val config: M, private val key: String? = null, - private val default: Number + default: () -> Number ) : ReadWriteProperty { + + private val default: Number by lazy(default) + override fun getValue(thisRef: Any?, property: KProperty<*>): Number { return config[key ?: property.name]?.number ?: default } @@ -345,15 +388,28 @@ fun > M.child(key: String? = null) = MetaNodeDelegate(thi @JvmName("safeString") fun > M.string(default: String, key: String? = null) = - SafeStringConfigDelegate(this, key, default) + SafeStringConfigDelegate(this, key) { default } @JvmName("safeBoolean") fun > M.boolean(default: Boolean, key: String? = null) = - SafeBooleanConfigDelegate(this, key, default) + SafeBooleanConfigDelegate(this, key) { default } @JvmName("safeNumber") fun > M.number(default: Number, key: String? = null) = + SafeNumberConfigDelegate(this, key) { default } + +@JvmName("safeString") +fun > M.string(key: String? = null, default: () -> String) = + SafeStringConfigDelegate(this, key, default) + +@JvmName("safeBoolean") +fun > M.boolean(key: String? = null, default: () -> Boolean) = + SafeBooleanConfigDelegate(this, key, default) + +@JvmName("safeNumber") +fun > M.number(key: String? = null, default: () -> Number) = SafeNumberConfigDelegate(this, key, default) + inline fun , reified E : Enum> M.enum(default: E, key: String? = null) = - SafeEnumvConfigDelegate(this, key, default) { enumValueOf(it) } \ No newline at end of file + SafeEnumvConfigDelegate(this, key, default) { enumValueOf(it) }