Compare commits

...

4 Commits

28 changed files with 573 additions and 701 deletions

View File

@ -8,6 +8,8 @@
- Add Meta and MutableMeta delegates for convertable and serializeable - Add Meta and MutableMeta delegates for convertable and serializeable
### Changed ### Changed
- Descriptor `children` renamed to `nodes`
- `MetaConverter` now inherits `MetaSpec` (former `Specifiction`). So `MetaConverter` could be used more universally.
### Deprecated ### Deprecated
- `node(key,converter)` in favor of `serializable` delegate - `node(key,converter)` in favor of `serializable` delegate

View File

@ -8,7 +8,7 @@ plugins {
allprojects { allprojects {
group = "space.kscience" group = "space.kscience"
version = "0.7.2-dev-2" version = "0.8.0-dev-1"
} }
subprojects { subprojects {

View File

@ -1,35 +0,0 @@
package space.kscience.dataforge.properties
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.ObservableMutableMeta
import space.kscience.dataforge.meta.transformations.MetaConverter
import space.kscience.dataforge.meta.transformations.nullableMetaToObject
import space.kscience.dataforge.meta.transformations.nullableObjectToMeta
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.startsWith
@DFExperimental
public class MetaProperty<T : Any>(
public val meta: ObservableMutableMeta,
public val name: Name,
public val converter: MetaConverter<T>,
) : Property<T?> {
override var value: T?
get() = converter.nullableMetaToObject(meta[name])
set(value) {
meta[name] = converter.nullableObjectToMeta(value) ?: Meta.EMPTY
}
override fun onChange(owner: Any?, callback: (T?) -> Unit) {
meta.onChange(owner) { name ->
if (name.startsWith(this@MetaProperty.name)) callback(converter.nullableMetaToObject(this[name]))
}
}
override fun removeChangeListener(owner: Any?) {
meta.removeListener(owner)
}
}

View File

@ -1,47 +0,0 @@
package space.kscience.dataforge.properties
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import space.kscience.dataforge.misc.DFExperimental
@DFExperimental
public interface Property<T> {
public var value: T
public fun onChange(owner: Any? = null, callback: (T) -> Unit)
public fun removeChangeListener(owner: Any? = null)
}
@DFExperimental
@OptIn(ExperimentalCoroutinesApi::class)
public fun <T> Property<T>.toFlow(): StateFlow<T> = MutableStateFlow(value).also { stateFlow ->
onChange {
stateFlow.value = it
}
}
/**
* Reflect all changes in the [source] property onto this property. Does not reflect changes back.
*
* @return a mirroring job
*/
@DFExperimental
public fun <T> Property<T>.mirror(source: Property<T>) {
source.onChange(this) {
this.value = it
}
}
/**
* Bi-directional connection between properties
*/
@DFExperimental
public fun <T> Property<T>.bind(other: Property<T>) {
onChange(other) {
other.value = it
}
other.onChange {
this.value = it
}
}

View File

@ -0,0 +1,51 @@
package space.kscience.dataforge.properties
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.misc.DFExperimental
@DFExperimental
public fun <T> ObservableMeta.asFlow(converter: MetaSpec<T>): Flow<T> = callbackFlow {
onChange(this){
trySend(converter.read(this))
}
awaitClose{
removeListener(this)
}
}
@DFExperimental
public fun <T> MutableMeta.listenTo(
scope: CoroutineScope,
converter: MetaConverter<T>,
flow: Flow<T>,
): Job = flow.onEach {
update(converter.convert(it))
}.launchIn(scope)
@DFExperimental
public fun <T> ObservableMutableMeta.bind(
scope: CoroutineScope,
converter: MetaConverter<T>,
flow: MutableSharedFlow<T>,
): Job = scope.launch{
listenTo(this, converter,flow)
onChange(flow){
launch {
flow.emit(converter.read(this@onChange))
}
}
flow.onCompletion {
removeListener(flow)
}
}.also {
it.invokeOnCompletion {
removeListener(flow)
}
}

View File

@ -1,31 +0,0 @@
package space.kscience.dataforge.properties
import space.kscience.dataforge.meta.Scheme
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.parseAsName
import space.kscience.dataforge.names.startsWith
import kotlin.reflect.KMutableProperty1
@DFExperimental
public fun <S : Scheme, T : Any> S.property(property: KMutableProperty1<S, T?>): Property<T?> =
object : Property<T?> {
override var value: T?
get() = property.get(this@property)
set(value) {
property.set(this@property, value)
}
override fun onChange(owner: Any?, callback: (T?) -> Unit) {
this@property.meta.onChange(this) { name ->
if (name.startsWith(property.name.parseAsName(true))) {
callback(property.get(this@property))
}
}
}
override fun removeChangeListener(owner: Any?) {
this@property.meta.removeListener(this@property)
}
}

View File

@ -1,28 +0,0 @@
package space.kscience.dataforge.properties
import space.kscience.dataforge.meta.Scheme
import space.kscience.dataforge.meta.SchemeSpec
import space.kscience.dataforge.meta.int
import space.kscience.dataforge.misc.DFExperimental
import kotlin.test.Test
import kotlin.test.assertEquals
internal class TestScheme : Scheme() {
var a by int()
var b by int()
companion object : SchemeSpec<TestScheme>(::TestScheme)
}
@DFExperimental
class MetaPropertiesTest {
@Test
fun testBinding() {
val scheme = TestScheme.empty()
val a = scheme.property(TestScheme::a)
val b = scheme.property(TestScheme::b)
a.bind(b)
scheme.a = 2
assertEquals(2, scheme.b)
assertEquals(2, b.value)
}
}

View File

@ -1,32 +0,0 @@
package space.kscience.dataforge.properties
import org.w3c.dom.HTMLInputElement
import space.kscience.dataforge.misc.DFExperimental
@DFExperimental
public fun HTMLInputElement.bindValue(property: Property<String>) {
if (this.onchange != null) error("Input element already bound")
this.onchange = {
property.value = this.value
Unit
}
property.onChange(this) {
if (value != it) {
value = it
}
}
}
@DFExperimental
public fun HTMLInputElement.bindChecked(property: Property<Boolean>) {
if (this.onchange != null) error("Input element already bound")
this.onchange = {
property.value = this.checked
Unit
}
property.onChange(this) {
if (checked != it) {
checked = it
}
}
}

View File

@ -1,126 +0,0 @@
/*
* 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.
*/
package space.kscience.dataforge.descriptors
//@MustBeDocumented
//annotation class Attribute(
// val key: String,
// val value: String
//)
//
//@MustBeDocumented
//annotation class Attributes(
// val attrs: Array<Attribute>
//)
//
//@MustBeDocumented
//annotation class ItemDef(
// val info: String = "",
// val multiple: Boolean = false,
// val required: Boolean = false
//)
//
//@Target(AnnotationTarget.PROPERTY)
//@MustBeDocumented
//annotation class ValueDef(
// val type: Array<ValueType> = [ValueType.STRING],
// val def: String = "",
// val allowed: Array<String> = [],
// val enumeration: KClass<*> = Any::class
//)
///**
// * Description text for meta property, node or whole object
// */
//@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
//@Retention(AnnotationRetention.RUNTIME)
//@MustBeDocumented
//annotation class Description(val value: String)
//
///**
// * Annotation for value property which states that lists are expected
// */
//@Target(AnnotationTarget.PROPERTY)
//@Retention(AnnotationRetention.RUNTIME)
//@MustBeDocumented
//annotation class Multiple
//
///**
// * Descriptor target
// * The DataForge path to the resource containing the description. Following targets are supported:
// * 1. resource
// * 1. file
// * 1. class
// * 1. method
// * 1. property
// *
// *
// * Does not work if [type] is provided
// */
//@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER)
//@Retention(AnnotationRetention.RUNTIME)
//@MustBeDocumented
//annotation class Descriptor(val value: String)
//
//
///**
// * Aggregator class for descriptor nodes
// */
//@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER)
//@Retention(AnnotationRetention.RUNTIME)
//@MustBeDocumented
//annotation class DescriptorNodes(vararg val nodes: NodeDef)
//
///**
// * Aggregator class for descriptor values
// */
//@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER)
//@Retention(AnnotationRetention.RUNTIME)
//@MustBeDocumented
//annotation class DescriptorValues(vararg val nodes: ValueDef)
//
///**
// * Alternative name for property descriptor declaration
// */
//@Target(AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER)
//@Retention(AnnotationRetention.RUNTIME)
//@MustBeDocumented
//annotation class DescriptorName(val name: String)
//
//@Target(AnnotationTarget.PROPERTY)
//@Retention(AnnotationRetention.RUNTIME)
//@MustBeDocumented
//annotation class DescriptorValue(val def: ValueDef)
////TODO enter fields directly?
//
//@Target(AnnotationTarget.PROPERTY)
//@Retention(AnnotationRetention.RUNTIME)
//@MustBeDocumented
//annotation class ValueProperty(
// val name: String = "",
// val type: Array<ValueType> = arrayOf(ValueType.STRING),
// val multiple: Boolean = false,
// val def: String = "",
// val enumeration: KClass<*> = Any::class,
// val tags: Array<String> = emptyArray()
//)
//
//
//@Target(AnnotationTarget.PROPERTY)
//@Retention(AnnotationRetention.RUNTIME)
//@MustBeDocumented
//annotation class NodeProperty(val name: String = "")

View File

@ -1,53 +1,124 @@
package space.kscience.dataforge.descriptors package space.kscience.dataforge.descriptors
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import org.slf4j.LoggerFactory
import space.kscience.dataforge.meta.Scheme
import space.kscience.dataforge.meta.SchemeSpec
import space.kscience.dataforge.meta.ValueType
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.MetaDescriptorBuilder
import space.kscience.dataforge.meta.descriptors.node
import java.net.URL
import kotlin.reflect.KClass
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.full.memberProperties
import kotlin.reflect.typeOf
//inline fun <reified T : Scheme> T.buildDescriptor(): NodeDescriptor = NodeDescriptor {
// T::class.apply {
// findAnnotation<ItemDef>()?.let { def ->
// info = def.info
// required = def.required
// multiple = def.multiple
// }
// findAnnotation<Attribute>()?.let { attr ->
// attributes {
// this[attr.key] = attr.value.parseValue()
// }
// }
// findAnnotation<Attributes>()?.attrs?.forEach { attr ->
// attributes {
// this[attr.key] = attr.value.parseValue()
// }
// }
// }
// T::class.memberProperties.forEach { property ->
// val delegate = property.getDelegate(this@buildDescriptor)
//
// val descriptor: ItemDescriptor = when (delegate) {
// is ConfigurableDelegate -> buildPropertyDescriptor(property, delegate)
// is ReadWriteDelegateWrapper<*, *> -> {
// if (delegate.delegate is ConfigurableDelegate) {
// buildPropertyDescriptor(property, delegate.delegate as ConfigurableDelegate)
// } else {
// return@forEach
// }
// }
// else -> return@forEach
// }
// defineItem(property.name, descriptor)
// }
//}
//inline fun <T : Scheme, reified V : Any?> buildPropertyDescriptor( /**
// property: KProperty1<T, V>, * Description text for meta property, node or whole object
// delegate: ConfigurableDelegate */
//): ItemDescriptor { @Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
// when { @Retention(AnnotationRetention.RUNTIME)
// V::class.isSubclassOf(Scheme::class) -> NodeDescriptor { @MustBeDocumented
// default = delegate.default.node public annotation class Description(val value: String)
// }
// V::class.isSubclassOf(Meta::class) -> NodeDescriptor { @Target(AnnotationTarget.PROPERTY)
// default = delegate.default.node @Retention(AnnotationRetention.RUNTIME)
// } @MustBeDocumented
// public annotation class Multiple()
// }
//} @Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
public annotation class DescriptorResource(val resourceName: String)
@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
public annotation class DescriptorUrl(val url: String)
@OptIn(ExperimentalSerializationApi::class)
private fun MetaDescriptorBuilder.loadDescriptorFromUrl(url: URL) {
url.openStream().use {
from(Json.decodeFromStream(MetaDescriptor.serializer(), it))
}
}
private fun MetaDescriptorBuilder.loadDescriptorFromResource(resource: DescriptorResource) {
val url = {}.javaClass.getResource(resource.resourceName)
if (url != null) {
loadDescriptorFromUrl(url)
} else {
LoggerFactory.getLogger("System")
.error("Can't find descriptor resource with name ${resource.resourceName}")
}
}
public fun <T : Any> MetaDescriptor.Companion.forClass(
kClass: KClass<T>,
mod: MetaDescriptorBuilder.() -> Unit = {},
): MetaDescriptor = MetaDescriptor {
when {
kClass.isSubclassOf(Number::class) -> valueType(ValueType.NUMBER)
kClass == String::class -> ValueType.STRING
kClass == Boolean::class -> ValueType.BOOLEAN
kClass == DoubleArray::class -> ValueType.LIST
}
kClass.annotations.forEach {
when (it) {
is Description -> description = it.value
is DescriptorResource -> loadDescriptorFromResource(it)
is DescriptorUrl -> loadDescriptorFromUrl(URL(it.url))
}
}
kClass.memberProperties.forEach { property ->
var flag = false
val descriptor = MetaDescriptor {
//use base type descriptor as a base
(property.returnType.classifier as? KClass<*>)?.let {
from(forClass(it))
}
property.annotations.forEach {
when (it) {
is Description -> {
description = it.value
flag = true
}
is Multiple -> {
multiple = true
flag = true
}
is DescriptorResource -> {
loadDescriptorFromResource(it)
flag = true
}
is DescriptorUrl -> {
loadDescriptorFromUrl(URL(it.url))
flag = true
}
}
}
}
if (flag) {
node(property.name, descriptor)
}
}
mod()
}
@Suppress("UNCHECKED_CAST")
public inline fun <reified T : Scheme> SchemeSpec<T>.autoDescriptor( noinline mod: MetaDescriptorBuilder.() -> Unit = {}): MetaDescriptor =
MetaDescriptor.forClass(typeOf<T>().classifier as KClass<T>, mod)

View File

@ -0,0 +1,31 @@
package space.kscience.dataforge.descriptors
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.junit.jupiter.api.Test
import space.kscience.dataforge.meta.Scheme
import space.kscience.dataforge.meta.SchemeSpec
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.int
import space.kscience.dataforge.meta.string
private class TestScheme: Scheme(){
@Description("A")
val a by string()
@Description("B")
val b by int()
companion object: SchemeSpec<TestScheme>(::TestScheme){
override val descriptor: MetaDescriptor = autoDescriptor()
}
}
class TestAutoDescriptors {
@Test
fun autoDescriptor(){
val autoDescriptor = MetaDescriptor.forClass(TestScheme::class)
println(Json{prettyPrint = true}.encodeToString(autoDescriptor))
}
}

View File

@ -71,6 +71,11 @@ internal class ByteArrayBinary(
override fun view(offset: Int, binarySize: Int): ByteArrayBinary = override fun view(offset: Int, binarySize: Int): ByteArrayBinary =
ByteArrayBinary(array, start + offset, binarySize) ByteArrayBinary(array, start + offset, binarySize)
override fun toString(): String =
"ByteArrayBinary(array=$array, start=$start, size=$size)"
} }
public fun ByteArray.asBinary(): Binary = ByteArrayBinary(this) public fun ByteArray.asBinary(): Binary = ByteArrayBinary(this)

View File

@ -31,7 +31,7 @@ private fun Meta.toJsonWithIndex(descriptor: MetaDescriptor?, index: String?): J
val pairs: MutableList<Pair<String, JsonElement>> = items.entries.groupBy { val pairs: MutableList<Pair<String, JsonElement>> = items.entries.groupBy {
it.key.body it.key.body
}.mapTo(ArrayList()) { (body, list) -> }.mapTo(ArrayList()) { (body, list) ->
val childDescriptor = descriptor?.children?.get(body) val childDescriptor = descriptor?.nodes?.get(body)
if (list.size == 1) { if (list.size == 1) {
val (token, element) = list.first() val (token, element) = list.first()
//do not add an empty element //do not add an empty element

View File

@ -1,19 +1,19 @@
package space.kscience.dataforge.meta.transformations package space.kscience.dataforge.meta
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.encodeToJsonElement import kotlinx.serialization.json.encodeToJsonElement
import kotlinx.serialization.serializer import kotlinx.serialization.serializer
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.MetaDescriptor import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DFExperimental
import kotlin.reflect.KType import kotlin.reflect.KType
import kotlin.reflect.typeOf import kotlin.reflect.typeOf
/** /**
* A converter of generic object to and from [Meta] * A converter of generic object to and from [Meta]
*/ */
public interface MetaConverter<T> { public interface MetaConverter<T>: MetaSpec<T> {
/** /**
* Runtime type of [T] * Runtime type of [T]
@ -23,32 +23,32 @@ public interface MetaConverter<T> {
/** /**
* A descriptor for resulting meta * A descriptor for resulting meta
*/ */
public val descriptor: MetaDescriptor get() = MetaDescriptor.EMPTY override val descriptor: MetaDescriptor get() = MetaDescriptor.EMPTY
/** /**
* Attempt conversion of [meta] to an object or return null if conversion failed * Attempt conversion of [source] to an object or return null if conversion failed
*/ */
public fun metaToObjectOrNull(meta: Meta): T? override fun readOrNull(source: Meta): T?
public fun metaToObject(meta: Meta): T = override fun read(source: Meta): T =
metaToObjectOrNull(meta) ?: error("Meta $meta could not be interpreted by $this") readOrNull(source) ?: error("Meta $source could not be interpreted by $this")
public fun objectToMeta(obj: T): Meta public fun convert(obj: T): Meta
public companion object { public companion object {
public val meta: MetaConverter<Meta> = object : MetaConverter<Meta> { public val meta: MetaConverter<Meta> = object : MetaConverter<Meta> {
override val type: KType = typeOf<Meta>() override val type: KType = typeOf<Meta>()
override fun metaToObjectOrNull(meta: Meta): Meta = meta override fun readOrNull(source: Meta): Meta = source
override fun objectToMeta(obj: Meta): Meta = obj override fun convert(obj: Meta): Meta = obj
} }
public val value: MetaConverter<Value> = object : MetaConverter<Value> { public val value: MetaConverter<Value> = object : MetaConverter<Value> {
override val type: KType = typeOf<Value>() override val type: KType = typeOf<Value>()
override fun metaToObjectOrNull(meta: Meta): Value? = meta.value override fun readOrNull(source: Meta): Value? = source.value
override fun objectToMeta(obj: Value): Meta = Meta(obj) override fun convert(obj: Value): Meta = Meta(obj)
} }
public val string: MetaConverter<String> = object : MetaConverter<String> { public val string: MetaConverter<String> = object : MetaConverter<String> {
@ -59,8 +59,8 @@ public interface MetaConverter<T> {
} }
override fun metaToObjectOrNull(meta: Meta): String? = meta.string override fun readOrNull(source: Meta): String? = source.string
override fun objectToMeta(obj: String): Meta = Meta(obj.asValue()) override fun convert(obj: String): Meta = Meta(obj.asValue())
} }
public val boolean: MetaConverter<Boolean> = object : MetaConverter<Boolean> { public val boolean: MetaConverter<Boolean> = object : MetaConverter<Boolean> {
@ -70,8 +70,8 @@ public interface MetaConverter<T> {
valueType(ValueType.BOOLEAN) valueType(ValueType.BOOLEAN)
} }
override fun metaToObjectOrNull(meta: Meta): Boolean? = meta.boolean override fun readOrNull(source: Meta): Boolean? = source.boolean
override fun objectToMeta(obj: Boolean): Meta = Meta(obj.asValue()) override fun convert(obj: Boolean): Meta = Meta(obj.asValue())
} }
public val number: MetaConverter<Number> = object : MetaConverter<Number> { public val number: MetaConverter<Number> = object : MetaConverter<Number> {
@ -81,8 +81,8 @@ public interface MetaConverter<T> {
valueType(ValueType.NUMBER) valueType(ValueType.NUMBER)
} }
override fun metaToObjectOrNull(meta: Meta): Number? = meta.number override fun readOrNull(source: Meta): Number? = source.number
override fun objectToMeta(obj: Number): Meta = Meta(obj.asValue()) override fun convert(obj: Number): Meta = Meta(obj.asValue())
} }
public val double: MetaConverter<Double> = object : MetaConverter<Double> { public val double: MetaConverter<Double> = object : MetaConverter<Double> {
@ -92,8 +92,8 @@ public interface MetaConverter<T> {
valueType(ValueType.NUMBER) valueType(ValueType.NUMBER)
} }
override fun metaToObjectOrNull(meta: Meta): Double? = meta.double override fun readOrNull(source: Meta): Double? = source.double
override fun objectToMeta(obj: Double): Meta = Meta(obj.asValue()) override fun convert(obj: Double): Meta = Meta(obj.asValue())
} }
public val float: MetaConverter<Float> = object : MetaConverter<Float> { public val float: MetaConverter<Float> = object : MetaConverter<Float> {
@ -103,8 +103,8 @@ public interface MetaConverter<T> {
valueType(ValueType.NUMBER) valueType(ValueType.NUMBER)
} }
override fun metaToObjectOrNull(meta: Meta): Float? = meta.float override fun readOrNull(source: Meta): Float? = source.float
override fun objectToMeta(obj: Float): Meta = Meta(obj.asValue()) override fun convert(obj: Float): Meta = Meta(obj.asValue())
} }
public val int: MetaConverter<Int> = object : MetaConverter<Int> { public val int: MetaConverter<Int> = object : MetaConverter<Int> {
@ -114,8 +114,8 @@ public interface MetaConverter<T> {
valueType(ValueType.NUMBER) valueType(ValueType.NUMBER)
} }
override fun metaToObjectOrNull(meta: Meta): Int? = meta.int override fun readOrNull(source: Meta): Int? = source.int
override fun objectToMeta(obj: Int): Meta = Meta(obj.asValue()) override fun convert(obj: Int): Meta = Meta(obj.asValue())
} }
public val long: MetaConverter<Long> = object : MetaConverter<Long> { public val long: MetaConverter<Long> = object : MetaConverter<Long> {
@ -125,8 +125,8 @@ public interface MetaConverter<T> {
valueType(ValueType.NUMBER) valueType(ValueType.NUMBER)
} }
override fun metaToObjectOrNull(meta: Meta): Long? = meta.long override fun readOrNull(source: Meta): Long? = source.long
override fun objectToMeta(obj: Long): Meta = Meta(obj.asValue()) override fun convert(obj: Long): Meta = Meta(obj.asValue())
} }
public inline fun <reified E : Enum<E>> enum(): MetaConverter<E> = object : MetaConverter<E> { public inline fun <reified E : Enum<E>> enum(): MetaConverter<E> = object : MetaConverter<E> {
@ -138,9 +138,9 @@ public interface MetaConverter<T> {
} }
@Suppress("USELESS_CAST") @Suppress("USELESS_CAST")
override fun metaToObjectOrNull(meta: Meta): E = meta.enum<E>() as? E ?: error("The Item is not a Enum") override fun readOrNull(source: Meta): E = source.enum<E>() as? E ?: error("The Item is not a Enum")
override fun objectToMeta(obj: E): Meta = Meta(obj.asValue()) override fun convert(obj: E): Meta = Meta(obj.asValue())
} }
public fun <T> valueList( public fun <T> valueList(
@ -153,9 +153,9 @@ public interface MetaConverter<T> {
valueType(ValueType.LIST) valueType(ValueType.LIST)
} }
override fun metaToObjectOrNull(meta: Meta): List<T>? = meta.value?.list?.map(reader) override fun readOrNull(source: Meta): List<T>? = source.value?.list?.map(reader)
override fun objectToMeta(obj: List<T>): Meta = Meta(obj.map(writer).asValue()) override fun convert(obj: List<T>): Meta = Meta(obj.map(writer).asValue())
} }
/** /**
@ -168,12 +168,12 @@ public interface MetaConverter<T> {
override val type: KType = typeOf<T>() override val type: KType = typeOf<T>()
private val serializer: KSerializer<T> = serializer() private val serializer: KSerializer<T> = serializer()
override fun metaToObjectOrNull(meta: Meta): T? { override fun readOrNull(source: Meta): T? {
val json = meta.toJson(descriptor) val json = source.toJson(descriptor)
return Json.decodeFromJsonElement(serializer, json) return Json.decodeFromJsonElement(serializer, json)
} }
override fun objectToMeta(obj: T): Meta { override fun convert(obj: T): Meta {
val json = Json.encodeToJsonElement(obj) val json = Json.encodeToJsonElement(obj)
return json.toMeta(descriptor) return json.toMeta(descriptor)
} }
@ -183,7 +183,6 @@ public interface MetaConverter<T> {
} }
} }
public fun <T : Any> MetaConverter<T>.nullableMetaToObject(item: Meta?): T? = item?.let { metaToObject(it) } public fun <T : Any> MetaConverter<T>.convertNullable(obj: T?): Meta? = obj?.let { convert(it) }
public fun <T : Any> MetaConverter<T>.nullableObjectToMeta(obj: T?): Meta? = obj?.let { objectToMeta(it) }
public fun <T> MetaConverter<T>.valueToObject(value: Value): T? = metaToObject(Meta(value))

View File

@ -1,7 +1,6 @@
package space.kscience.dataforge.meta package space.kscience.dataforge.meta
import space.kscience.dataforge.meta.descriptors.MetaDescriptor import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.transformations.MetaConverter
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.asName
@ -14,13 +13,13 @@ public fun MetaProvider.node(key: Name? = null): ReadOnlyProperty<Any?, Meta?> =
} }
/** /**
* Use [converter] to read the Meta node * Use [metaSpec] to read the Meta node
*/ */
public fun <T> MetaProvider.convertable( public fun <T> MetaProvider.spec(
converter: MetaConverter<T>, metaSpec: MetaSpec<T>,
key: Name? = null, key: Name? = null,
): ReadOnlyProperty<Any?, T?> = ReadOnlyProperty { _, property -> ): ReadOnlyProperty<Any?, T?> = ReadOnlyProperty { _, property ->
get(key ?: property.name.asName())?.let { converter.metaToObject(it) } get(key ?: property.name.asName())?.let { metaSpec.read(it) }
} }
/** /**
@ -30,30 +29,30 @@ public fun <T> MetaProvider.convertable(
public inline fun <reified T> MetaProvider.serializable( public inline fun <reified T> MetaProvider.serializable(
descriptor: MetaDescriptor? = null, descriptor: MetaDescriptor? = null,
key: Name? = null, key: Name? = null,
): ReadOnlyProperty<Any?, T?> = convertable(MetaConverter.serializable(descriptor), key) ): ReadOnlyProperty<Any?, T?> = spec(MetaConverter.serializable(descriptor), key)
@Deprecated("Use convertable", ReplaceWith("convertable(converter, key)")) @Deprecated("Use convertable", ReplaceWith("convertable(converter, key)"))
public fun <T> MetaProvider.node( public fun <T> MetaProvider.node(
key: Name? = null, key: Name? = null,
converter: MetaConverter<T>, converter: MetaSpec<T>,
): ReadOnlyProperty<Any?, T?> = convertable(converter, key) ): ReadOnlyProperty<Any?, T?> = spec(converter, key)
/** /**
* Use [converter] to convert a list of same name siblings meta to object * Use [converter] to convert a list of same name siblings meta to object
*/ */
public fun <T> Meta.listOfConvertable( public fun <T> Meta.listOfSpec(
converter: MetaConverter<T>, converter: MetaSpec<T>,
key: Name? = null, key: Name? = null,
): ReadOnlyProperty<Any?, List<T>> = ReadOnlyProperty{_, property -> ): ReadOnlyProperty<Any?, List<T>> = ReadOnlyProperty{_, property ->
val name = key ?: property.name.asName() val name = key ?: property.name.asName()
getIndexed(name).values.map { converter.metaToObject(it) } getIndexed(name).values.map { converter.read(it) }
} }
@DFExperimental @DFExperimental
public inline fun <reified T> Meta.listOfSerializable( public inline fun <reified T> Meta.listOfSerializable(
descriptor: MetaDescriptor? = null, descriptor: MetaDescriptor? = null,
key: Name? = null, key: Name? = null,
): ReadOnlyProperty<Any?, List<T>> = listOfConvertable(MetaConverter.serializable(descriptor), key) ): ReadOnlyProperty<Any?, List<T>> = listOfSpec(MetaConverter.serializable(descriptor), key)
/** /**
* A property delegate that uses custom key * A property delegate that uses custom key

View File

@ -0,0 +1,21 @@
package space.kscience.dataforge.meta
import space.kscience.dataforge.meta.descriptors.Described
public interface MetaSpec<out T> : Described {
/**
* Read the source meta into an object and return null if Meta could not be interpreted as a target type
*/
public fun readOrNull(source: Meta): T?
/**
* Read generic read-only meta with this [MetaSpec] producing instance of the desired type.
* Throws an error if conversion could not be done.
*/
public fun read(source: Meta): T = readOrNull(source) ?: error("Meta $source could not be interpreted by $this")
}
public fun <T : Any> MetaSpec<T>.readNullable(item: Meta?): T? = item?.let { read(it) }
public fun <T> MetaSpec<T>.readValue(value: Value): T? = read(Meta(value))

View File

@ -1,6 +1,5 @@
package space.kscience.dataforge.meta.transformations package space.kscience.dataforge.meta
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import kotlin.jvm.JvmInline import kotlin.jvm.JvmInline

View File

@ -1,7 +1,6 @@
package space.kscience.dataforge.meta package space.kscience.dataforge.meta
import space.kscience.dataforge.meta.descriptors.MetaDescriptor import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.transformations.MetaConverter
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.asName
@ -33,12 +32,12 @@ public fun <T> MutableMetaProvider.convertable(
object : ReadWriteProperty<Any?, T?> { object : ReadWriteProperty<Any?, T?> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T? { override fun getValue(thisRef: Any?, property: KProperty<*>): T? {
val name = key ?: property.name.asName() val name = key ?: property.name.asName()
return get(name)?.let { converter.metaToObject(it) } return get(name)?.let { converter.read(it) }
} }
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) { override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
val name = key ?: property.name.asName() val name = key ?: property.name.asName()
set(name, value?.let { converter.objectToMeta(it) }) set(name, value?.let { converter.convert(it) })
} }
} }
@ -66,12 +65,12 @@ public fun <T> MutableMeta.listOfConvertable(
): ReadWriteProperty<Any?, List<T>> = object : ReadWriteProperty<Any?, List<T>> { ): ReadWriteProperty<Any?, List<T>> = object : ReadWriteProperty<Any?, List<T>> {
override fun getValue(thisRef: Any?, property: KProperty<*>): List<T> { override fun getValue(thisRef: Any?, property: KProperty<*>): List<T> {
val name = key ?: property.name.asName() val name = key ?: property.name.asName()
return getIndexed(name).values.map { converter.metaToObject(it) } return getIndexed(name).values.map { converter.read(it) }
} }
override fun setValue(thisRef: Any?, property: KProperty<*>, value: List<T>) { override fun setValue(thisRef: Any?, property: KProperty<*>, value: List<T>) {
val name = key ?: property.name.asName() val name = key ?: property.name.asName()
setIndexed(name, value.map { converter.objectToMeta(it) }) setIndexed(name, value.map { converter.convert(it) })
} }
} }

View File

@ -1,8 +1,10 @@
package space.kscience.dataforge.meta package space.kscience.dataforge.meta
import space.kscience.dataforge.misc.ThreadSafe import space.kscience.dataforge.misc.ThreadSafe
import space.kscience.dataforge.names.* import space.kscience.dataforge.names.Name
import kotlin.reflect.KProperty1 import space.kscience.dataforge.names.cutFirst
import space.kscience.dataforge.names.firstOrNull
import space.kscience.dataforge.names.isEmpty
internal data class MetaListener( internal data class MetaListener(
@ -15,12 +17,15 @@ internal data class MetaListener(
*/ */
public interface ObservableMeta : Meta { public interface ObservableMeta : Meta {
/** /**
* Add change listener to this meta. Owner is declared to be able to remove listeners later. Listener without owner could not be removed * Add change listener to this meta. The Owner is declared to be able to remove listeners later.
* Listeners without an owner could be only removed all together.
*
* `this` object in the listener represents the current state of this meta. The name points to a changed node
*/ */
public fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit) public fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit)
/** /**
* Remove all listeners belonging to given owner * Remove all listeners belonging to the given [owner]. Passing null removes all listeners.
*/ */
public fun removeListener(owner: Any?) public fun removeListener(owner: Any?)
@ -67,24 +72,4 @@ internal abstract class AbstractObservableMeta : ObservableMeta {
override fun toString(): String = Meta.toString(this) override fun toString(): String = Meta.toString(this)
override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta) override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta)
override fun hashCode(): Int = Meta.hashCode(this) override fun hashCode(): Int = Meta.hashCode(this)
}
/**
* Use the value of the property in a [callBack].
* The callback is called once immediately after subscription to pass the initial value.
*
* Optional [owner] property is used for
*/
public fun <S : Scheme, T> S.useProperty(
property: KProperty1<S, T>,
owner: Any? = null,
callBack: S.(T) -> Unit,
) {
//Pass initial value.
callBack(property.get(this))
meta.onChange(owner) { name ->
if (name.startsWith(property.name.asName())) {
callBack(property.get(this@useProperty))
}
}
} }

View File

@ -7,36 +7,49 @@ import space.kscience.dataforge.meta.descriptors.validate
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.misc.ThreadSafe import space.kscience.dataforge.misc.ThreadSafe
import space.kscience.dataforge.names.* import space.kscience.dataforge.names.*
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty1
/** /**
* A base for delegate-based or descriptor-based scheme. [Scheme] has an empty constructor to simplify usage from [Specification]. * A base for delegate-based or descriptor-based scheme. [Scheme] has an empty constructor to simplify usage from [MetaSpec].
* Default item provider and [MetaDescriptor] are optional * Default item provider and [MetaDescriptor] are optional
*/ */
public open class Scheme : Described, MetaRepr, MutableMetaProvider, Configurable { public open class Scheme : Described, MetaRepr, MutableMetaProvider, Configurable {
/** /**
* Meta to be mutated by this schme * Meta to be mutated by this scheme
*/ */
private var targetMeta: MutableMeta = MutableMeta() private var target: MutableMeta? = null
get() {
// automatic initialization of target if it is missing
if (field == null) {
field = MutableMeta()
}
return field
}
/** /**
* Default values provided by this scheme * Default values provided by this scheme
*/ */
private var defaultMeta: Meta? = null private var prototype: Meta? = null
final override val meta: ObservableMutableMeta = SchemeMeta(Name.EMPTY) final override val meta: ObservableMutableMeta = SchemeMeta(Name.EMPTY)
final override var descriptor: MetaDescriptor? = null final override var descriptor: MetaDescriptor? = null
internal set private set
internal fun wrap( /**
newMeta: MutableMeta, * This method must be called before the scheme could be used
preserveDefault: Boolean = false, */
internal fun initialize(
target: MutableMeta,
prototype: Meta,
descriptor: MetaDescriptor?,
) { ) {
if (preserveDefault) { this.target = target
defaultMeta = targetMeta.seal() this.prototype = prototype
} this.descriptor = descriptor
targetMeta = newMeta
} }
/** /**
@ -47,11 +60,11 @@ public open class Scheme : Described, MetaRepr, MutableMetaProvider, Configurabl
return descriptor?.validate(meta) ?: true return descriptor?.validate(meta) ?: true
} }
override fun get(name: Name): MutableMeta? = meta.get(name) override fun get(name: Name): MutableMeta? = meta[name]
override fun set(name: Name, node: Meta?) { override fun set(name: Name, node: Meta?) {
if (validate(name, meta)) { if (validate(name, meta)) {
meta.set(name, node) meta[name] = node
} else { } else {
error("Validation failed for node $node at $name") error("Validation failed for node $node at $name")
} }
@ -68,14 +81,16 @@ public open class Scheme : Described, MetaRepr, MutableMetaProvider, Configurabl
private val listeners: MutableList<MetaListener> = mutableListOf() private val listeners: MutableList<MetaListener> = mutableListOf()
override fun toString(): String = meta.toString()
private inner class SchemeMeta(val pathName: Name) : ObservableMutableMeta { private inner class SchemeMeta(val pathName: Name) : ObservableMutableMeta {
override var value: Value? override var value: Value?
get() = targetMeta[pathName]?.value get() = target[pathName]?.value
?: defaultMeta?.get(pathName)?.value ?: prototype?.get(pathName)?.value
?: descriptor?.get(pathName)?.defaultValue ?: descriptor?.get(pathName)?.defaultValue
set(value) { set(value) {
val oldValue = targetMeta[pathName]?.value val oldValue = target[pathName]?.value
targetMeta[pathName] = value target!![pathName] = value
if (oldValue != value) { if (oldValue != value) {
invalidate(Name.EMPTY) invalidate(Name.EMPTY)
} }
@ -83,8 +98,8 @@ public open class Scheme : Described, MetaRepr, MutableMetaProvider, Configurabl
override val items: Map<NameToken, ObservableMutableMeta> override val items: Map<NameToken, ObservableMutableMeta>
get() { get() {
val targetKeys = targetMeta[pathName]?.items?.keys ?: emptySet() val targetKeys = target[pathName]?.items?.keys ?: emptySet()
val defaultKeys = defaultMeta?.get(pathName)?.items?.keys ?: emptySet() val defaultKeys = prototype?.get(pathName)?.items?.keys ?: emptySet()
return (targetKeys + defaultKeys).associateWith { SchemeMeta(pathName + it) } return (targetKeys + defaultKeys).associateWith { SchemeMeta(pathName + it) }
} }
@ -111,7 +126,7 @@ public open class Scheme : Described, MetaRepr, MutableMetaProvider, Configurabl
override fun hashCode(): Int = Meta.hashCode(this) override fun hashCode(): Int = Meta.hashCode(this)
override fun set(name: Name, node: Meta?) { override fun set(name: Name, node: Meta?) {
targetMeta.set(name, node) target!![name] = node
invalidate(name) invalidate(name)
} }
@ -119,7 +134,6 @@ public open class Scheme : Described, MetaRepr, MutableMetaProvider, Configurabl
@DFExperimental @DFExperimental
override fun attach(name: Name, node: ObservableMutableMeta) { override fun attach(name: Name, node: ObservableMutableMeta) {
//TODO implement zero-copy attachment
set(name, node) set(name, node)
node.onChange(this) { changeName -> node.onChange(this) { changeName ->
set(name + changeName, this[changeName]) set(name + changeName, this[changeName])
@ -131,10 +145,11 @@ public open class Scheme : Described, MetaRepr, MutableMetaProvider, Configurabl
/** /**
* Relocate scheme target onto given [MutableMeta]. Old provider does not get updates anymore. * Relocate scheme target onto given [MutableMeta]. Old provider does not get updates anymore.
* Current state of the scheme used as a default. * The Current state of the scheme that os used as a default.
*/ */
@DFExperimental
public fun <T : Scheme> T.retarget(provider: MutableMeta): T = apply { public fun <T : Scheme> T.retarget(provider: MutableMeta): T = apply {
wrap(provider, true) initialize(provider, meta.seal(), descriptor)
} }
/** /**
@ -153,24 +168,145 @@ public inline fun <T : Scheme> T.copy(spec: SchemeSpec<T>, block: T.() -> Unit =
*/ */
public open class SchemeSpec<out T : Scheme>( public open class SchemeSpec<out T : Scheme>(
private val builder: () -> T, private val builder: () -> T,
) : Specification<T> { ) : MetaSpec<T> {
override fun read(source: Meta): T = builder().also {
it.wrap(MutableMeta().withDefault(source))
}
override fun write(target: MutableMeta): T = empty().also {
it.wrap(target)
}
//TODO Generate descriptor from Scheme class
override val descriptor: MetaDescriptor? get() = null override val descriptor: MetaDescriptor? get() = null
override fun empty(): T = builder().also { override fun readOrNull(source: Meta): T = builder().also {
it.descriptor = descriptor it.initialize(MutableMeta(), source, descriptor)
} }
@Suppress("OVERRIDE_BY_INLINE") public fun write(target: MutableMeta): T = empty().also {
final override inline operator fun invoke(action: T.() -> Unit): T = empty().apply(action) it.initialize(target, Meta.EMPTY, descriptor)
}
/**
* Generate an empty object
*/
public fun empty(): T = builder().also {
it.initialize(MutableMeta(), Meta.EMPTY, descriptor)
}
/**
* A convenience method to use specifications in builders
*/
public inline operator fun invoke(action: T.() -> Unit): T = empty().apply(action)
}
/**
* Update a [MutableMeta] using given specification
*/
public fun <T : Scheme> MutableMeta.updateWith(
spec: SchemeSpec<T>,
action: T.() -> Unit,
): T = spec.write(this).apply(action)
/**
* Update configuration using given specification
*/
public fun <T : Scheme> Configurable.updateWith(
spec: SchemeSpec<T>,
action: T.() -> Unit,
): T = spec.write(meta).apply(action)
/**
* A delegate that uses a [MetaSpec] to wrap a child of this provider
*/
public fun <T : Scheme> MutableMeta.scheme(
spec: SchemeSpec<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 spec.write(getOrCreate(name))
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
val name = key ?: property.name.asName()
set(name, value.toMeta())
}
}
public fun <T : Scheme> Scheme.scheme(
spec: SchemeSpec<T>,
key: Name? = null,
): ReadWriteProperty<Any?, T> = meta.scheme(spec, key)
/**
* A delegate that uses a [MetaSpec] to wrap a child of this provider.
* Returns null if meta with given name does not exist.
*/
public fun <T : Scheme> MutableMeta.schemeOrNull(
spec: SchemeSpec<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 if (get(name) == null) null else spec.write(getOrCreate(name))
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
val name = key ?: property.name.asName()
if (value == null) remove(name)
else set(name, value.toMeta())
}
}
public fun <T : Scheme> Scheme.schemeOrNull(
spec: SchemeSpec<T>,
key: Name? = null,
): ReadWriteProperty<Any?, T?> = meta.schemeOrNull(spec, key)
/**
* A delegate that uses a [MetaSpec] to wrap a list of child providers.
* If children are mutable, the changes in list elements are reflected on them.
* The list is a snapshot of children state, so change in structure is not reflected on its composition.
*/
@DFExperimental
public fun <T : Scheme> MutableMeta.listOfScheme(
spec: SchemeSpec<T>,
key: Name? = null,
): ReadWriteProperty<Any?, List<T>> = object : ReadWriteProperty<Any?, List<T>> {
override fun getValue(thisRef: Any?, property: KProperty<*>): List<T> {
val name = key ?: property.name.asName()
return getIndexed(name).values.map { spec.write(it as MutableMeta) }
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: List<T>) {
val name = key ?: property.name.asName()
setIndexed(name, value.map { it.toMeta() })
}
}
@DFExperimental
public fun <T : Scheme> Scheme.listOfScheme(
spec: SchemeSpec<T>,
key: Name? = null,
): ReadWriteProperty<Any?, List<T>> = meta.listOfScheme(spec, key)
/**
* Use the value of the property in a [callBack].
* The callback is called once immediately after subscription to pass the initial value.
*
* Optional [owner] property is used for
*/
public fun <S : Scheme, T> S.useProperty(
property: KProperty1<S, T>,
owner: Any? = null,
callBack: S.(T) -> Unit,
) {
//Pass initial value.
callBack(property.get(this))
meta.onChange(owner) { name ->
if (name.startsWith(property.name.asName())) {
callBack(property.get(this@useProperty))
}
}
} }

View File

@ -1,137 +0,0 @@
package space.kscience.dataforge.meta
import space.kscience.dataforge.meta.descriptors.Described
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
public interface ReadOnlySpecification<out T : Any> : Described {
/**
* Read generic read-only meta with this [Specification] producing instance of desired type.
* The source is not mutated even if it is in theory mutable
*/
public fun read(source: Meta): T
/**
* Generate an empty object
*/
public fun empty(): T
/**
* A convenience method to use specifications in builders
*/
public operator fun invoke(action: T.() -> Unit): T = empty().apply(action)
}
/**
* Allows to apply custom configuration in a type safe way to simple untyped configuration.
* By convention [Scheme] companion should inherit this class
*
*/
public interface Specification<out T : Any> : ReadOnlySpecification<T> {
/**
* Wrap [MutableMeta], using it as inner storage (changes to [Specification] are reflected on [MutableMeta]
*/
public fun write(target: MutableMeta): T
}
/**
* Update a [MutableMeta] using given specification
*/
public fun <T : Any> MutableMeta.updateWith(
spec: Specification<T>,
action: T.() -> Unit,
): T = spec.write(this).apply(action)
/**
* Update configuration using given specification
*/
public fun <T : Any> Configurable.updateWith(
spec: Specification<T>,
action: T.() -> Unit,
): T = spec.write(meta).apply(action)
//
//public fun <M : MutableTypedMeta<M>> MutableMeta.withSpec(spec: Specification<M>): M? =
// spec.write(it)
/**
* A delegate that uses a [Specification] to wrap a child of this provider
*/
public fun <T : Scheme> MutableMeta.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 spec.write(getOrCreate(name))
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
val name = key ?: property.name.asName()
set(name, value.toMeta())
}
}
public fun <T : Scheme> Scheme.spec(
spec: Specification<T>,
key: Name? = null,
): ReadWriteProperty<Any?, T> = meta.spec(spec, key)
/**
* A delegate that uses a [Specification] to wrap a child of this provider.
* Returns null if meta with given name does not exist.
*/
public fun <T : Scheme> MutableMeta.specOrNull(
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 if (get(name) == null) null else spec.write(getOrCreate(name))
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
val name = key ?: property.name.asName()
if (value == null) remove(name)
else set(name, value.toMeta())
}
}
public fun <T : Scheme> Scheme.specOrNull(
spec: Specification<T>,
key: Name? = null,
): ReadWriteProperty<Any?, T?> = meta.specOrNull(spec, key)
/**
* A delegate that uses a [Specification] to wrap a list of child providers.
* If children are mutable, the changes in list elements are reflected on them.
* The list is a snapshot of children state, so change in structure is not reflected on its composition.
*/
@DFExperimental
public fun <T : Scheme> MutableMeta.listOfSpec(
spec: Specification<T>,
key: Name? = null,
): ReadWriteProperty<Any?, List<T>> = object : ReadWriteProperty<Any?, List<T>> {
override fun getValue(thisRef: Any?, property: KProperty<*>): List<T> {
val name = key ?: property.name.asName()
return getIndexed(name).values.map { spec.write(it as MutableMeta) }
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: List<T>) {
val name = key ?: property.name.asName()
setIndexed(name, value.map { it.toMeta() })
}
}
@DFExperimental
public fun <T : Scheme> Scheme.listOfSpec(
spec: Specification<T>,
key: Name? = null,
): ReadWriteProperty<Any?, List<T>> = meta.listOfSpec(spec, key)

View File

@ -27,7 +27,7 @@ public enum class ValueRestriction {
/** /**
* The descriptor for a meta * The descriptor for a meta
* @param description description text * @param description description text
* @param children child descriptors for this node * @param nodes child descriptors for this node
* @param multiple True if same name siblings with this name are allowed * @param multiple True if same name siblings with this name are allowed
* @param valueRestriction The requirements for node content * @param valueRestriction The requirements for node content
* @param valueTypes list of allowed types for [Meta.value], null if all values are allowed. * @param valueTypes list of allowed types for [Meta.value], null if all values are allowed.
@ -39,7 +39,7 @@ public enum class ValueRestriction {
@Serializable @Serializable
public data class MetaDescriptor( public data class MetaDescriptor(
public val description: String? = null, public val description: String? = null,
public val children: Map<String, MetaDescriptor> = emptyMap(), public val nodes: Map<String, MetaDescriptor> = emptyMap(),
public val multiple: Boolean = false, public val multiple: Boolean = false,
public val valueRestriction: ValueRestriction = ValueRestriction.NONE, public val valueRestriction: ValueRestriction = ValueRestriction.NONE,
public val valueTypes: List<ValueType>? = null, public val valueTypes: List<ValueType>? = null,
@ -47,6 +47,9 @@ public data class MetaDescriptor(
public val defaultValue: Value? = null, public val defaultValue: Value? = null,
public val attributes: Meta = Meta.EMPTY, public val attributes: Meta = Meta.EMPTY,
) { ) {
@Deprecated("Replace by nodes", ReplaceWith("nodes"))
public val children: Map<String, MetaDescriptor> get() = nodes
/** /**
* A node constructed of default values for this descriptor and its children * A node constructed of default values for this descriptor and its children
*/ */
@ -55,7 +58,7 @@ public data class MetaDescriptor(
defaultValue?.let { defaultValue -> defaultValue?.let { defaultValue ->
this.value = defaultValue this.value = defaultValue
} }
children.forEach { (key, descriptor) -> nodes.forEach { (key, descriptor) ->
set(key, descriptor.defaultNode) set(key, descriptor.defaultNode)
} }
} }
@ -67,13 +70,13 @@ public data class MetaDescriptor(
} }
} }
public val MetaDescriptor.required: Boolean get() = valueRestriction == ValueRestriction.REQUIRED || children.values.any { required } public val MetaDescriptor.required: Boolean get() = valueRestriction == ValueRestriction.REQUIRED || nodes.values.any { required }
public val MetaDescriptor.allowedValues: List<Value>? get() = attributes[MetaDescriptor.ALLOWED_VALUES_KEY]?.value?.list public val MetaDescriptor.allowedValues: List<Value>? get() = attributes[MetaDescriptor.ALLOWED_VALUES_KEY]?.value?.list
public operator fun MetaDescriptor.get(name: Name): MetaDescriptor? = when (name.length) { public operator fun MetaDescriptor.get(name: Name): MetaDescriptor? = when (name.length) {
0 -> this 0 -> this
1 -> children[name.firstOrNull()!!.toString()] 1 -> nodes[name.firstOrNull()!!.toString()]
else -> get(name.firstOrNull()!!.asName())?.get(name.cutFirst()) else -> get(name.firstOrNull()!!.asName())?.get(name.cutFirst())
} }
@ -95,7 +98,7 @@ public fun MetaDescriptor.validate(item: Meta?): Boolean {
if (item == null) return !required if (item == null) return !required
if (!validate(item.value)) return false if (!validate(item.value)) return false
children.forEach { (key, childDescriptor) -> nodes.forEach { (key, childDescriptor) ->
if (!childDescriptor.validate(item[key])) return false if (!childDescriptor.validate(item[key])) return false
} }
return true return true

View File

@ -44,38 +44,27 @@ public class MetaDescriptorBuilder @PublishedApi internal constructor() {
attributes.apply(block) attributes.apply(block)
} }
public fun item(name: Name, block: MetaDescriptorBuilder.() -> Unit = {}): MetaDescriptorBuilder { internal fun node(
return when (name.length) { name: Name,
0 -> apply(block) descriptorBuilder: MetaDescriptorBuilder,
): Unit {
when (name.length) {
0 -> error("Can't set descriptor to root")
1 -> { 1 -> {
val target = MetaDescriptorBuilder().apply(block) children[name.first().body] = descriptorBuilder
children[name.first().body] = target
target
} }
else -> { else -> children.getOrPut(name.first().body) {
children.getOrPut(name.first().body) { MetaDescriptorBuilder() }.item(name.cutFirst(), block) MetaDescriptorBuilder()
} }.node(name.cutFirst(), descriptorBuilder)
} }
} }
public fun node( internal fun node(
name: Name, name: Name,
descriptor: MetaDescriptor, descriptorBuilder: MetaDescriptor,
block: MetaDescriptorBuilder.() -> Unit = {}, ): Unit {
): MetaDescriptorBuilder = when (name.length) { node(name, descriptorBuilder.toBuilder())
0 -> error("Can't set descriptor to root")
1 -> {
val item = descriptor.toBuilder().apply {
valueRestriction = ValueRestriction.ABSENT
}.apply(block)
children[name.first().body] = item
item
}
else -> children.getOrPut(name.first().body) {
MetaDescriptorBuilder()
}.node(name.cutFirst(), descriptor, block)
} }
public var allowedValues: List<Value> public var allowedValues: List<Value>
@ -89,10 +78,21 @@ public class MetaDescriptorBuilder @PublishedApi internal constructor() {
allowedValues = values.map { Value.of(it) } allowedValues = values.map { Value.of(it) }
} }
public fun from(descriptor: MetaDescriptor) {
description = descriptor.description
children.putAll(descriptor.nodes.mapValues { it.value.toBuilder() })
multiple = descriptor.multiple
valueRestriction = descriptor.valueRestriction
valueTypes = descriptor.valueTypes
indexKey = descriptor.indexKey
default = descriptor.defaultValue
attributes.update(descriptor.attributes)
}
@PublishedApi @PublishedApi
internal fun build(): MetaDescriptor = MetaDescriptor( internal fun build(): MetaDescriptor = MetaDescriptor(
description = description, description = description,
children = children.mapValues { it.value.build() }, nodes = children.mapValues { it.value.build() },
multiple = multiple, multiple = multiple,
valueRestriction = valueRestriction, valueRestriction = valueRestriction,
valueTypes = valueTypes, valueTypes = valueTypes,
@ -102,12 +102,57 @@ public class MetaDescriptorBuilder @PublishedApi internal constructor() {
) )
} }
public fun MetaDescriptorBuilder.item(name: String, block: MetaDescriptorBuilder.() -> Unit): MetaDescriptorBuilder = //public fun MetaDescriptorBuilder.item(name: String, block: MetaDescriptorBuilder.() -> Unit): MetaDescriptorBuilder =
item(Name.parse(name), block) // item(Name.parse(name), block)
public inline fun MetaDescriptor(block: MetaDescriptorBuilder.() -> Unit): MetaDescriptor = public inline fun MetaDescriptor(block: MetaDescriptorBuilder.() -> Unit): MetaDescriptor =
MetaDescriptorBuilder().apply(block).build() MetaDescriptorBuilder().apply(block).build()
/**
* Create and configure child node descriptor
*/
public fun MetaDescriptorBuilder.node(
name: Name,
block: MetaDescriptorBuilder.() -> Unit,
) {
node(
name,
MetaDescriptorBuilder().apply(block)
)
}
public fun MetaDescriptorBuilder.node(name: String, descriptor: MetaDescriptor) {
node(Name.parse(name), descriptor)
}
public fun MetaDescriptorBuilder.node(name: String, block: MetaDescriptorBuilder.() -> Unit) {
node(Name.parse(name), block)
}
public fun MetaDescriptorBuilder.node(
key: String,
base: Described,
block: MetaDescriptorBuilder.() -> Unit = {},
) {
node(Name.parse(key), base.descriptor?.toBuilder()?.apply(block) ?: MetaDescriptorBuilder())
}
public fun MetaDescriptorBuilder.required() {
valueRestriction = ValueRestriction.REQUIRED
}
private fun MetaDescriptor.toBuilder(): MetaDescriptorBuilder = MetaDescriptorBuilder().apply {
description = this@toBuilder.description
children = this@toBuilder.nodes.mapValuesTo(LinkedHashMap()) { it.value.toBuilder() }
multiple = this@toBuilder.multiple
valueRestriction = this@toBuilder.valueRestriction
valueTypes = this@toBuilder.valueTypes
indexKey = this@toBuilder.indexKey
default = defaultValue
attributes = this@toBuilder.attributes.toMutableMeta()
}
/** /**
* Create and configure child value descriptor * Create and configure child value descriptor
*/ */
@ -116,7 +161,7 @@ public fun MetaDescriptorBuilder.value(
type: ValueType, type: ValueType,
vararg additionalTypes: ValueType, vararg additionalTypes: ValueType,
block: MetaDescriptorBuilder.() -> Unit = {}, block: MetaDescriptorBuilder.() -> Unit = {},
): MetaDescriptorBuilder = item(name) { ): Unit = node(name) {
valueType(type, *additionalTypes) valueType(type, *additionalTypes)
block() block()
} }
@ -126,41 +171,14 @@ public fun MetaDescriptorBuilder.value(
type: ValueType, type: ValueType,
vararg additionalTypes: ValueType, vararg additionalTypes: ValueType,
block: MetaDescriptorBuilder.() -> Unit = {}, block: MetaDescriptorBuilder.() -> Unit = {},
): MetaDescriptorBuilder = value(Name.parse(name), type, additionalTypes = additionalTypes, block) ): Unit = value(Name.parse(name), type, additionalTypes = additionalTypes, block)
/**
* Create and configure child value descriptor
*/
public fun MetaDescriptorBuilder.node(
name: Name, block: MetaDescriptorBuilder.() -> Unit,
): MetaDescriptorBuilder = item(name) {
valueRestriction = ValueRestriction.ABSENT
block()
}
public fun MetaDescriptorBuilder.node(name: String, block: MetaDescriptorBuilder.() -> Unit) {
node(Name.parse(name), block)
}
public fun MetaDescriptorBuilder.node(
key: String,
described: Described,
block: MetaDescriptorBuilder.() -> Unit = {},
) {
described.descriptor?.let {
node(Name.parse(key), it, block)
}
}
public fun MetaDescriptorBuilder.required() {
valueRestriction = ValueRestriction.REQUIRED
}
public inline fun <reified E : Enum<E>> MetaDescriptorBuilder.enum( public inline fun <reified E : Enum<E>> MetaDescriptorBuilder.enum(
key: Name, key: Name,
default: E?, default: E?,
crossinline modifier: MetaDescriptorBuilder.() -> Unit = {}, crossinline modifier: MetaDescriptorBuilder.() -> Unit = {},
): MetaDescriptorBuilder = value(key, ValueType.STRING) { ): Unit = value(key, ValueType.STRING) {
default?.let { default?.let {
this.default = default.asValue() this.default = default.asValue()
} }
@ -168,17 +186,6 @@ public inline fun <reified E : Enum<E>> MetaDescriptorBuilder.enum(
modifier() modifier()
} }
private fun MetaDescriptor.toBuilder(): MetaDescriptorBuilder = MetaDescriptorBuilder().apply {
description = this@toBuilder.description
children = this@toBuilder.children.mapValuesTo(LinkedHashMap()) { it.value.toBuilder() }
multiple = this@toBuilder.multiple
valueRestriction = this@toBuilder.valueRestriction
valueTypes = this@toBuilder.valueTypes
indexKey = this@toBuilder.indexKey
default = defaultValue
attributes = this@toBuilder.attributes.toMutableMeta()
}
/** /**
* Make a deep copy of this descriptor applying given transformation [block] * Make a deep copy of this descriptor applying given transformation [block]
*/ */

View File

@ -3,13 +3,15 @@ package space.kscience.dataforge.meta.descriptors
import space.kscience.dataforge.meta.Scheme import space.kscience.dataforge.meta.Scheme
import space.kscience.dataforge.meta.SchemeSpec import space.kscience.dataforge.meta.SchemeSpec
import space.kscience.dataforge.meta.ValueType import space.kscience.dataforge.meta.ValueType
import space.kscience.dataforge.misc.DFExperimental
import kotlin.reflect.KProperty1 import kotlin.reflect.KProperty1
import kotlin.reflect.typeOf import kotlin.reflect.typeOf
@DFExperimental
public inline fun <S : Scheme, reified T> MetaDescriptorBuilder.value( public inline fun <S : Scheme, reified T> MetaDescriptorBuilder.value(
property: KProperty1<S, T>, property: KProperty1<S, T>,
noinline block: MetaDescriptorBuilder.() -> Unit = {}, noinline block: MetaDescriptorBuilder.() -> Unit = {},
): MetaDescriptorBuilder = when (typeOf<T>()) { ): Unit = when (typeOf<T>()) {
typeOf<Number>(), typeOf<Int>(), typeOf<Double>(), typeOf<Short>(), typeOf<Long>(), typeOf<Float>() -> typeOf<Number>(), typeOf<Int>(), typeOf<Double>(), typeOf<Short>(), typeOf<Long>(), typeOf<Float>() ->
value(property.name, ValueType.NUMBER) { value(property.name, ValueType.NUMBER) {
block() block()
@ -34,9 +36,10 @@ public inline fun <S : Scheme, reified T> MetaDescriptorBuilder.value(
multiple = true multiple = true
block() block()
} }
else -> item(property.name, block) else -> node(property.name, block)
} }
@DFExperimental
public inline fun <S : Scheme, reified T : Scheme> MetaDescriptorBuilder.scheme( public inline fun <S : Scheme, reified T : Scheme> MetaDescriptorBuilder.scheme(
property: KProperty1<S, T>, property: KProperty1<S, T>,
spec: SchemeSpec<T>, spec: SchemeSpec<T>,

View File

@ -2,7 +2,7 @@ package space.kscience.dataforge.meta
import kotlinx.serialization.json.* import kotlinx.serialization.json.*
import space.kscience.dataforge.meta.descriptors.MetaDescriptor import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.item import space.kscience.dataforge.meta.descriptors.node
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -32,7 +32,7 @@ class JsonMetaTest {
} }
val descriptor = MetaDescriptor { val descriptor = MetaDescriptor {
item("nodeArray") { node("nodeArray") {
indexKey = "index" indexKey = "index"
} }
} }

View File

@ -20,7 +20,7 @@ class MetaDelegateTest {
var myValue by string() var myValue by string()
var safeValue by double(2.2) var safeValue by double(2.2)
var enumValue by enum(TestEnum.YES) var enumValue by enum(TestEnum.YES)
var inner by spec(InnerScheme) var inner by scheme(InnerScheme)
companion object : SchemeSpec<TestScheme>(::TestScheme) companion object : SchemeSpec<TestScheme>(::TestScheme)
} }

View File

@ -6,7 +6,7 @@ import space.kscience.dataforge.data.DataTree
import space.kscience.dataforge.data.GoalExecutionRestriction import space.kscience.dataforge.data.GoalExecutionRestriction
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaRepr import space.kscience.dataforge.meta.MetaRepr
import space.kscience.dataforge.meta.Specification import space.kscience.dataforge.meta.MetaSpec
import space.kscience.dataforge.meta.descriptors.Described import space.kscience.dataforge.meta.descriptors.Described
import space.kscience.dataforge.meta.descriptors.MetaDescriptor import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.misc.DfType import space.kscience.dataforge.misc.DfType
@ -43,10 +43,10 @@ public interface Task<out T : Any> : Described {
} }
/** /**
* A [Task] with [Specification] for wrapping and unwrapping task configuration * A [Task] with [MetaSpec] for wrapping and unwrapping task configuration
*/ */
public interface TaskWithSpec<out T : Any, C : Any> : Task<T> { public interface TaskWithSpec<out T : Any, C : Any> : Task<T> {
public val spec: Specification<C> public val spec: MetaSpec<C>
override val descriptor: MetaDescriptor? get() = spec.descriptor override val descriptor: MetaDescriptor? get() = spec.descriptor
public suspend fun execute(workspace: Workspace, taskName: Name, configuration: C): TaskResult<T> public suspend fun execute(workspace: Workspace, taskName: Name, configuration: C): TaskResult<T>
@ -55,11 +55,11 @@ public interface TaskWithSpec<out T : Any, C : Any> : Task<T> {
execute(workspace, taskName, spec.read(taskMeta)) execute(workspace, taskName, spec.read(taskMeta))
} }
public suspend fun <T : Any, C : Any> TaskWithSpec<T, C>.execute( //public suspend fun <T : Any, C : Scheme> TaskWithSpec<T, C>.execute(
workspace: Workspace, // workspace: Workspace,
taskName: Name, // taskName: Name,
block: C.() -> Unit = {}, // block: C.() -> Unit = {},
): TaskResult<T> = execute(workspace, taskName, spec(block)) //): TaskResult<T> = execute(workspace, taskName, spec(block))
public class TaskResultBuilder<in T : Any>( public class TaskResultBuilder<in T : Any>(
public val workspace: Workspace, public val workspace: Workspace,
@ -76,7 +76,6 @@ public class TaskResultBuilder<in T : Any>(
* @param descriptor of meta accepted by this task * @param descriptor of meta accepted by this task
* @param builder for resulting data set * @param builder for resulting data set
*/ */
@Suppress("FunctionName")
public fun <T : Any> Task( public fun <T : Any> Task(
resultType: KType, resultType: KType,
descriptor: MetaDescriptor? = null, descriptor: MetaDescriptor? = null,
@ -98,7 +97,6 @@ public fun <T : Any> Task(
} }
} }
@Suppress("FunctionName")
public inline fun <reified T : Any> Task( public inline fun <reified T : Any> Task(
descriptor: MetaDescriptor? = null, descriptor: MetaDescriptor? = null,
noinline builder: suspend TaskResultBuilder<T>.() -> Unit, noinline builder: suspend TaskResultBuilder<T>.() -> Unit,
@ -116,10 +114,10 @@ public inline fun <reified T : Any> Task(
@Suppress("FunctionName") @Suppress("FunctionName")
public fun <T : Any, C : MetaRepr> Task( public fun <T : Any, C : MetaRepr> Task(
resultType: KType, resultType: KType,
specification: Specification<C>, specification: MetaSpec<C>,
builder: suspend TaskResultBuilder<T>.(C) -> Unit, builder: suspend TaskResultBuilder<T>.(C) -> Unit,
): TaskWithSpec<T, C> = object : TaskWithSpec<T, C> { ): TaskWithSpec<T, C> = object : TaskWithSpec<T, C> {
override val spec: Specification<C> = specification override val spec: MetaSpec<C> = specification
override suspend fun execute( override suspend fun execute(
workspace: Workspace, workspace: Workspace,
@ -135,8 +133,7 @@ public fun <T : Any, C : MetaRepr> Task(
} }
} }
@Suppress("FunctionName")
public inline fun <reified T : Any, C : MetaRepr> Task( public inline fun <reified T : Any, C : MetaRepr> Task(
specification: Specification<C>, specification: MetaSpec<C>,
noinline builder: suspend TaskResultBuilder<T>.(C) -> Unit, noinline builder: suspend TaskResultBuilder<T>.(C) -> Unit,
): Task<T> = Task(typeOf<T>(), specification, builder) ): Task<T> = Task(typeOf<T>(), specification, builder)

View File

@ -7,8 +7,8 @@ import space.kscience.dataforge.context.Global
import space.kscience.dataforge.data.* import space.kscience.dataforge.data.*
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaRepr import space.kscience.dataforge.meta.MetaRepr
import space.kscience.dataforge.meta.MetaSpec
import space.kscience.dataforge.meta.MutableMeta import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.Specification
import space.kscience.dataforge.meta.descriptors.MetaDescriptor import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.MetaDescriptorBuilder import space.kscience.dataforge.meta.descriptors.MetaDescriptorBuilder
import space.kscience.dataforge.misc.DFBuilder import space.kscience.dataforge.misc.DFBuilder
@ -68,7 +68,7 @@ public inline fun <reified T : Any> TaskContainer.task(
} }
public inline fun <reified T : Any, C : MetaRepr> TaskContainer.task( public inline fun <reified T : Any, C : MetaRepr> TaskContainer.task(
specification: Specification<C>, specification: MetaSpec<C>,
noinline builder: suspend TaskResultBuilder<T>.(C) -> Unit, noinline builder: suspend TaskResultBuilder<T>.(C) -> Unit,
): PropertyDelegateProvider<Any?, ReadOnlyProperty<Any?, TaskReference<T>>> = PropertyDelegateProvider { _, property -> ): PropertyDelegateProvider<Any?, ReadOnlyProperty<Any?, TaskReference<T>>> = PropertyDelegateProvider { _, property ->
val taskName = Name.parse(property.name) val taskName = Name.parse(property.name)