WIP full refactor of Meta
This commit is contained in:
parent
679175391a
commit
9f5b010847
@ -4,7 +4,7 @@ plugins {
|
||||
|
||||
allprojects {
|
||||
group = "space.kscience"
|
||||
version = "0.5.0-dev-2"
|
||||
version = "0.5.0-dev-3"
|
||||
}
|
||||
|
||||
subprojects {
|
||||
@ -15,10 +15,6 @@ readme {
|
||||
readmeTemplate = file("docs/templates/README-TEMPLATE.md")
|
||||
}
|
||||
|
||||
changelog{
|
||||
version = project.version.toString()
|
||||
}
|
||||
|
||||
ksciencePublish {
|
||||
github("dataforge-core")
|
||||
space("https://maven.pkg.jetbrains.space/mipt-npm/p/sci/maven")
|
||||
|
@ -3,10 +3,7 @@ package space.kscience.dataforge.context
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import space.kscience.dataforge.meta.Laminate
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.MetaRepr
|
||||
import space.kscience.dataforge.meta.itemSequence
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.misc.Named
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.provider.Provider
|
||||
@ -50,13 +47,13 @@ public open class Context internal constructor(
|
||||
public fun content(target: String, inherit: Boolean): Map<Name, Any> {
|
||||
return if (inherit) {
|
||||
when (target) {
|
||||
PROPERTY_TARGET -> properties.itemSequence().toMap()
|
||||
PROPERTY_TARGET -> properties.nodeSequence().toMap()
|
||||
Plugin.TARGET -> plugins.list(true).associateBy { it.name }
|
||||
else -> emptyMap()
|
||||
}
|
||||
} else {
|
||||
when (target) {
|
||||
PROPERTY_TARGET -> properties.layers.firstOrNull()?.itemSequence()?.toMap() ?: emptyMap()
|
||||
PROPERTY_TARGET -> properties.layers.firstOrNull()?.nodeSequence()?.toMap() ?: emptyMap()
|
||||
Plugin.TARGET -> plugins.list(false).associateBy { it.name }
|
||||
else -> emptyMap()
|
||||
}
|
||||
@ -99,7 +96,7 @@ public open class Context internal constructor(
|
||||
override fun toMeta(): Meta = Meta {
|
||||
"parent" to parent?.name
|
||||
"properties" put properties.layers.firstOrNull()
|
||||
"plugins" put plugins.map { it.toMeta() }
|
||||
"plugins" put plugins.map { it.toMeta().asMetaItem() }
|
||||
}
|
||||
|
||||
public companion object {
|
||||
|
@ -1,7 +1,7 @@
|
||||
package space.kscience.dataforge.context
|
||||
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.MetaBuilder
|
||||
import space.kscience.dataforge.meta.MutableMeta
|
||||
import space.kscience.dataforge.meta.seal
|
||||
import space.kscience.dataforge.meta.toMutableMeta
|
||||
import space.kscience.dataforge.misc.DFBuilder
|
||||
@ -25,7 +25,7 @@ public class ContextBuilder internal constructor(
|
||||
internal val factories = HashMap<PluginFactory<*>, Meta>()
|
||||
internal var meta = meta.toMutableMeta()
|
||||
|
||||
public fun properties(action: MetaBuilder.() -> Unit) {
|
||||
public fun properties(action: MutableMeta.() -> Unit) {
|
||||
meta.action()
|
||||
}
|
||||
|
||||
@ -38,20 +38,20 @@ public class ContextBuilder internal constructor(
|
||||
parent.gatherInSequence<PluginFactory<*>>(PluginFactory.TYPE).values
|
||||
.find { it.tag.matches(tag) } ?: error("Can't resolve plugin factory for $tag")
|
||||
|
||||
public fun plugin(tag: PluginTag, metaBuilder: MetaBuilder.() -> Unit = {}) {
|
||||
public fun plugin(tag: PluginTag, mutableMeta: MutableMeta.() -> Unit = {}) {
|
||||
val factory = findPluginFactory(tag)
|
||||
factories[factory] = Meta(metaBuilder)
|
||||
factories[factory] = Meta(mutableMeta)
|
||||
}
|
||||
|
||||
public fun plugin(factory: PluginFactory<*>, meta: Meta) {
|
||||
factories[factory] = meta
|
||||
}
|
||||
|
||||
public fun plugin(factory: PluginFactory<*>, metaBuilder: MetaBuilder.() -> Unit = {}) {
|
||||
factories[factory] = Meta(metaBuilder)
|
||||
public fun plugin(factory: PluginFactory<*>, mutableMeta: MutableMeta.() -> Unit = {}) {
|
||||
factories[factory] = Meta(mutableMeta)
|
||||
}
|
||||
|
||||
public fun plugin(name: String, group: String = "", version: String = "", action: MetaBuilder.() -> Unit = {}) {
|
||||
public fun plugin(name: String, group: String = "", version: String = "", action: MutableMeta.() -> Unit = {}) {
|
||||
plugin(PluginTag(name, group, version), action)
|
||||
}
|
||||
|
||||
|
@ -1,18 +1,17 @@
|
||||
package space.kscience.dataforge.properties
|
||||
|
||||
import space.kscience.dataforge.meta.ObservableMeta
|
||||
|
||||
import space.kscience.dataforge.meta.MutableMeta
|
||||
import space.kscience.dataforge.meta.get
|
||||
import space.kscience.dataforge.meta.set
|
||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||
import space.kscience.dataforge.meta.transformations.nullableItemToObject
|
||||
import space.kscience.dataforge.meta.transformations.nullableObjectToMetaItem
|
||||
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: ObservableMeta,
|
||||
public val meta: MutableMeta,
|
||||
public val name: Name,
|
||||
public val converter: MetaConverter<T>,
|
||||
) : Property<T?> {
|
||||
|
@ -1,14 +1,13 @@
|
||||
package space.kscience.dataforge.properties
|
||||
|
||||
|
||||
import space.kscience.dataforge.meta.ObservableItemProvider
|
||||
import space.kscience.dataforge.meta.ObservableMeta
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.names.startsWith
|
||||
import space.kscience.dataforge.names.toName
|
||||
import kotlin.reflect.KMutableProperty1
|
||||
|
||||
@DFExperimental
|
||||
public fun <P : ObservableItemProvider, T : Any> P.property(property: KMutableProperty1<P, T?>): Property<T?> =
|
||||
public fun <P : ObservableMeta, T : Any> P.property(property: KMutableProperty1<P, T?>): Property<T?> =
|
||||
object : Property<T?> {
|
||||
override var value: T?
|
||||
get() = property.get(this@property)
|
||||
|
@ -33,11 +33,7 @@ public value class Path(public val tokens: List<PathToken>) : Iterable<PathToken
|
||||
public companion object {
|
||||
public const val PATH_SEGMENT_SEPARATOR: String = "/"
|
||||
|
||||
public fun parse(path: String): Path {
|
||||
val head = path.substringBefore(PATH_SEGMENT_SEPARATOR)
|
||||
val tail = path.substringAfter(PATH_SEGMENT_SEPARATOR)
|
||||
return PathToken.parse(head).asPath() + parse(tail)
|
||||
}
|
||||
public fun parse(path: String): Path = Path(path.split(PATH_SEGMENT_SEPARATOR).map { PathToken.parse(it) })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,14 @@
|
||||
package space.kscience.dataforge.provider
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class PathTest {
|
||||
@Test
|
||||
fun testParse(){
|
||||
val nameString = "a.b.c.d"
|
||||
val pathString = "a.b/c.d"
|
||||
assertEquals(1, Path.parse(nameString).length)
|
||||
assertEquals(2, Path.parse(pathString).length)
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import space.kscience.dataforge.data.*
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.MetaBuilder
|
||||
import space.kscience.dataforge.meta.MutableMeta
|
||||
import space.kscience.dataforge.meta.seal
|
||||
import space.kscience.dataforge.meta.toMutableMeta
|
||||
import space.kscience.dataforge.misc.DFBuilder
|
||||
@ -29,7 +29,7 @@ public data class ActionEnv(
|
||||
* Action environment
|
||||
*/
|
||||
@DFBuilder
|
||||
public class MapActionBuilder<T, R>(public var name: Name, public var meta: MetaBuilder, public val actionMeta: Meta) {
|
||||
public class MapActionBuilder<T, R>(public var name: Name, public var meta: MutableMeta, public val actionMeta: Meta) {
|
||||
public lateinit var result: suspend ActionEnv.(T) -> R
|
||||
|
||||
/**
|
||||
|
@ -6,7 +6,7 @@ import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.fold
|
||||
import space.kscience.dataforge.data.*
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.MetaBuilder
|
||||
import space.kscience.dataforge.meta.MutableMeta
|
||||
import space.kscience.dataforge.misc.DFBuilder
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.misc.DFInternal
|
||||
@ -18,7 +18,7 @@ import kotlin.reflect.typeOf
|
||||
|
||||
public class JoinGroup<T : Any, R : Any>(public var name: String, internal val set: DataSet<T>) {
|
||||
|
||||
public var meta: MetaBuilder = MetaBuilder()
|
||||
public var meta: MutableMeta = MutableMeta()
|
||||
|
||||
public lateinit var result: suspend ActionEnv.(Map<Name, T>) -> R
|
||||
|
||||
|
@ -7,7 +7,7 @@ import kotlinx.coroutines.launch
|
||||
import space.kscience.dataforge.data.*
|
||||
import space.kscience.dataforge.meta.Laminate
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.MetaBuilder
|
||||
import space.kscience.dataforge.meta.MutableMeta
|
||||
import space.kscience.dataforge.meta.toMutableMeta
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.misc.DFInternal
|
||||
@ -20,7 +20,7 @@ import kotlin.reflect.typeOf
|
||||
|
||||
public class SplitBuilder<T : Any, R : Any>(public val name: Name, public val meta: Meta) {
|
||||
|
||||
public class FragmentRule<T : Any, R : Any>(public val name: Name, public var meta: MetaBuilder) {
|
||||
public class FragmentRule<T : Any, R : Any>(public val name: Name, public var meta: MutableMeta) {
|
||||
public lateinit var result: suspend (T) -> R
|
||||
|
||||
public fun result(f: suspend (T) -> R) {
|
||||
|
@ -4,7 +4,7 @@ import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.MetaBuilder
|
||||
import space.kscience.dataforge.meta.MutableMeta
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.plus
|
||||
@ -124,8 +124,8 @@ public suspend inline fun <reified T : Any> DataSetBuilder<T>.static(name: Name,
|
||||
public suspend inline fun <reified T : Any> DataSetBuilder<T>.static(
|
||||
name: String,
|
||||
data: T,
|
||||
metaBuilder: MetaBuilder.() -> Unit,
|
||||
): Unit = emit(name.toName(), Data.static(data, Meta(metaBuilder)))
|
||||
mutableMeta: MutableMeta.() -> Unit,
|
||||
): Unit = emit(name.toName(), Data.static(data, Meta(mutableMeta)))
|
||||
|
||||
/**
|
||||
* Update data with given node data and meta with node meta.
|
||||
|
@ -1,7 +1,7 @@
|
||||
package space.kscience.dataforge.data
|
||||
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.MetaBuilder
|
||||
import space.kscience.dataforge.meta.MutableMeta
|
||||
|
||||
|
||||
/**
|
||||
@ -17,4 +17,4 @@ public suspend fun DataSetBuilder<*>.meta(meta: Meta): Unit = emit(DataSet.META_
|
||||
/**
|
||||
* Add meta-data node to a [DataSet]
|
||||
*/
|
||||
public suspend fun DataSetBuilder<*>.meta(metaBuilder: MetaBuilder.() -> Unit): Unit = meta(Meta(metaBuilder))
|
||||
public suspend fun DataSetBuilder<*>.meta(mutableMeta: MutableMeta.() -> Unit): Unit = meta(Meta(mutableMeta))
|
@ -2,7 +2,7 @@ package space.kscience.dataforge.data
|
||||
|
||||
import kotlinx.coroutines.flow.*
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.MetaBuilder
|
||||
import space.kscience.dataforge.meta.MutableMeta
|
||||
import space.kscience.dataforge.meta.seal
|
||||
import space.kscience.dataforge.meta.toMutableMeta
|
||||
import space.kscience.dataforge.misc.DFInternal
|
||||
@ -140,7 +140,7 @@ public suspend inline fun <T : Any, reified R : Any> Flow<NamedData<T>>.foldToDa
|
||||
public suspend fun <T : Any, R : Any> DataSet<T>.map(
|
||||
outputType: KType,
|
||||
coroutineContext: CoroutineContext = EmptyCoroutineContext,
|
||||
metaTransform: MetaBuilder.() -> Unit = {},
|
||||
metaTransform: MutableMeta.() -> Unit = {},
|
||||
block: suspend (T) -> R,
|
||||
): DataTree<R> = DataTree<R>(outputType) {
|
||||
populate(
|
||||
@ -156,7 +156,7 @@ public suspend fun <T : Any, R : Any> DataSet<T>.map(
|
||||
@OptIn(DFInternal::class)
|
||||
public suspend inline fun <T : Any, reified R : Any> DataSet<T>.map(
|
||||
coroutineContext: CoroutineContext = EmptyCoroutineContext,
|
||||
noinline metaTransform: MetaBuilder.() -> Unit = {},
|
||||
noinline metaTransform: MutableMeta.() -> Unit = {},
|
||||
noinline block: suspend (T) -> R,
|
||||
): DataTree<R> = map(typeOf<R>(), coroutineContext, metaTransform, block)
|
||||
|
||||
|
@ -34,7 +34,7 @@ public fun Meta.toYaml(): YamlMap {
|
||||
return YamlMap(map)
|
||||
}
|
||||
|
||||
private class YamlMeta(private val yamlMap: YamlMap, private val descriptor: NodeDescriptor? = null) : MetaBase() {
|
||||
private class YamlMeta(private val yamlMap: YamlMap, private val descriptor: NodeDescriptor? = null) : AbstractTypedMeta() {
|
||||
|
||||
private fun buildItems(): Map<NameToken, MetaItem> {
|
||||
val map = LinkedHashMap<NameToken, MetaItem>()
|
||||
|
@ -100,7 +100,7 @@ public object BinaryMetaFormat : MetaFormat, MetaFormatFactory {
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
public fun Input.readMetaItem(): TypedMetaItem<MetaBuilder> {
|
||||
public fun Input.readMetaItem(): TypedMetaItem<MutableMeta> {
|
||||
return when (val keyChar = readByte().toInt().toChar()) {
|
||||
'S' -> MetaItemValue(StringValue(readString()))
|
||||
'N' -> MetaItemValue(Null)
|
||||
|
@ -4,7 +4,7 @@ import io.ktor.utils.io.core.Output
|
||||
import space.kscience.dataforge.meta.*
|
||||
|
||||
public class EnvelopeBuilder : Envelope {
|
||||
private val metaBuilder = MetaBuilder()
|
||||
private val metaBuilder = MutableMeta()
|
||||
|
||||
override var data: Binary? = null
|
||||
override var meta: Meta
|
||||
@ -13,7 +13,7 @@ public class EnvelopeBuilder : Envelope {
|
||||
metaBuilder.update(value)
|
||||
}
|
||||
|
||||
public fun meta(block: MetaBuilder.() -> Unit) {
|
||||
public fun meta(block: MutableMeta.() -> Unit) {
|
||||
metaBuilder.apply(block)
|
||||
}
|
||||
|
||||
|
@ -11,9 +11,8 @@ import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.io.IOFormat.Companion.NAME_KEY
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
|
||||
import space.kscience.dataforge.meta.node
|
||||
import space.kscience.dataforge.meta.toJson
|
||||
import space.kscience.dataforge.meta.toMetaItem
|
||||
import space.kscience.dataforge.meta.toMeta
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.typeOf
|
||||
|
||||
@ -36,7 +35,7 @@ public class JsonMetaFormat(private val json: Json = DEFAULT_JSON) : MetaFormat
|
||||
override fun readMeta(input: Input, descriptor: NodeDescriptor?): Meta {
|
||||
val str = input.readUtf8String()//readByteArray().decodeToString()
|
||||
val jsonElement = json.parseToJsonElement(str)
|
||||
val item = jsonElement.toMetaItem(descriptor)
|
||||
val item = jsonElement.toMeta(descriptor)
|
||||
return item.node ?: Meta.EMPTY
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,6 @@ import kotlinx.serialization.json.*
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.meta.JsonMeta.Companion.JSON_ARRAY_KEY
|
||||
import space.kscience.dataforge.values.ListValue
|
||||
import space.kscience.dataforge.values.number
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@ -72,7 +71,7 @@ class MetaFormatTest {
|
||||
add(JsonPrimitive(3.0))
|
||||
})
|
||||
}
|
||||
val meta = json.toMetaItem().node!!
|
||||
val meta = json.toMeta().node!!
|
||||
|
||||
assertEquals(true, meta["$JSON_ARRAY_KEY[0].$JSON_ARRAY_KEY[1].d"].boolean)
|
||||
assertEquals("value", meta["$JSON_ARRAY_KEY[1]"].string)
|
||||
@ -98,7 +97,7 @@ class MetaFormatTest {
|
||||
}
|
||||
""".trimIndent()
|
||||
val json = Json.parseToJsonElement(jsonString)
|
||||
val meta = json.toMetaItem().node!!
|
||||
val meta = json.toMeta().node!!
|
||||
assertEquals(ListValue.EMPTY, meta["comments"].value)
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
package space.kscience.dataforge.meta
|
||||
|
||||
import space.kscience.dataforge.misc.DFBuilder
|
||||
import space.kscience.dataforge.names.Name
|
||||
import kotlin.properties.ReadWriteProperty
|
||||
|
||||
/**
|
||||
* A container that holds a [ObservableMeta].
|
||||
@ -11,15 +9,11 @@ public interface Configurable {
|
||||
/**
|
||||
* Backing config
|
||||
*/
|
||||
public val config: ObservableMeta
|
||||
public val config: MutableMeta
|
||||
}
|
||||
|
||||
|
||||
public fun <T : Configurable> T.configure(meta: Meta): T = this.apply { config.update(meta) }
|
||||
|
||||
@DFBuilder
|
||||
public inline fun <T : Configurable> T.configure(action: ObservableMeta.() -> Unit): T = apply { config.apply(action) }
|
||||
|
||||
/* Node delegates */
|
||||
|
||||
public fun Configurable.config(key: Name? = null): ReadWriteProperty<Any?, ObservableMeta?> = config.node(key)
|
||||
public inline fun <T : Configurable> T.configure(action: MutableMeta.() -> Unit): T = apply { config.apply(action) }
|
@ -1,111 +0,0 @@
|
||||
package space.kscience.dataforge.meta
|
||||
|
||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.asName
|
||||
import space.kscience.dataforge.values.Value
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
|
||||
/* Meta delegates */
|
||||
|
||||
public typealias ItemDelegate = ReadOnlyProperty<Any?, MetaItem?>
|
||||
|
||||
public fun ItemProvider.item(key: Name? = null): ItemDelegate = ReadOnlyProperty { _, property ->
|
||||
get(key ?: property.name.asName())
|
||||
}
|
||||
|
||||
//TODO add caching for sealed nodes
|
||||
|
||||
|
||||
/**
|
||||
* Apply a converter to this delegate creating a delegate with a custom type
|
||||
*/
|
||||
public fun <R : Any> ItemDelegate.convert(
|
||||
converter: MetaConverter<R>,
|
||||
): ReadOnlyProperty<Any?, R?> = ReadOnlyProperty { thisRef, property ->
|
||||
this@convert.getValue(thisRef, property)?.let(converter::itemToObject)
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
public fun <R : Any> ItemDelegate.convert(
|
||||
converter: MetaConverter<R>,
|
||||
default: () -> R,
|
||||
): ReadOnlyProperty<Any?, R> = ReadOnlyProperty<Any?, R> { thisRef, property ->
|
||||
this@convert.getValue(thisRef, property)?.let(converter::itemToObject) ?: default()
|
||||
}
|
||||
|
||||
/**
|
||||
* A converter with a custom reader transformation
|
||||
*/
|
||||
public fun <R> ItemDelegate.convert(
|
||||
reader: (MetaItem?) -> R,
|
||||
): ReadOnlyProperty<Any?, R> = ReadOnlyProperty<Any?, R> { thisRef, property ->
|
||||
this@convert.getValue(thisRef, property).let(reader)
|
||||
}
|
||||
|
||||
/* Read-only delegates for [ItemProvider] */
|
||||
|
||||
/**
|
||||
* A property delegate that uses custom key
|
||||
*/
|
||||
public fun ItemProvider.value(key: Name? = null): ReadOnlyProperty<Any?, Value?> =
|
||||
item(key).convert(MetaConverter.value)
|
||||
|
||||
public fun ItemProvider.string(key: Name? = null): ReadOnlyProperty<Any?, String?> =
|
||||
item(key).convert(MetaConverter.string)
|
||||
|
||||
public fun ItemProvider.boolean(key: Name? = null): ReadOnlyProperty<Any?, Boolean?> =
|
||||
item(key).convert(MetaConverter.boolean)
|
||||
|
||||
public fun ItemProvider.number(key: Name? = null): ReadOnlyProperty<Any?, Number?> =
|
||||
item(key).convert(MetaConverter.number)
|
||||
|
||||
public fun ItemProvider.double(key: Name? = null): ReadOnlyProperty<Any?, Double?> =
|
||||
item(key).convert(MetaConverter.double)
|
||||
|
||||
public fun ItemProvider.float(key: Name? = null): ReadOnlyProperty<Any?, Float?> =
|
||||
item(key).convert(MetaConverter.float)
|
||||
|
||||
public fun ItemProvider.int(key: Name? = null): ReadOnlyProperty<Any?, Int?> =
|
||||
item(key).convert(MetaConverter.int)
|
||||
|
||||
public fun ItemProvider.long(key: Name? = null): ReadOnlyProperty<Any?, Long?> =
|
||||
item(key).convert(MetaConverter.long)
|
||||
|
||||
public fun ItemProvider.node(key: Name? = null): ReadOnlyProperty<Any?, Meta?> =
|
||||
item(key).convert(MetaConverter.meta)
|
||||
|
||||
public fun ItemProvider.string(default: String, key: Name? = null): ReadOnlyProperty<Any?, String> =
|
||||
item(key).convert(MetaConverter.string) { default }
|
||||
|
||||
public fun ItemProvider.boolean(default: Boolean, key: Name? = null): ReadOnlyProperty<Any?, Boolean> =
|
||||
item(key).convert(MetaConverter.boolean) { default }
|
||||
|
||||
public fun ItemProvider.number(default: Number, key: Name? = null): ReadOnlyProperty<Any?, Number> =
|
||||
item(key).convert(MetaConverter.number) { default }
|
||||
|
||||
public fun ItemProvider.double(default: Double, key: Name? = null): ReadOnlyProperty<Any?, Double> =
|
||||
item(key).convert(MetaConverter.double) { default }
|
||||
|
||||
public fun ItemProvider.float(default: Float, key: Name? = null): ReadOnlyProperty<Any?, Float> =
|
||||
item(key).convert(MetaConverter.float) { default }
|
||||
|
||||
public fun ItemProvider.int(default: Int, key: Name? = null): ReadOnlyProperty<Any?, Int> =
|
||||
item(key).convert(MetaConverter.int) { default }
|
||||
|
||||
public fun ItemProvider.long(default: Long, key: Name? = null): ReadOnlyProperty<Any?, Long> =
|
||||
item(key).convert(MetaConverter.long) { default }
|
||||
|
||||
public inline fun <reified E : Enum<E>> ItemProvider.enum(default: E, key: Name? = null): ReadOnlyProperty<Any?, E> =
|
||||
item(key).convert(MetaConverter.enum()) { default }
|
||||
|
||||
public fun ItemProvider.string(key: Name? = null, default: () -> String): ReadOnlyProperty<Any?, String> =
|
||||
item(key).convert(MetaConverter.string, default)
|
||||
|
||||
public fun ItemProvider.boolean(key: Name? = null, default: () -> Boolean): ReadOnlyProperty<Any?, Boolean> =
|
||||
item(key).convert(MetaConverter.boolean, default)
|
||||
|
||||
public fun ItemProvider.number(key: Name? = null, default: () -> Number): ReadOnlyProperty<Any?, Number> =
|
||||
item(key).convert(MetaConverter.number, default)
|
@ -1,88 +0,0 @@
|
||||
package space.kscience.dataforge.meta
|
||||
|
||||
import space.kscience.dataforge.names.*
|
||||
|
||||
public fun interface ItemProvider {
|
||||
//getItem used instead of get in order to provide extension freedom
|
||||
public fun getItem(name: Name): MetaItem?
|
||||
|
||||
public companion object {
|
||||
public val EMPTY: ItemProvider = ItemProvider { null }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Get operations*/
|
||||
|
||||
/**
|
||||
* 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 return current [Meta] as a [MetaItemNode]
|
||||
*/
|
||||
public operator fun ItemProvider?.get(name: Name): MetaItem? = this?.getItem(name)
|
||||
|
||||
/**
|
||||
* Root item of this provider
|
||||
*/
|
||||
public val ItemProvider.rootItem: MetaItem? get() = get(Name.EMPTY)
|
||||
|
||||
/**
|
||||
* The root node of this item provider if it is present
|
||||
*/
|
||||
public val ItemProvider.rootNode: Meta? get() = rootItem.node
|
||||
|
||||
/**
|
||||
* Parse [Name] from [key] using full name notation and pass it to [Meta.get]
|
||||
*/
|
||||
public operator fun ItemProvider?.get(key: String): MetaItem? = this?.get(key.toName())
|
||||
|
||||
/**
|
||||
* Create a provider that uses given provider for default values if those are not found in this provider
|
||||
*/
|
||||
public fun ItemProvider.withDefault(default: ItemProvider?): ItemProvider = if (default == null) {
|
||||
this
|
||||
} else {
|
||||
ItemProvider {
|
||||
this[it] ?: default[it]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all items matching given name. The index of the last element, if present is used as a [Regex],
|
||||
* against which indexes of elements are matched.
|
||||
*/
|
||||
public fun ItemProvider.getIndexed(name: Name): Map<String?, MetaItem> {
|
||||
val root: Meta = when (name.length) {
|
||||
0 -> error("Can't use empty name for 'getIndexed'")
|
||||
1 -> this.rootNode ?: return emptyMap()
|
||||
else -> this[name.cutLast()].node ?: return emptyMap()
|
||||
}
|
||||
|
||||
val (body, index) = name.lastOrNull()!!
|
||||
return if (index == null) {
|
||||
root.items.filter { it.key.body == body }.mapKeys { it.key.index }
|
||||
} else {
|
||||
val regex = index.toRegex()
|
||||
root.items.filter { it.key.body == body && (regex.matches(it.key.index ?: "")) }
|
||||
.mapKeys { it.key.index }
|
||||
}
|
||||
}
|
||||
|
||||
public fun ItemProvider.getIndexed(name: String): Map<String?, MetaItem> = getIndexed(name.toName())
|
||||
|
||||
/**
|
||||
* Return a provider referencing a child node
|
||||
*/
|
||||
public fun ItemProvider.getChild(childName: Name): ItemProvider = get(childName).node ?: ItemProvider.EMPTY
|
||||
|
||||
public fun ItemProvider.getChild(childName: String): ItemProvider = getChild(childName.toName())
|
||||
|
||||
///**
|
||||
// * Get all items matching given name.
|
||||
// */
|
||||
//@Suppress("UNCHECKED_CAST")
|
||||
//public fun <M : TypedMeta<M>> M.getIndexed(name: Name): Map<String?, MetaItem<M>> =
|
||||
// (this as Meta).getIndexed(name) as Map<String?, MetaItem<M>>
|
||||
//
|
||||
//public fun <M : TypedMeta<M>> M.getIndexed(name: String): Map<String?, MetaItem<M>> =
|
||||
// getIndexed(name.toName())
|
@ -3,20 +3,15 @@
|
||||
package space.kscience.dataforge.meta
|
||||
|
||||
import kotlinx.serialization.json.*
|
||||
import space.kscience.dataforge.meta.JsonMeta.Companion.JSON_ARRAY_KEY
|
||||
import space.kscience.dataforge.meta.descriptors.ItemDescriptor
|
||||
import space.kscience.dataforge.meta.descriptors.ItemDescriptor.Companion.DEFAULT_INDEX_KEY
|
||||
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
|
||||
import space.kscience.dataforge.meta.descriptors.ValueDescriptor
|
||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||
import space.kscience.dataforge.names.NameToken
|
||||
import space.kscience.dataforge.names.withIndex
|
||||
import space.kscience.dataforge.values.*
|
||||
|
||||
|
||||
/**
|
||||
* @param descriptor reserved for custom serialization in future
|
||||
*/
|
||||
public fun Value.toJson(descriptor: ValueDescriptor? = null): JsonElement = when (type) {
|
||||
public fun Value.toJson(descriptor: MetaDescriptor? = null): JsonElement = when (type) {
|
||||
ValueType.NUMBER -> JsonPrimitive(numberOrNull)
|
||||
ValueType.STRING -> JsonPrimitive(string)
|
||||
ValueType.BOOLEAN -> JsonPrimitive(boolean)
|
||||
@ -26,73 +21,47 @@ public fun Value.toJson(descriptor: ValueDescriptor? = null): JsonElement = when
|
||||
|
||||
//Use these methods to customize JSON key mapping
|
||||
@Suppress("NULLABLE_EXTENSION_OPERATOR_WITH_SAFE_CALL_RECEIVER")
|
||||
private fun String.toJsonKey(descriptor: ItemDescriptor?) = descriptor?.attributes?.get("jsonName").string ?: toString()
|
||||
private fun String.toJsonKey(descriptor: MetaDescriptor?) = descriptor?.attributes?.get("jsonName").string ?: toString()
|
||||
|
||||
//private fun NodeDescriptor?.getDescriptor(key: String) = this?.items?.get(key)
|
||||
|
||||
/**
|
||||
* Convert given [Meta] to [JsonObject]. Primitives and nodes are copied as is, same name siblings are treated as json arrays
|
||||
*/
|
||||
private fun Meta.toJsonWithIndex(descriptor: NodeDescriptor?, indexValue: String?): JsonObject {
|
||||
|
||||
val elementMap = HashMap<String, JsonElement>()
|
||||
|
||||
fun MetaItem.toJsonElement(itemDescriptor: ItemDescriptor?, index: String?): JsonElement = when (this) {
|
||||
is MetaItemValue -> {
|
||||
value.toJson(itemDescriptor as? ValueDescriptor)
|
||||
}
|
||||
is MetaItemNode -> {
|
||||
node.toJsonWithIndex(itemDescriptor as? NodeDescriptor, index)
|
||||
private fun Meta.toJsonWithIndex(descriptor: MetaDescriptor?, index: String?): JsonElement = if (items.isEmpty()) {
|
||||
value?.toJson(descriptor) ?: JsonNull
|
||||
} else {
|
||||
val pairs: MutableList<Pair<String, JsonElement>> = items.entries.groupBy {
|
||||
it.key.body
|
||||
}.mapTo(ArrayList()) { (body, list) ->
|
||||
val childDescriptor = descriptor?.children?.get(body)
|
||||
if (list.size == 1) {
|
||||
val (token, element) = list.first()
|
||||
val child: JsonElement = element.toJsonWithIndex(childDescriptor, token.index)
|
||||
body to child
|
||||
} else {
|
||||
val elements: List<JsonElement> = list.sortedBy { it.key.index }.mapIndexed { index, entry ->
|
||||
//Use index if it is not equal to the item order
|
||||
val actualIndex = if (index.toString() != entry.key.index) entry.key.index else null
|
||||
entry.value.toJsonWithIndex(childDescriptor, actualIndex)
|
||||
}
|
||||
body to JsonArray(elements)
|
||||
}
|
||||
}
|
||||
|
||||
fun addElement(key: String) {
|
||||
val itemDescriptor = descriptor?.items?.get(key)
|
||||
val jsonKey = key.toJsonKey(itemDescriptor)
|
||||
val items: Map<String?, MetaItem> = getIndexed(key)
|
||||
when (items.size) {
|
||||
0 -> {
|
||||
//do nothing
|
||||
}
|
||||
1 -> {
|
||||
val (index, item) = items.entries.first()
|
||||
val element = item.toJsonElement(itemDescriptor, index)
|
||||
if (index == null) {
|
||||
elementMap[jsonKey] = element
|
||||
} else {
|
||||
//treat arrays with single element
|
||||
elementMap[jsonKey] = buildJsonArray {
|
||||
add(element)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
val array = buildJsonArray {
|
||||
items.forEach { (index, item) ->
|
||||
add(item.toJsonElement(itemDescriptor, index))
|
||||
}
|
||||
}
|
||||
elementMap[jsonKey] = array
|
||||
}
|
||||
}
|
||||
//Add index if needed
|
||||
if (index != null) {
|
||||
pairs += Meta.INDEX_KEY to JsonPrimitive(index)
|
||||
}
|
||||
|
||||
((descriptor?.items?.keys ?: emptySet()) + items.keys.map { it.body }).forEach(::addElement)
|
||||
|
||||
|
||||
if (indexValue != null) {
|
||||
val indexKey = descriptor?.indexKey ?: DEFAULT_INDEX_KEY
|
||||
elementMap[indexKey] = JsonPrimitive(indexValue)
|
||||
//Add value if needed
|
||||
if (value != null) {
|
||||
pairs += Meta.VALUE_KEY to value!!.toJson(null)
|
||||
}
|
||||
|
||||
return JsonObject(elementMap)
|
||||
JsonObject(pairs.toMap())
|
||||
}
|
||||
|
||||
public fun Meta.toJson(descriptor: NodeDescriptor? = null): JsonObject = toJsonWithIndex(descriptor, null)
|
||||
public fun Meta.toJson(descriptor: MetaDescriptor? = null): JsonElement = toJsonWithIndex(descriptor, null)
|
||||
|
||||
public fun JsonObject.toMeta(descriptor: NodeDescriptor? = null): JsonMeta = JsonMeta(this, descriptor)
|
||||
public fun JsonObject.toMeta(descriptor: MetaDescriptor? = null): JsonMeta = JsonMeta(this, descriptor)
|
||||
|
||||
public fun JsonPrimitive.toValue(descriptor: ValueDescriptor?): Value {
|
||||
public fun JsonPrimitive.toValue(descriptor: MetaDescriptor?): Value {
|
||||
return when (this) {
|
||||
JsonNull -> Null
|
||||
else -> {
|
||||
@ -105,79 +74,47 @@ public fun JsonPrimitive.toValue(descriptor: ValueDescriptor?): Value {
|
||||
}
|
||||
}
|
||||
|
||||
public fun JsonElement.toMetaItem(descriptor: ItemDescriptor? = null): TypedMetaItem<JsonMeta> = when (this) {
|
||||
is JsonPrimitive -> {
|
||||
val value = this.toValue(descriptor as? ValueDescriptor)
|
||||
MetaItemValue(value)
|
||||
}
|
||||
is JsonObject -> {
|
||||
val meta = JsonMeta(this, descriptor as? NodeDescriptor)
|
||||
MetaItemNode(meta)
|
||||
}
|
||||
is JsonArray -> {
|
||||
if (this.all { it is JsonPrimitive }) {
|
||||
val value = if (isEmpty()) {
|
||||
Null
|
||||
} else {
|
||||
map<JsonElement, Value> {
|
||||
//We already checked that all values are primitives
|
||||
(it as JsonPrimitive).toValue(descriptor as? ValueDescriptor)
|
||||
}.asValue()
|
||||
}
|
||||
MetaItemValue(value)
|
||||
} else {
|
||||
//We can't return multiple items therefore we create top level node
|
||||
buildJsonObject { put(JSON_ARRAY_KEY, this@toMetaItem) }.toMetaItem(descriptor)
|
||||
}
|
||||
}
|
||||
}
|
||||
public fun JsonElement.toMeta(descriptor: MetaDescriptor? = null): TypedMeta<JsonMeta> = JsonMeta(this, descriptor)
|
||||
|
||||
/**
|
||||
* A meta wrapping json object
|
||||
*/
|
||||
public class JsonMeta(private val json: JsonObject, private val descriptor: NodeDescriptor? = null) : MetaBase() {
|
||||
public class JsonMeta(
|
||||
private val json: JsonElement,
|
||||
private val descriptor: MetaDescriptor? = null
|
||||
) : TypedMeta<JsonMeta> {
|
||||
|
||||
private fun buildItems(): Map<NameToken, TypedMetaItem<JsonMeta>> {
|
||||
val map = LinkedHashMap<NameToken, TypedMetaItem<JsonMeta>>()
|
||||
private val indexName by lazy { descriptor?.indexKey ?: Meta.INDEX_KEY }
|
||||
|
||||
json.forEach { (jsonKey, value) ->
|
||||
val key = NameToken(jsonKey)
|
||||
val itemDescriptor = descriptor?.items?.get(jsonKey)
|
||||
when (value) {
|
||||
is JsonPrimitive -> {
|
||||
map[key] = MetaItemValue(value.toValue(itemDescriptor as? ValueDescriptor))
|
||||
}
|
||||
is JsonObject -> {
|
||||
map[key] = MetaItemNode(
|
||||
JsonMeta(
|
||||
value,
|
||||
itemDescriptor as? NodeDescriptor
|
||||
)
|
||||
)
|
||||
}
|
||||
is JsonArray -> if (value.all { it is JsonPrimitive }) {
|
||||
val listValue = ListValue(
|
||||
value.map {
|
||||
//We already checked that all values are primitives
|
||||
(it as JsonPrimitive).toValue(itemDescriptor as? ValueDescriptor)
|
||||
}
|
||||
)
|
||||
map[key] = MetaItemValue(listValue)
|
||||
} else value.forEachIndexed { index, jsonElement ->
|
||||
val indexKey = (itemDescriptor as? NodeDescriptor)?.indexKey ?: DEFAULT_INDEX_KEY
|
||||
val indexValue: String = (jsonElement as? JsonObject)
|
||||
?.get(indexKey)?.jsonPrimitive?.contentOrNull
|
||||
?: index.toString() //In case index is non-string, the backward transformation will be broken.
|
||||
|
||||
val token = key.withIndex(indexValue)
|
||||
map[token] = jsonElement.toMetaItem(itemDescriptor)
|
||||
}
|
||||
override val value: Value? by lazy {
|
||||
when (json) {
|
||||
is JsonPrimitive -> json.toValue(descriptor)
|
||||
is JsonObject -> json[Meta.VALUE_KEY]?.let { JsonMeta(it).value }
|
||||
is JsonArray -> if (json.all { it is JsonPrimitive }) {
|
||||
//convert array of primitives to ListValue
|
||||
json.map { (it as JsonPrimitive).toValue(descriptor) }.asValue()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
override val items: Map<NameToken, TypedMetaItem<JsonMeta>> by lazy(::buildItems)
|
||||
override val items: Map<NameToken, JsonMeta> by lazy {
|
||||
when (json) {
|
||||
is JsonPrimitive -> emptyMap()
|
||||
is JsonObject -> json.entries.associate { (name, child) ->
|
||||
val index = (child as? JsonObject)?.get(indexName)?.jsonPrimitive?.content
|
||||
val token = NameToken(name, index)
|
||||
token to JsonMeta(child, descriptor?.children?.get(name))
|
||||
}
|
||||
is JsonArray -> json.mapIndexed { index, child ->
|
||||
//Use explicit index or or order for index
|
||||
val tokenIndex = (child as? JsonObject)?.get(indexName)?.jsonPrimitive?.content ?: index.toString()
|
||||
val token = NameToken(JSON_ARRAY_KEY, tokenIndex)
|
||||
token to JsonMeta(child)
|
||||
}.toMap()
|
||||
}
|
||||
}
|
||||
|
||||
public companion object {
|
||||
/**
|
||||
|
@ -2,12 +2,15 @@ package space.kscience.dataforge.meta
|
||||
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.NameToken
|
||||
import space.kscience.dataforge.values.Value
|
||||
|
||||
/**
|
||||
* A meta laminate consisting of multiple immutable meta layers. For mutable front layer, use [Scheme].
|
||||
* If [layers] list contains a [Laminate] it is flat-mapped.
|
||||
*/
|
||||
public class Laminate(layers: List<Meta>) : MetaBase() {
|
||||
public class Laminate(layers: List<Meta>) : TypedMeta<SealedMeta> {
|
||||
|
||||
override val value: Value? = layers.firstNotNullOfOrNull { it.value }
|
||||
|
||||
public val layers: List<Meta> = layers.flatMap {
|
||||
if (it is Laminate) {
|
||||
@ -17,7 +20,7 @@ public class Laminate(layers: List<Meta>) : MetaBase() {
|
||||
}
|
||||
}
|
||||
|
||||
override val items: Map<NameToken, TypedMetaItem<Meta>> by lazy {
|
||||
override val items: Map<NameToken, SealedMeta> by lazy {
|
||||
layers.map { it.items.keys }.flatten().associateWith { key ->
|
||||
layers.asSequence().map { it.items[key] }.filterNotNull().let(replaceRule)
|
||||
}
|
||||
@ -30,7 +33,7 @@ public class Laminate(layers: List<Meta>) : MetaBase() {
|
||||
val items = layers.map { it.items.keys }.flatten().associateWith { key ->
|
||||
layers.asSequence().map { it.items[key] }.filterNotNull().merge()
|
||||
}
|
||||
return SealedMeta(items)
|
||||
return SealedMeta(value, items)
|
||||
}
|
||||
|
||||
public companion object {
|
||||
@ -40,33 +43,21 @@ public class Laminate(layers: List<Meta>) : MetaBase() {
|
||||
*
|
||||
* TODO add picture
|
||||
*/
|
||||
public val replaceRule: (Sequence<MetaItem>) -> TypedMetaItem<SealedMeta> = { it.first().seal() }
|
||||
public val replaceRule: (Sequence<Meta>) -> SealedMeta = { it.first().seal() }
|
||||
|
||||
private fun Sequence<MetaItem>.merge(): TypedMetaItem<SealedMeta> {
|
||||
return when {
|
||||
all { it is MetaItemValue } -> //If all items are values, take first
|
||||
first().seal()
|
||||
all { it is MetaItemNode } -> {
|
||||
//list nodes in item
|
||||
val nodes = map { (it as MetaItemNode).node }
|
||||
//represent as key->value entries
|
||||
val entries = nodes.flatMap { it.items.entries.asSequence() }
|
||||
//group by keys
|
||||
val groups = entries.groupBy { it.key }
|
||||
// recursively apply the rule
|
||||
val items = groups.mapValues { entry ->
|
||||
entry.value.asSequence().map { it.value }.merge()
|
||||
}
|
||||
MetaItemNode(SealedMeta(items))
|
||||
|
||||
}
|
||||
else -> map {
|
||||
when (it) {
|
||||
is MetaItemValue -> MetaItemNode(Meta { Meta.VALUE_KEY put it.value })
|
||||
is MetaItemNode -> it
|
||||
}
|
||||
}.merge()
|
||||
private fun Sequence<Meta>.merge(): SealedMeta {
|
||||
val value = firstNotNullOfOrNull { it.value }
|
||||
//list nodes in item
|
||||
val nodes = toList()
|
||||
//represent as key->value entries
|
||||
val entries = nodes.flatMap { it.items.entries.asSequence() }
|
||||
//group by keys
|
||||
val groups = entries.groupBy { it.key }
|
||||
// recursively apply the rule
|
||||
val items = groups.mapValues { entry ->
|
||||
entry.value.asSequence().map { it.value }.merge()
|
||||
}
|
||||
return SealedMeta(value,items)
|
||||
}
|
||||
|
||||
|
||||
@ -74,7 +65,7 @@ public class Laminate(layers: List<Meta>) : MetaBase() {
|
||||
* The values a replaced but meta children are joined
|
||||
* TODO add picture
|
||||
*/
|
||||
public val mergeRule: (Sequence<MetaItem>) -> TypedMetaItem<SealedMeta> = { it.merge() }
|
||||
public val mergeRule: (Sequence<Meta>) -> TypedMeta<SealedMeta> = { it.merge() }
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,7 +75,7 @@ public fun Laminate(vararg layers: Meta?): Laminate = Laminate(layers.filterNotN
|
||||
/**
|
||||
* Performance optimized version of get method
|
||||
*/
|
||||
public fun Laminate.getFirst(name: Name): MetaItem? {
|
||||
public fun Laminate.getFirst(name: Name): Meta? {
|
||||
layers.forEach { layer ->
|
||||
layer[name]?.let { return it }
|
||||
}
|
||||
|
@ -1,51 +1,37 @@
|
||||
package space.kscience.dataforge.meta
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import space.kscience.dataforge.misc.Type
|
||||
import space.kscience.dataforge.names.*
|
||||
import space.kscience.dataforge.values.Value
|
||||
import space.kscience.dataforge.values.*
|
||||
|
||||
|
||||
/**
|
||||
* The object that could be represented as [Meta]. Meta provided by [toMeta] method should fully represent object state.
|
||||
* Meaning that two states with the same meta are equal.
|
||||
*/
|
||||
@Serializable(MetaSerializer::class)
|
||||
|
||||
public interface MetaRepr {
|
||||
public fun toMeta(): Meta
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic meta tree representation. Elements are [TypedMetaItem] objects that could be represented by three different entities:
|
||||
* * [MetaItemValue] (leaf)
|
||||
* * [MetaItemNode] single node
|
||||
*
|
||||
* * Same name siblings are supported via elements with the same [Name] but different queries
|
||||
* A meta node
|
||||
* TODO add documentation
|
||||
* Same name siblings are supported via elements with the same [Name] but different indices.
|
||||
*/
|
||||
@Type(Meta.TYPE)
|
||||
@Serializable(MetaSerializer::class)
|
||||
public interface Meta : MetaRepr, ItemProvider {
|
||||
/**
|
||||
* Top level items of meta tree
|
||||
*/
|
||||
public val items: Map<NameToken, MetaItem>
|
||||
|
||||
override fun getItem(name: Name): MetaItem? {
|
||||
if (name.isEmpty()) return MetaItemNode(this)
|
||||
return name.firstOrNull()?.let { token ->
|
||||
val tail = name.cutFirst()
|
||||
when (tail.length) {
|
||||
0 -> items[token]
|
||||
else -> items[token]?.node?.get(tail)
|
||||
}
|
||||
}
|
||||
}
|
||||
public interface Meta : MetaRepr {
|
||||
public val value: Value?
|
||||
public val items: Map<NameToken, Meta>
|
||||
|
||||
override fun toMeta(): Meta = this
|
||||
|
||||
override fun equals(other: Any?): Boolean
|
||||
|
||||
override fun hashCode(): Int
|
||||
|
||||
override fun toString(): String
|
||||
override fun equals(other: Any?): Boolean
|
||||
override fun hashCode(): Int
|
||||
|
||||
public companion object {
|
||||
public const val TYPE: String = "meta"
|
||||
@ -54,43 +40,179 @@ public interface Meta : MetaRepr, ItemProvider {
|
||||
* A key for single value node
|
||||
*/
|
||||
public const val VALUE_KEY: String = "@value"
|
||||
public const val INDEX_KEY: String = "@index"
|
||||
|
||||
public fun equals(meta1: Meta?, meta2: Meta?): Boolean = meta1?.items == meta2?.items
|
||||
|
||||
public val EMPTY: Meta = object : MetaBase() {
|
||||
override val items: Map<NameToken, MetaItem> = emptyMap()
|
||||
public fun hashCode(meta: Meta): Int {
|
||||
var result = meta.value?.hashCode() ?: 0
|
||||
result = 31 * result + meta.items.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
public fun equals(meta1: Meta?, meta2: Meta?): Boolean {
|
||||
return meta1?.value == meta2?.value && meta1?.items == meta2?.items
|
||||
}
|
||||
|
||||
private val json = Json {
|
||||
prettyPrint = true
|
||||
useArrayPolymorphism = true
|
||||
}
|
||||
|
||||
public fun toString(meta: Meta): String = json.encodeToString(MetaSerializer, meta)
|
||||
|
||||
public val EMPTY: Meta = SealedMeta(null, emptyMap())
|
||||
}
|
||||
}
|
||||
|
||||
public operator fun Meta.get(token: NameToken): MetaItem? = items.get(token)
|
||||
|
||||
public operator fun Meta.get(token: NameToken): Meta? = items[token]
|
||||
|
||||
/**
|
||||
* Get a sequence of [Name]-[Value] pairs
|
||||
* 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 return current [Meta]
|
||||
*/
|
||||
public fun Meta.valueSequence(): Sequence<Pair<Name, Value>> {
|
||||
return items.asSequence().flatMap { (key, item) ->
|
||||
when (item) {
|
||||
is MetaItemValue -> sequenceOf(key.asName() to item.value)
|
||||
is MetaItemNode -> item.node.valueSequence().map { pair -> (key.asName() + pair.first) to pair.second }
|
||||
public tailrec operator fun Meta.get(name: Name): Meta? = if (name.isEmpty()) {
|
||||
this
|
||||
} else {
|
||||
get(name.firstOrNull()!!)?.get(name.cutFirst())
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse [Name] from [key] using full name notation and pass it to [Meta.get]
|
||||
*/
|
||||
public operator fun Meta.get(key: String): Meta? = this[key.toName()]
|
||||
|
||||
/**
|
||||
* Get all items matching given name. The index of the last element, if present is used as a [Regex],
|
||||
* against which indexes of elements are matched.
|
||||
*/
|
||||
public fun Meta.getIndexed(name: Name): Map<String?, Meta> {
|
||||
val root: Meta = when (name.length) {
|
||||
0 -> error("Can't use empty name for 'getIndexed'")
|
||||
1 -> this
|
||||
else -> this[name.cutLast()] ?: return emptyMap()
|
||||
}
|
||||
|
||||
val (body, index) = name.lastOrNull()!!
|
||||
return if (index == null) {
|
||||
root.items
|
||||
.filter { it.key.body == body }
|
||||
.mapKeys { it.key.index }
|
||||
} else {
|
||||
val regex = index.toRegex()
|
||||
root.items
|
||||
.filter { it.key.body == body && (regex.matches(it.key.index ?: "")) }
|
||||
.mapKeys { it.key.index }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A meta node that ensures that all of its descendants has at least the same type.
|
||||
*
|
||||
*/
|
||||
public interface TypedMeta<out M : TypedMeta<M>> : Meta {
|
||||
|
||||
/**
|
||||
* Access self as a recursive type instance
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
public val self: M
|
||||
get() = this as M
|
||||
|
||||
override val value: Value?
|
||||
override val items: Map<NameToken, M>
|
||||
|
||||
override fun toMeta(): Meta = this
|
||||
}
|
||||
|
||||
//public typealias Meta = TypedMeta<*>
|
||||
|
||||
public operator fun <M : TypedMeta<M>> TypedMeta<M>.get(token: NameToken): M? = items[token]
|
||||
|
||||
/**
|
||||
* Perform recursive item search using given [name]. Each [NameToken] is treated as a name in [TypedMeta.items] of a parent node.
|
||||
*
|
||||
* If [name] is empty return current [Meta]
|
||||
*/
|
||||
public tailrec operator fun <M : TypedMeta<M>> TypedMeta<M>.get(name: Name): M? = if (name.isEmpty()) {
|
||||
self
|
||||
} else {
|
||||
get(name.firstOrNull()!!)?.get(name.cutFirst())
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse [Name] from [key] using full name notation and pass it to [TypedMeta.get]
|
||||
*/
|
||||
public operator fun <M : TypedMeta<M>> TypedMeta<M>.get(key: String): M? = this[key.toName()]
|
||||
|
||||
|
||||
/**
|
||||
* Get a sequence of [Name]-[Value] pairs using top-down traversal of the tree
|
||||
*/
|
||||
public fun Meta.valueSequence(): Sequence<Pair<Name, Value>> = sequence {
|
||||
items.forEach { (key, item) ->
|
||||
item.value?.let { itemValue ->
|
||||
yield(key.asName() to itemValue)
|
||||
}
|
||||
yieldAll(item.valueSequence().map { pair -> (key.asName() + pair.first) to pair.second })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a sequence of all [Name]-[TypedMetaItem] pairs for all items including nodes
|
||||
* Get a sequence of all [Name]-[TypedMeta] pairs in a top-down traversal
|
||||
*/
|
||||
public fun Meta.itemSequence(): Sequence<Pair<Name, MetaItem>> = sequence {
|
||||
public fun Meta.nodeSequence(): Sequence<Pair<Name, Meta>> = sequence {
|
||||
items.forEach { (key, item) ->
|
||||
yield(key.asName() to item)
|
||||
if (item is MetaItemNode) {
|
||||
yieldAll(item.node.itemSequence().map { (innerKey, innerItem) ->
|
||||
(key + innerKey) to innerItem
|
||||
})
|
||||
}
|
||||
yieldAll(item.nodeSequence().map { (innerKey, innerItem) ->
|
||||
(key + innerKey) to innerItem
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
public operator fun Meta.iterator(): Iterator<Pair<Name, MetaItem>> = itemSequence().iterator()
|
||||
public operator fun Meta.iterator(): Iterator<Pair<Name, Meta>> = nodeSequence().iterator()
|
||||
|
||||
public fun Meta.isEmpty(): Boolean = this === Meta.EMPTY || this.items.isEmpty()
|
||||
public fun Meta.isEmpty(): Boolean = this === Meta.EMPTY || (value == null && items.isEmpty())
|
||||
|
||||
|
||||
/* Get operations*/
|
||||
|
||||
/**
|
||||
* Get all items matching given name. The index of the last element, if present is used as a [Regex],
|
||||
* against which indexes of elements are matched.
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
public fun <M : TypedMeta<M>> TypedMeta<M>.getIndexed(name: Name): Map<String?, M> =
|
||||
(this as Meta).getIndexed(name) as Map<String?, M>
|
||||
|
||||
public fun <M : TypedMeta<M>> TypedMeta<M>.getIndexed(name: String): Map<String?, Meta> = getIndexed(name.toName())
|
||||
|
||||
|
||||
public val Meta?.string: String? get() = this?.value?.string
|
||||
public val Meta?.boolean: Boolean? get() = this?.value?.boolean
|
||||
public val Meta?.number: Number? get() = this?.value?.numberOrNull
|
||||
public val Meta?.double: Double? get() = number?.toDouble()
|
||||
public val Meta?.float: Float? get() = number?.toFloat()
|
||||
public val Meta?.int: Int? get() = number?.toInt()
|
||||
public val Meta?.long: Long? get() = number?.toLong()
|
||||
public val Meta?.short: Short? get() = number?.toShort()
|
||||
|
||||
public inline fun <reified E : Enum<E>> Meta?.enum(): E? = this?.value?.let {
|
||||
if (it is EnumValue<*>) {
|
||||
it.value as E
|
||||
} else {
|
||||
string?.let { str -> enumValueOf<E>(str) }
|
||||
}
|
||||
}
|
||||
|
||||
public val Meta.stringList: List<String>? get() = value?.list?.map { it.string }
|
||||
|
||||
/**
|
||||
* Create a provider that uses given provider for default values if those are not found in this provider
|
||||
*/
|
||||
public fun Meta.withDefault(default: Meta?): Meta = if (default == null) {
|
||||
this
|
||||
} else {
|
||||
Laminate(this, default)
|
||||
}
|
@ -1,146 +0,0 @@
|
||||
package space.kscience.dataforge.meta
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import space.kscience.dataforge.misc.DFBuilder
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.NameToken
|
||||
import space.kscience.dataforge.names.asName
|
||||
import space.kscience.dataforge.values.EnumValue
|
||||
import space.kscience.dataforge.values.Value
|
||||
import space.kscience.dataforge.values.asValue
|
||||
import kotlin.jvm.JvmName
|
||||
|
||||
/**
|
||||
* DSL builder for meta. Is not intended to store mutable state
|
||||
*/
|
||||
@DFBuilder
|
||||
@Serializable
|
||||
public class MetaBuilder : AbstractMutableMeta<MetaBuilder>() {
|
||||
override val children: MutableMap<NameToken, TypedMetaItem<MetaBuilder>> = LinkedHashMap()
|
||||
|
||||
override fun wrapNode(meta: Meta): MetaBuilder = if (meta is MetaBuilder) meta else meta.toMutableMeta()
|
||||
override fun empty(): MetaBuilder = MetaBuilder()
|
||||
|
||||
public infix fun String.put(item: MetaItem?) {
|
||||
set(this, item)
|
||||
}
|
||||
|
||||
public infix fun String.put(value: Value?) {
|
||||
set(this, value)
|
||||
}
|
||||
|
||||
public infix fun String.put(string: String?) {
|
||||
set(this, string?.asValue())
|
||||
}
|
||||
|
||||
public infix fun String.put(number: Number?) {
|
||||
set(this, number?.asValue())
|
||||
}
|
||||
|
||||
public infix fun String.put(boolean: Boolean?) {
|
||||
set(this, boolean?.asValue())
|
||||
}
|
||||
|
||||
public infix fun String.put(enum: Enum<*>) {
|
||||
set(this, EnumValue(enum))
|
||||
}
|
||||
|
||||
@JvmName("putValues")
|
||||
public infix fun String.put(iterable: Iterable<Value>) {
|
||||
set(this, iterable.asValue())
|
||||
}
|
||||
|
||||
@JvmName("putNumbers")
|
||||
public infix fun String.put(iterable: Iterable<Number>) {
|
||||
set(this, iterable.map { it.asValue() }.asValue())
|
||||
}
|
||||
|
||||
@JvmName("putStrings")
|
||||
public infix fun String.put(iterable: Iterable<String>) {
|
||||
set(this, iterable.map { it.asValue() }.asValue())
|
||||
}
|
||||
|
||||
public infix fun String.put(array: DoubleArray) {
|
||||
set(this, array.asValue())
|
||||
}
|
||||
|
||||
public infix fun String.put(meta: Meta?) {
|
||||
this@MetaBuilder[this] = meta
|
||||
}
|
||||
|
||||
public infix fun String.put(repr: MetaRepr?) {
|
||||
set(this, repr?.toMeta())
|
||||
}
|
||||
|
||||
@JvmName("putMetas")
|
||||
public infix fun String.put(value: Iterable<Meta>) {
|
||||
set(this,value.toList())
|
||||
}
|
||||
|
||||
public inline infix fun String.put(metaBuilder: MetaBuilder.() -> Unit) {
|
||||
this@MetaBuilder[this] = MetaBuilder().apply(metaBuilder)
|
||||
}
|
||||
|
||||
public infix fun Name.put(value: Value?) {
|
||||
set(this, value)
|
||||
}
|
||||
|
||||
public infix fun Name.put(string: String?) {
|
||||
set(this, string?.asValue())
|
||||
}
|
||||
|
||||
public infix fun Name.put(number: Number?) {
|
||||
set(this, number?.asValue())
|
||||
}
|
||||
|
||||
public infix fun Name.put(boolean: Boolean?) {
|
||||
set(this, boolean?.asValue())
|
||||
}
|
||||
|
||||
public infix fun Name.put(enum: Enum<*>) {
|
||||
set(this, EnumValue(enum))
|
||||
}
|
||||
|
||||
@JvmName("putValues")
|
||||
public infix fun Name.put(iterable: Iterable<Value>) {
|
||||
set(this, iterable.asValue())
|
||||
}
|
||||
|
||||
public infix fun Name.put(meta: Meta?) {
|
||||
this@MetaBuilder[this] = meta
|
||||
}
|
||||
|
||||
public infix fun Name.put(repr: MetaRepr?) {
|
||||
set(this, repr?.toMeta())
|
||||
}
|
||||
|
||||
@JvmName("putMetas")
|
||||
public infix fun Name.put(value: Iterable<Meta>) {
|
||||
set(this, value.toList())
|
||||
}
|
||||
|
||||
public infix fun Name.put(metaBuilder: MetaBuilder.() -> Unit) {
|
||||
this@MetaBuilder[this] = MetaBuilder().apply(metaBuilder)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For safety, builder always copies the initial meta even if it is builder itself
|
||||
*/
|
||||
public fun Meta.toMutableMeta(): MetaBuilder {
|
||||
return MetaBuilder().also { builder ->
|
||||
items.mapValues { entry ->
|
||||
val item = entry.value
|
||||
builder[entry.key.asName()] = when (item) {
|
||||
is MetaItemValue -> item.value
|
||||
is MetaItemNode -> MetaItemNode(item.node.toMutableMeta())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a [MetaBuilder] using given transformation
|
||||
*/
|
||||
@Suppress("FunctionName")
|
||||
public inline fun Meta(builder: MetaBuilder.() -> Unit): MetaBuilder = MetaBuilder().apply(builder)
|
@ -0,0 +1,111 @@
|
||||
package space.kscience.dataforge.meta
|
||||
|
||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.asName
|
||||
import space.kscience.dataforge.values.Value
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
|
||||
/* Meta delegates */
|
||||
|
||||
public typealias MetaDelegate = ReadOnlyProperty<Any?, Meta?>
|
||||
|
||||
public fun Meta.item(key: Name? = null): MetaDelegate = ReadOnlyProperty { _, property ->
|
||||
get(key ?: property.name.asName())
|
||||
}
|
||||
|
||||
//TODO add caching for sealed nodes
|
||||
|
||||
|
||||
/**
|
||||
* Apply a converter to this delegate creating a delegate with a custom type
|
||||
*/
|
||||
public fun <R : Any> MetaDelegate.convert(
|
||||
converter: MetaConverter<R>,
|
||||
): ReadOnlyProperty<Any?, R?> = ReadOnlyProperty { thisRef, property ->
|
||||
this@convert.getValue(thisRef, property)?.let(converter::itemToObject)
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
public fun <R : Any> MetaDelegate.convert(
|
||||
converter: MetaConverter<R>,
|
||||
default: () -> R,
|
||||
): ReadOnlyProperty<Any?, R> = ReadOnlyProperty<Any?, R> { thisRef, property ->
|
||||
this@convert.getValue(thisRef, property)?.let(converter::itemToObject) ?: default()
|
||||
}
|
||||
|
||||
/**
|
||||
* A converter with a custom reader transformation
|
||||
*/
|
||||
public fun <R> MetaDelegate.convert(
|
||||
reader: (Meta?) -> R,
|
||||
): ReadOnlyProperty<Any?, R> = ReadOnlyProperty<Any?, R> { thisRef, property ->
|
||||
this@convert.getValue(thisRef, property).let(reader)
|
||||
}
|
||||
|
||||
/* Read-only delegates for [Meta] */
|
||||
|
||||
/**
|
||||
* A property delegate that uses custom key
|
||||
*/
|
||||
public fun Meta.value(key: Name? = null): ReadOnlyProperty<Any?, Value?> =
|
||||
item(key).convert(MetaConverter.value)
|
||||
|
||||
public fun Meta.string(key: Name? = null): ReadOnlyProperty<Any?, String?> =
|
||||
item(key).convert(MetaConverter.string)
|
||||
|
||||
public fun Meta.boolean(key: Name? = null): ReadOnlyProperty<Any?, Boolean?> =
|
||||
item(key).convert(MetaConverter.boolean)
|
||||
|
||||
public fun Meta.number(key: Name? = null): ReadOnlyProperty<Any?, Number?> =
|
||||
item(key).convert(MetaConverter.number)
|
||||
|
||||
public fun Meta.double(key: Name? = null): ReadOnlyProperty<Any?, Double?> =
|
||||
item(key).convert(MetaConverter.double)
|
||||
|
||||
public fun Meta.float(key: Name? = null): ReadOnlyProperty<Any?, Float?> =
|
||||
item(key).convert(MetaConverter.float)
|
||||
|
||||
public fun Meta.int(key: Name? = null): ReadOnlyProperty<Any?, Int?> =
|
||||
item(key).convert(MetaConverter.int)
|
||||
|
||||
public fun Meta.long(key: Name? = null): ReadOnlyProperty<Any?, Long?> =
|
||||
item(key).convert(MetaConverter.long)
|
||||
|
||||
public fun Meta.node(key: Name? = null): ReadOnlyProperty<Any?, Meta?> =
|
||||
item(key).convert(MetaConverter.meta)
|
||||
|
||||
public fun Meta.string(default: String, key: Name? = null): ReadOnlyProperty<Any?, String> =
|
||||
item(key).convert(MetaConverter.string) { default }
|
||||
|
||||
public fun Meta.boolean(default: Boolean, key: Name? = null): ReadOnlyProperty<Any?, Boolean> =
|
||||
item(key).convert(MetaConverter.boolean) { default }
|
||||
|
||||
public fun Meta.number(default: Number, key: Name? = null): ReadOnlyProperty<Any?, Number> =
|
||||
item(key).convert(MetaConverter.number) { default }
|
||||
|
||||
public fun Meta.double(default: Double, key: Name? = null): ReadOnlyProperty<Any?, Double> =
|
||||
item(key).convert(MetaConverter.double) { default }
|
||||
|
||||
public fun Meta.float(default: Float, key: Name? = null): ReadOnlyProperty<Any?, Float> =
|
||||
item(key).convert(MetaConverter.float) { default }
|
||||
|
||||
public fun Meta.int(default: Int, key: Name? = null): ReadOnlyProperty<Any?, Int> =
|
||||
item(key).convert(MetaConverter.int) { default }
|
||||
|
||||
public fun Meta.long(default: Long, key: Name? = null): ReadOnlyProperty<Any?, Long> =
|
||||
item(key).convert(MetaConverter.long) { default }
|
||||
|
||||
public inline fun <reified E : Enum<E>> Meta.enum(default: E, key: Name? = null): ReadOnlyProperty<Any?, E> =
|
||||
item(key).convert(MetaConverter.enum()) { default }
|
||||
|
||||
public fun Meta.string(key: Name? = null, default: () -> String): ReadOnlyProperty<Any?, String> =
|
||||
item(key).convert(MetaConverter.string, default)
|
||||
|
||||
public fun Meta.boolean(key: Name? = null, default: () -> Boolean): ReadOnlyProperty<Any?, Boolean> =
|
||||
item(key).convert(MetaConverter.boolean, default)
|
||||
|
||||
public fun Meta.number(key: Name? = null, default: () -> Number): ReadOnlyProperty<Any?, Number> =
|
||||
item(key).convert(MetaConverter.number, default)
|
@ -1,96 +0,0 @@
|
||||
package space.kscience.dataforge.meta
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.isEmpty
|
||||
import space.kscience.dataforge.values.*
|
||||
|
||||
/**
|
||||
* A member of the meta tree. Could be represented as one of following:
|
||||
* * a [MetaItemValue] (leaf)
|
||||
* * a [MetaItemNode] (node)
|
||||
*/
|
||||
@Serializable(MetaItemSerializer::class)
|
||||
public sealed class TypedMetaItem<out M : Meta> : ItemProvider {
|
||||
|
||||
abstract override fun equals(other: Any?): Boolean
|
||||
|
||||
abstract override fun hashCode(): Int
|
||||
|
||||
public companion object {
|
||||
public fun of(arg: Any?): MetaItem {
|
||||
return when (arg) {
|
||||
null -> Null.asMetaItem()
|
||||
is MetaItem -> arg
|
||||
is Meta -> arg.asMetaItem()
|
||||
is ItemProvider -> arg.rootItem ?: Null.asMetaItem()
|
||||
else -> Value.of(arg).asMetaItem()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public typealias MetaItem = TypedMetaItem<*>
|
||||
|
||||
@Serializable(MetaItemSerializer::class)
|
||||
public class MetaItemValue(public val value: Value) : TypedMetaItem<Nothing>() {
|
||||
override fun getItem(name: Name): MetaItem? = if (name.isEmpty()) this else null
|
||||
|
||||
override fun toString(): String = value.toString()
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return this.value == (other as? MetaItemValue)?.value
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return value.hashCode()
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable(MetaItemSerializer::class)
|
||||
public class MetaItemNode<M : Meta>(public val node: M) : TypedMetaItem<M>() {
|
||||
override fun getItem(name: Name): MetaItem? = if (name.isEmpty()) this else node.getItem(name)
|
||||
|
||||
//Fixing serializer for node could cause class cast problems, but it should not since Meta descendants are not serializable
|
||||
override fun toString(): String = node.toString()
|
||||
|
||||
override fun equals(other: Any?): Boolean = Meta.equals(node, (other as? MetaItemNode<*>)?.node)
|
||||
|
||||
override fun hashCode(): Int = node.hashCode()
|
||||
}
|
||||
|
||||
public fun Value.asMetaItem(): MetaItemValue = MetaItemValue(this)
|
||||
public fun <M : Meta> M.asMetaItem(): MetaItemNode<M> = MetaItemNode(this)
|
||||
|
||||
|
||||
/**
|
||||
* Unsafe methods to access values and nodes directly from [TypedMetaItem]
|
||||
*/
|
||||
public val MetaItem?.value: Value?
|
||||
get() = (this as? MetaItemValue)?.value
|
||||
?: (this?.node?.get(Meta.VALUE_KEY) as? MetaItemValue)?.value
|
||||
|
||||
public val MetaItem?.string: String? get() = value?.string
|
||||
public val MetaItem?.boolean: Boolean? get() = value?.boolean
|
||||
public val MetaItem?.number: Number? get() = value?.numberOrNull
|
||||
public val MetaItem?.double: Double? get() = number?.toDouble()
|
||||
public val MetaItem?.float: Float? get() = number?.toFloat()
|
||||
public val MetaItem?.int: Int? get() = number?.toInt()
|
||||
public val MetaItem?.long: Long? get() = number?.toLong()
|
||||
public val MetaItem?.short: Short? get() = number?.toShort()
|
||||
|
||||
public inline fun <reified E : Enum<E>> MetaItem?.enum(): E? =
|
||||
if (this is MetaItemValue && this.value is EnumValue<*>) {
|
||||
this.value.value as E
|
||||
} else {
|
||||
string?.let { enumValueOf<E>(it) }
|
||||
}
|
||||
|
||||
public val MetaItem.stringList: List<String>? get() = value?.list?.map { it.string }
|
||||
|
||||
public val <M : Meta> TypedMetaItem<M>?.node: M?
|
||||
get() = when (this) {
|
||||
null -> null
|
||||
is MetaItemValue -> null//error("Trying to interpret value meta item as node item")
|
||||
is MetaItemNode -> node
|
||||
}
|
@ -1,80 +1,46 @@
|
||||
package space.kscience.dataforge.meta
|
||||
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.InternalSerializationApi
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.builtins.MapSerializer
|
||||
import kotlinx.serialization.descriptors.*
|
||||
import kotlinx.serialization.encoding.*
|
||||
import kotlinx.serialization.json.JsonDecoder
|
||||
import kotlinx.serialization.json.JsonEncoder
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import space.kscience.dataforge.names.NameToken
|
||||
import space.kscience.dataforge.names.NameTokenSerializer
|
||||
import space.kscience.dataforge.values.ValueSerializer
|
||||
|
||||
public object MetaItemSerializer : KSerializer<MetaItem> {
|
||||
|
||||
@OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class)
|
||||
override val descriptor: SerialDescriptor = buildSerialDescriptor("MetaItem", PolymorphicKind.SEALED) {
|
||||
element<Boolean>("isNode")
|
||||
element("value", buildSerialDescriptor("MetaItem.value", SerialKind.CONTEXTUAL))
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): MetaItem {
|
||||
decoder.decodeStructure(descriptor) {
|
||||
//Force strict serialization order
|
||||
require(decodeElementIndex(descriptor) == 0) { "Node flag must be first item serialized" }
|
||||
val isNode = decodeBooleanElement(descriptor, 0)
|
||||
require(decodeElementIndex(descriptor) == 1) { "Missing MetaItem content" }
|
||||
val item = if (isNode) {
|
||||
decodeSerializableElement(descriptor,1, MetaSerializer).asMetaItem()
|
||||
} else {
|
||||
decodeSerializableElement(descriptor,1, ValueSerializer).asMetaItem()
|
||||
}
|
||||
require(decodeElementIndex(descriptor) == CompositeDecoder.DECODE_DONE){"Serialized MetaItem contains additional fields"}
|
||||
return item
|
||||
}
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, value: MetaItem) {
|
||||
encoder.encodeStructure(descriptor) {
|
||||
encodeBooleanElement(descriptor, 0, value is MetaItemNode)
|
||||
when (value) {
|
||||
is MetaItemValue -> encodeSerializableElement(descriptor, 1, ValueSerializer, value.value)
|
||||
is MetaItemNode -> encodeSerializableElement(descriptor, 1, MetaSerializer, value.node)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialized for meta
|
||||
*/
|
||||
public object MetaSerializer : KSerializer<Meta> {
|
||||
|
||||
private val mapSerializer: KSerializer<Map<NameToken, TypedMetaItem<Meta>>> = MapSerializer(
|
||||
private val itemsSerializer: KSerializer<Map<NameToken, Meta>> = MapSerializer(
|
||||
NameTokenSerializer,
|
||||
MetaItemSerializer//MetaItem.serializer(MetaSerializer)
|
||||
MetaSerializer
|
||||
)
|
||||
|
||||
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Meta")
|
||||
override val descriptor: SerialDescriptor = JsonElement.serializer().descriptor
|
||||
|
||||
override fun deserialize(decoder: Decoder): Meta {
|
||||
return if (decoder is JsonDecoder) {
|
||||
JsonObject.serializer().deserialize(decoder).toMeta()
|
||||
} else {
|
||||
object : MetaBase() {
|
||||
override val items: Map<NameToken, MetaItem> = mapSerializer.deserialize(decoder)
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun deserialize(decoder: Decoder): Meta = JsonElement.serializer().deserialize(decoder).toMeta()
|
||||
|
||||
override fun serialize(encoder: Encoder, value: Meta) {
|
||||
if (encoder is JsonEncoder) {
|
||||
JsonObject.serializer().serialize(encoder, value.toJson())
|
||||
} else {
|
||||
mapSerializer.serialize(encoder, value.items)
|
||||
}
|
||||
JsonElement.serializer().serialize(encoder, value.toJson())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A serializer for [MutableMeta]
|
||||
*/
|
||||
public object MutableMetaSerializer : KSerializer<MutableMeta> {
|
||||
override val descriptor: SerialDescriptor = MetaSerializer.descriptor
|
||||
|
||||
|
||||
override fun deserialize(decoder: Decoder): MutableMeta {
|
||||
val meta = decoder.decodeSerializableValue(MetaSerializer)
|
||||
return (meta as? MutableMeta) ?: meta.toMutableMeta()
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, value: MutableMeta) {
|
||||
encoder.encodeSerializableValue(MetaSerializer, value)
|
||||
}
|
||||
}
|
@ -1,153 +0,0 @@
|
||||
package space.kscience.dataforge.meta
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import space.kscience.dataforge.names.*
|
||||
import space.kscience.dataforge.values.Value
|
||||
|
||||
@Serializable(MutableItemProviderSerializer::class)
|
||||
public interface MutableItemProvider : ItemProvider {
|
||||
public fun setItem(name: Name, item: MetaItem?)
|
||||
}
|
||||
|
||||
/**
|
||||
* A serializer form [MutableItemProvider]
|
||||
*/
|
||||
public class MutableItemProviderSerializer : KSerializer<MutableItemProvider> {
|
||||
override val descriptor: SerialDescriptor = MetaSerializer.descriptor
|
||||
|
||||
|
||||
override fun deserialize(decoder: Decoder): MutableItemProvider {
|
||||
val meta = decoder.decodeSerializableValue(MetaSerializer)
|
||||
return (meta as? MetaBuilder) ?: meta.toMutableMeta()
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, value: MutableItemProvider) {
|
||||
encoder.encodeSerializableValue(MetaSerializer, value.rootItem?.node ?: Meta.EMPTY)
|
||||
}
|
||||
}
|
||||
|
||||
public operator fun MutableItemProvider.set(name: Name, item: MetaItem?): Unit = setItem(name, item)
|
||||
|
||||
public operator fun MutableItemProvider.set(name: Name, value: Value?): Unit = set(name, value?.asMetaItem())
|
||||
|
||||
public operator fun MutableItemProvider.set(name: Name, meta: Meta?): Unit = set(name, meta?.asMetaItem())
|
||||
|
||||
public operator fun MutableItemProvider.set(key: String, item: MetaItem?): Unit = set(key.toName(), item)
|
||||
|
||||
public operator fun MutableItemProvider.set(key: String, meta: Meta?): Unit = set(key, meta?.asMetaItem())
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
public inline fun MutableItemProvider.remove(name: Name): Unit = setItem(name, null)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
public inline fun MutableItemProvider.remove(name: String): Unit = remove(name.toName())
|
||||
|
||||
/**
|
||||
* Universal unsafe set method
|
||||
*/
|
||||
public operator fun MutableItemProvider.set(name: Name, value: Any?) {
|
||||
when (value) {
|
||||
null -> remove(name)
|
||||
else -> set(name, MetaItem.of(value))
|
||||
}
|
||||
}
|
||||
|
||||
public operator fun MutableItemProvider.set(name: NameToken, value: Any?): Unit =
|
||||
set(name.asName(), value)
|
||||
|
||||
public operator fun MutableItemProvider.set(key: String, value: Any?): Unit =
|
||||
set(key.toName(), value)
|
||||
|
||||
public operator fun MutableItemProvider.set(key: String, index: String, value: Any?): Unit =
|
||||
set(key.toName().withIndex(index), value)
|
||||
|
||||
|
||||
/* Same name siblings generation */
|
||||
|
||||
public fun MutableItemProvider.setIndexedItems(
|
||||
name: Name,
|
||||
items: Iterable<MetaItem>,
|
||||
indexFactory: (MetaItem, index: Int) -> String = { _, index -> index.toString() },
|
||||
) {
|
||||
val tokens = name.tokens.toMutableList()
|
||||
val last = tokens.last()
|
||||
items.forEachIndexed { index, meta ->
|
||||
val indexedToken = NameToken(last.body, (last.index ?: "") + indexFactory(meta, index))
|
||||
tokens[tokens.lastIndex] = indexedToken
|
||||
set(Name(tokens), meta)
|
||||
}
|
||||
}
|
||||
|
||||
public fun MutableItemProvider.setIndexed(
|
||||
name: Name,
|
||||
metas: Iterable<Meta>,
|
||||
indexFactory: (Meta, index: Int) -> String = { _, index -> index.toString() },
|
||||
) {
|
||||
setIndexedItems(name, metas.map { MetaItemNode(it) }) { item, index -> indexFactory(item.node!!, index) }
|
||||
}
|
||||
|
||||
public operator fun MutableItemProvider.set(name: Name, metas: Iterable<Meta>): Unit = setIndexed(name, metas)
|
||||
public operator fun MutableItemProvider.set(name: String, metas: Iterable<Meta>): Unit =
|
||||
setIndexed(name.toName(), metas)
|
||||
|
||||
/**
|
||||
* Get a [MutableItemProvider] referencing a child node
|
||||
*/
|
||||
public fun MutableItemProvider.getChild(childName: Name): MutableItemProvider {
|
||||
fun createProvider() = object : MutableItemProvider {
|
||||
override fun setItem(name: Name, item: MetaItem?) {
|
||||
this@getChild.setItem(childName + name, item)
|
||||
}
|
||||
|
||||
override fun getItem(name: Name): MetaItem? = this@getChild.getItem(childName + name)
|
||||
}
|
||||
|
||||
return when {
|
||||
childName.isEmpty() -> this
|
||||
this is MutableMeta<*> -> {
|
||||
get(childName).node ?: createProvider()
|
||||
}
|
||||
else -> {
|
||||
createProvider()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun MutableItemProvider.getChild(childName: String): MutableItemProvider = getChild(childName.toName())
|
||||
|
||||
|
||||
/**
|
||||
* Update existing mutable node with another node. The rules are following:
|
||||
* * value replaces anything
|
||||
* * node updates node and replaces anything but node
|
||||
* * node list updates node list if number of nodes in the list is the same and replaces anything otherwise
|
||||
*/
|
||||
public fun MutableItemProvider.update(meta: Meta) {
|
||||
meta.valueSequence().forEach { (name, value) -> set(name, value) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit a provider child at given name location
|
||||
*/
|
||||
public fun MutableItemProvider.editChild(name: Name, builder: MutableItemProvider.() -> Unit): MutableItemProvider =
|
||||
getChild(name).apply(builder)
|
||||
|
||||
/**
|
||||
* Create a mutable item provider that uses given provider for default values if those are not found in this provider.
|
||||
* Changes are propagated only to this provider.
|
||||
*/
|
||||
public fun MutableItemProvider.withDefault(default: ItemProvider?): MutableItemProvider =
|
||||
if (default == null || (default is Meta && default.isEmpty())) {
|
||||
//Optimize for use with empty default
|
||||
this
|
||||
} else object : MutableItemProvider {
|
||||
override fun setItem(name: Name, item: MetaItem?) {
|
||||
this@withDefault.setItem(name, item)
|
||||
}
|
||||
|
||||
override fun getItem(name: Name): MetaItem? = this@withDefault.getItem(name) ?: default.getItem(name)
|
||||
}
|
@ -1,74 +1,352 @@
|
||||
package space.kscience.dataforge.meta
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import space.kscience.dataforge.misc.DFBuilder
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.names.*
|
||||
import space.kscience.dataforge.values.EnumValue
|
||||
import space.kscience.dataforge.values.Value
|
||||
import space.kscience.dataforge.values.asValue
|
||||
import kotlin.jvm.Synchronized
|
||||
|
||||
public interface MutableMeta<out M : MutableMeta<M>> : TypedMeta<M>, MutableItemProvider {
|
||||
override val items: Map<NameToken, TypedMetaItem<M>>
|
||||
}
|
||||
|
||||
/**
|
||||
* A mutable meta node with attachable change listener.
|
||||
*
|
||||
* Changes in Meta are not thread safe.
|
||||
* Mutable variant of [Meta]
|
||||
* TODO documentation
|
||||
*/
|
||||
public abstract class AbstractMutableMeta<M : MutableMeta<M>> : AbstractTypedMeta<M>(), MutableMeta<M> {
|
||||
@Serializable(MutableMetaSerializer::class)
|
||||
public interface MutableMeta : Meta {
|
||||
|
||||
protected abstract val children: MutableMap<NameToken, TypedMetaItem<M>>
|
||||
/**
|
||||
* Get or set value of this node
|
||||
*/
|
||||
override var value: Value?
|
||||
|
||||
override val items: Map<NameToken, TypedMetaItem<M>> get() = children
|
||||
/**
|
||||
* Set or replace node at given [name]
|
||||
*/
|
||||
public operator fun set(name: Name, meta: Meta)
|
||||
|
||||
//protected abstract fun itemChanged(name: Name, oldItem: MetaItem<*>?, newItem: MetaItem<*>?)
|
||||
/**
|
||||
* Remove a note at a given [name] if it is present
|
||||
*/
|
||||
public fun removeNode(name: Name)
|
||||
|
||||
protected open fun replaceItem(key: NameToken, oldItem: TypedMetaItem<M>?, newItem: TypedMetaItem<M>?) {
|
||||
if (newItem == null) {
|
||||
children.remove(key)
|
||||
} else {
|
||||
children[key] = newItem
|
||||
/**
|
||||
* Get existing node or create a new one
|
||||
*/
|
||||
public fun getOrCreate(name: Name): MutableMeta
|
||||
|
||||
//TODO to be moved to extensions with multi-receivers
|
||||
|
||||
public infix fun Name.put(value: Value?) {
|
||||
set(this, value)
|
||||
}
|
||||
|
||||
public infix fun Name.put(string: String) {
|
||||
set(this, string.asValue())
|
||||
}
|
||||
|
||||
public infix fun Name.put(number: Number) {
|
||||
set(this, number.asValue())
|
||||
}
|
||||
|
||||
public infix fun Name.put(boolean: Boolean) {
|
||||
set(this, boolean.asValue())
|
||||
}
|
||||
|
||||
public infix fun Name.put(enum: Enum<*>) {
|
||||
set(this, EnumValue(enum))
|
||||
}
|
||||
|
||||
public infix fun Name.put(iterable: Iterable<Meta>) {
|
||||
setIndexed(this, iterable)
|
||||
}
|
||||
|
||||
public infix fun Name.put(meta: Meta) {
|
||||
set(this, meta)
|
||||
}
|
||||
|
||||
public infix fun Name.put(repr: MetaRepr) {
|
||||
put(repr.toMeta())
|
||||
}
|
||||
|
||||
public infix fun Name.put(mutableMeta: MutableMeta.() -> Unit) {
|
||||
set(this, Meta(mutableMeta))
|
||||
}
|
||||
|
||||
public infix fun String.put(meta: Meta) {
|
||||
toName() put meta
|
||||
}
|
||||
|
||||
public infix fun String.put(value: Value?) {
|
||||
set(toName(), value)
|
||||
}
|
||||
|
||||
public infix fun String.put(string: String) {
|
||||
set(toName(), string.asValue())
|
||||
}
|
||||
|
||||
public infix fun String.put(number: Number) {
|
||||
set(toName(), number.asValue())
|
||||
}
|
||||
|
||||
public infix fun String.put(boolean: Boolean) {
|
||||
set(toName(), boolean.asValue())
|
||||
}
|
||||
|
||||
public infix fun String.put(enum: Enum<*>) {
|
||||
set(toName(), EnumValue(enum))
|
||||
}
|
||||
|
||||
public infix fun String.put(array: DoubleArray) {
|
||||
set(toName(), array.asValue())
|
||||
}
|
||||
|
||||
public infix fun String.put(repr: MetaRepr) {
|
||||
toName() put repr.toMeta()
|
||||
}
|
||||
|
||||
public infix fun String.put(builder: MutableMeta.() -> Unit) {
|
||||
set(toName(), MutableMeta(builder))
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable(MutableMetaSerializer::class)
|
||||
public interface MutableTypedMeta<M : MutableTypedMeta<M>> : TypedMeta<M>, MutableMeta {
|
||||
/**
|
||||
* Zero-copy attach or replace existing node. Node is used with any additional state, listeners, etc.
|
||||
* In some cases it is possible to have the same node as a child to several others
|
||||
*/
|
||||
public fun attach(name: Name, node: M)
|
||||
|
||||
override fun getOrCreate(name: Name): M
|
||||
}
|
||||
|
||||
public operator fun MutableMeta.set(key: String, item: Meta?): Unit =
|
||||
set(key.toName(), item)
|
||||
|
||||
|
||||
public fun MutableMeta.remove(name: Name): Unit = set(name, null)
|
||||
|
||||
public fun MutableMeta.remove(name: String): Unit = remove(name.toName())
|
||||
|
||||
/**
|
||||
* Universal unsafe set method
|
||||
*/
|
||||
public operator fun MutableMeta.set(name: Name, value: Any?) {
|
||||
when (value) {
|
||||
null -> remove(name)
|
||||
else -> set(name, Value.of(value))
|
||||
}
|
||||
}
|
||||
|
||||
public operator fun MutableMeta.set(name: NameToken, value: Any?): Unit =
|
||||
set(name.asName(), value)
|
||||
|
||||
public operator fun MutableMeta.set(key: String, value: Any?): Unit =
|
||||
set(key.toName(), value)
|
||||
|
||||
public operator fun MutableMeta.set(key: String, index: String, value: Any?): Unit =
|
||||
set(key.toName().withIndex(index), value)
|
||||
|
||||
|
||||
/* Same name siblings generation */
|
||||
|
||||
public fun MutableMeta.setIndexedItems(
|
||||
name: Name,
|
||||
items: Iterable<Meta>,
|
||||
indexFactory: (Meta, index: Int) -> String = { _, index -> index.toString() },
|
||||
) {
|
||||
val tokens = name.tokens.toMutableList()
|
||||
val last = tokens.last()
|
||||
items.forEachIndexed { index, meta ->
|
||||
val indexedToken = NameToken(last.body, (last.index ?: "") + indexFactory(meta, index))
|
||||
tokens[tokens.lastIndex] = indexedToken
|
||||
set(Name(tokens), meta)
|
||||
}
|
||||
}
|
||||
|
||||
public fun MutableMeta.setIndexed(
|
||||
name: Name,
|
||||
metas: Iterable<Meta>,
|
||||
indexFactory: (Meta, index: Int) -> String = { _, index -> index.toString() },
|
||||
) {
|
||||
setIndexedItems(name, metas) { item, index -> indexFactory(item, index) }
|
||||
}
|
||||
|
||||
public operator fun <M : MutableTypedMeta<M>> MutableTypedMeta<M>.set(name: Name, metas: Iterable<Meta>): Unit =
|
||||
setIndexed(name, metas)
|
||||
|
||||
public operator fun <M : MutableTypedMeta<M>> MutableTypedMeta<M>.set(name: String, metas: Iterable<Meta>): Unit =
|
||||
setIndexed(name.toName(), metas)
|
||||
|
||||
|
||||
/**
|
||||
* Update existing mutable node with another node. The rules are following:
|
||||
* * value replaces anything
|
||||
* * node updates node and replaces anything but node
|
||||
* * node list updates node list if number of nodes in the list is the same and replaces anything otherwise
|
||||
*/
|
||||
public fun <M : MutableTypedMeta<M>> MutableTypedMeta<M>.update(meta: Meta) {
|
||||
meta.valueSequence().forEach { (name, value) ->
|
||||
set(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
///**
|
||||
// * Get child with given name or create a new one
|
||||
// */
|
||||
//public fun <M : MutableTypedMeta<M>> MutableTypedMeta<M>.getOrCreate(name: Name): M =
|
||||
// get(name) ?: empty().also { attach(name, it) }
|
||||
|
||||
/**
|
||||
* Edit node at [name]
|
||||
*/
|
||||
public fun <M : MutableTypedMeta<M>> MutableTypedMeta<M>.edit(name: Name, builder: M.() -> Unit): M =
|
||||
getOrCreate(name).apply(builder)
|
||||
|
||||
/**
|
||||
* Set a value at a given [name]. If node does not exist, create it.
|
||||
*/
|
||||
public operator fun <M : MutableTypedMeta<M>> MutableTypedMeta<M>.set(name: Name, value: Value?) {
|
||||
edit(name) {
|
||||
this.value = value
|
||||
}
|
||||
}
|
||||
|
||||
///**
|
||||
// * Create a mutable item provider that uses given provider for default values if those are not found in this provider.
|
||||
// * Changes are propagated only to this provider.
|
||||
// */
|
||||
//public fun <M : MutableTypedMeta<M>> M.withDefault(default: Meta?): MutableTypedMeta<*> =
|
||||
// if (default == null || default.isEmpty()) {
|
||||
// //Optimize for use with empty default
|
||||
// this
|
||||
// } else object : MutableTypedMeta<M> {
|
||||
// override fun set(name: Name, item: MetaItem?) {
|
||||
// this@withDefault.set(name, item)
|
||||
// }
|
||||
//
|
||||
// override fun getItem(name: Name): MetaItem? = this@withDefault.getItem(name) ?: default.getItem(name)
|
||||
// }
|
||||
|
||||
/**
|
||||
* A general implementation of mutable [Meta] which implements both [MutableTypedMeta] and [ObservableMeta].
|
||||
* The implementation uses blocking synchronization on mutation on JVM
|
||||
*/
|
||||
@DFBuilder
|
||||
private class MutableMetaImpl(
|
||||
override var value: Value?,
|
||||
children: Map<NameToken, Meta> = emptyMap()
|
||||
) : ObservableMutableMeta {
|
||||
|
||||
private val children: LinkedHashMap<NameToken, ObservableMutableMeta> =
|
||||
LinkedHashMap(children.mapValues { (key, meta) ->
|
||||
MutableMetaImpl(meta.value, meta.items).apply { adoptBy(this, key) }
|
||||
})
|
||||
|
||||
override val items: Map<NameToken, ObservableMutableMeta> get() = children
|
||||
|
||||
private val listeners = HashSet<MetaListener>()
|
||||
|
||||
private fun changed(name: Name) {
|
||||
listeners.forEach { it.callback(this, name) }
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit) {
|
||||
listeners.add(MetaListener(owner, callback))
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun removeListener(owner: Any?) {
|
||||
listeners.removeAll { it.owner === owner }
|
||||
}
|
||||
|
||||
|
||||
private fun adoptBy(parent: MutableMetaImpl, key: NameToken) {
|
||||
onChange(parent) { name ->
|
||||
parent.changed(key + name)
|
||||
}
|
||||
//itemChanged(key.asName(), oldItem, newItem)
|
||||
}
|
||||
|
||||
private fun wrapItem(item: MetaItem?): TypedMetaItem<M>? = when (item) {
|
||||
null -> null
|
||||
is MetaItemValue -> item
|
||||
is MetaItemNode -> MetaItemNode(wrapNode(item.node))
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform given meta to node type of this meta tree
|
||||
*/
|
||||
protected abstract fun wrapNode(meta: Meta): M
|
||||
|
||||
/**
|
||||
* Create empty node
|
||||
*/
|
||||
internal abstract fun empty(): M
|
||||
|
||||
override fun setItem(name: Name, item: MetaItem?) {
|
||||
override fun attach(name: Name, node: ObservableMutableMeta) {
|
||||
when (name.length) {
|
||||
0 -> error("Can't set a meta item for empty name")
|
||||
0 -> error("Can't set a meta with empty name")
|
||||
1 -> {
|
||||
val token = name.firstOrNull()!!
|
||||
val oldItem: TypedMetaItem<M>? = getItem(name)
|
||||
replaceItem(token, oldItem, wrapItem(item))
|
||||
}
|
||||
else -> {
|
||||
val token = name.firstOrNull()!!
|
||||
//get existing or create new node. Query is ignored for new node
|
||||
if (items[token] == null) {
|
||||
replaceItem(token, null, MetaItemNode(empty()))
|
||||
}
|
||||
items[token]?.node!!.set(name.cutFirst(), item)
|
||||
val key = name.firstOrNull()!!
|
||||
children[key] = node
|
||||
adoptBy(this, key)
|
||||
changed(name)
|
||||
}
|
||||
else -> get(name.cutLast())?.attach(name.lastOrNull()!!.asName(), node)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getOrCreate(name: Name): ObservableMutableMeta =
|
||||
get(name) ?: MutableMetaImpl(null).also { attach(name, it) }
|
||||
|
||||
override fun removeNode(name: Name) {
|
||||
when (name.length) {
|
||||
0 -> error("Can't remove self")
|
||||
1 -> if (children.remove(name.firstOrNull()!!) != null) changed(name)
|
||||
else -> get(name.cutLast())?.removeNode(name.lastOrNull()!!.asName())
|
||||
}
|
||||
}
|
||||
|
||||
private fun replaceItem(
|
||||
key: NameToken,
|
||||
oldItem: ObservableMutableMeta?,
|
||||
newItem: MutableMetaImpl?
|
||||
) {
|
||||
if (oldItem != newItem) {
|
||||
if (newItem == null) {
|
||||
children.remove(key)
|
||||
} else {
|
||||
newItem.adoptBy(this, key)
|
||||
children[key] = newItem
|
||||
}
|
||||
changed(key.asName())
|
||||
}
|
||||
}
|
||||
|
||||
private fun wrapItem(meta: Meta): MutableMetaImpl =
|
||||
MutableMetaImpl(meta.value, meta.items.mapValuesTo(LinkedHashMap()) { wrapItem(it.value) })
|
||||
|
||||
|
||||
override fun set(name: Name, meta: Meta) {
|
||||
val oldItem: ObservableMutableMeta? = get(name)
|
||||
if (oldItem != meta) {
|
||||
when (name.length) {
|
||||
0 -> error("Can't set a meta with empty name")
|
||||
1 -> {
|
||||
val token = name.firstOrNull()!!
|
||||
replaceItem(token, oldItem, wrapItem(meta))
|
||||
}
|
||||
else -> {
|
||||
val token = name.firstOrNull()!!
|
||||
//get existing or create new node. Index is ignored for new node
|
||||
if (items[token] == null) {
|
||||
replaceItem(token, null, MutableMetaImpl(null))
|
||||
}
|
||||
items[token]?.set(name.cutFirst(), meta)
|
||||
}
|
||||
}
|
||||
changed(name)
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String = Meta.toString(this)
|
||||
override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta)
|
||||
override fun hashCode(): Int = Meta.hashCode(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the node with a same-name-sibling, automatically generating numerical index
|
||||
*/
|
||||
public fun MutableItemProvider.append(name: Name, value: Any?) {
|
||||
@DFExperimental
|
||||
public fun MutableMeta.append(name: Name, value: Any?) {
|
||||
require(!name.isEmpty()) { "Name could not be empty for append operation" }
|
||||
val newIndex = name.lastOrNull()!!.index
|
||||
if (newIndex != null) {
|
||||
@ -79,17 +357,40 @@ public fun MutableItemProvider.append(name: Name, value: Any?) {
|
||||
}
|
||||
}
|
||||
|
||||
public fun MutableItemProvider.append(name: String, value: Any?): Unit = append(name.toName(), value)
|
||||
@DFExperimental
|
||||
public fun MutableMeta.append(name: String, value: Any?): Unit = append(name.toName(), value)
|
||||
|
||||
///**
|
||||
// * Apply existing node with given [builder] or create a new element with it.
|
||||
// */
|
||||
//@DFExperimental
|
||||
//public fun MutableMeta.edit(name: Name, builder: MutableMeta.() -> Unit) {
|
||||
// val item = when (val existingItem = get(name)) {
|
||||
// null -> MutableMeta().also { set(name, it) }
|
||||
// is MetaItemNode<MutableMeta> -> existingItem.node
|
||||
// else -> error("Can't edit value meta item")
|
||||
// }
|
||||
// item.apply(builder)
|
||||
//}
|
||||
|
||||
/**
|
||||
* Apply existing node with given [builder] or create a new element with it.
|
||||
* Create a mutable copy of this meta. The copy is created even if the Meta is already mutable
|
||||
*/
|
||||
@DFExperimental
|
||||
public fun <M : AbstractMutableMeta<M>> M.edit(name: Name, builder: M.() -> Unit) {
|
||||
val item = when (val existingItem = get(name)) {
|
||||
null -> empty().also { set(name, it) }
|
||||
is MetaItemNode<M> -> existingItem.node
|
||||
else -> error("Can't edit value meta item")
|
||||
}
|
||||
item.apply(builder)
|
||||
}
|
||||
public fun Meta.toMutableMeta(): ObservableMutableMeta = MutableMetaImpl(value, items)
|
||||
|
||||
public fun Meta.asMutableMeta(): MutableMeta = (this as? MutableMeta) ?: toMutableMeta()
|
||||
|
||||
/**
|
||||
* Build a [MutableMeta] using given transformation
|
||||
*/
|
||||
@Suppress("FunctionName")
|
||||
public fun MutableMeta(builder: MutableMeta.() -> Unit = {}): ObservableMutableMeta =
|
||||
MutableMetaImpl(null).apply(builder)
|
||||
|
||||
|
||||
/**
|
||||
* Create a copy of this [Meta], optionally applying the given [block].
|
||||
* The listeners of the original Config are not retained.
|
||||
*/
|
||||
public inline fun Meta.copy(block: MutableMeta.() -> Unit = {}): Meta =
|
||||
toMutableMeta().apply(block)
|
@ -9,14 +9,14 @@ import kotlin.reflect.KProperty
|
||||
|
||||
/* Read-write delegates */
|
||||
|
||||
public typealias MutableItemDelegate = ReadWriteProperty<Any?, MetaItem?>
|
||||
public typealias MutableMetaDelegate = ReadWriteProperty<Any?, Meta?>
|
||||
|
||||
public fun MutableItemProvider.item(key: Name? = null): MutableItemDelegate = object : MutableItemDelegate {
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): MetaItem? {
|
||||
public fun MutableMeta.item(key: Name? = null): MutableMetaDelegate = object : MutableMetaDelegate {
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): Meta? {
|
||||
return get(key ?: property.name.asName())
|
||||
}
|
||||
|
||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: MetaItem?) {
|
||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Meta?) {
|
||||
val name = key ?: property.name.asName()
|
||||
set(name, value)
|
||||
}
|
||||
@ -27,7 +27,7 @@ public fun MutableItemProvider.item(key: Name? = null): MutableItemDelegate = ob
|
||||
/**
|
||||
* A type converter for a mutable [TypedMetaItem] delegate
|
||||
*/
|
||||
public fun <R : Any> MutableItemDelegate.convert(
|
||||
public fun <R : Any> MutableMetaDelegate.convert(
|
||||
converter: MetaConverter<R>,
|
||||
): ReadWriteProperty<Any?, R?> = object : ReadWriteProperty<Any?, R?> {
|
||||
|
||||
@ -40,7 +40,7 @@ public fun <R : Any> MutableItemDelegate.convert(
|
||||
}
|
||||
}
|
||||
|
||||
public fun <R : Any> MutableItemDelegate.convert(
|
||||
public fun <R : Any> MutableMetaDelegate.convert(
|
||||
converter: MetaConverter<R>,
|
||||
default: () -> R,
|
||||
): ReadWriteProperty<Any?, R> = object : ReadWriteProperty<Any?, R> {
|
||||
@ -54,9 +54,9 @@ public fun <R : Any> MutableItemDelegate.convert(
|
||||
}
|
||||
}
|
||||
|
||||
public fun <R> MutableItemDelegate.convert(
|
||||
reader: (MetaItem?) -> R,
|
||||
writer: (R) -> MetaItem?,
|
||||
public fun <R> MutableMetaDelegate.convert(
|
||||
reader: (Meta?) -> R,
|
||||
writer: (R) -> Meta?,
|
||||
): ReadWriteProperty<Any?, R> = object : ReadWriteProperty<Any?, R> {
|
||||
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): R =
|
||||
@ -74,120 +74,112 @@ public fun <R> MutableItemDelegate.convert(
|
||||
/**
|
||||
* A property delegate that uses custom key
|
||||
*/
|
||||
public fun MutableItemProvider.value(key: Name? = null): ReadWriteProperty<Any?, Value?> =
|
||||
public fun MutableMeta.value(key: Name? = null): ReadWriteProperty<Any?, Value?> =
|
||||
item(key).convert(MetaConverter.value)
|
||||
|
||||
public fun MutableItemProvider.string(key: Name? = null): ReadWriteProperty<Any?, String?> =
|
||||
public fun MutableMeta.string(key: Name? = null): ReadWriteProperty<Any?, String?> =
|
||||
item(key).convert(MetaConverter.string)
|
||||
|
||||
public fun MutableItemProvider.boolean(key: Name? = null): ReadWriteProperty<Any?, Boolean?> =
|
||||
public fun MutableMeta.boolean(key: Name? = null): ReadWriteProperty<Any?, Boolean?> =
|
||||
item(key).convert(MetaConverter.boolean)
|
||||
|
||||
public fun MutableItemProvider.number(key: Name? = null): ReadWriteProperty<Any?, Number?> =
|
||||
public fun MutableMeta.number(key: Name? = null): ReadWriteProperty<Any?, Number?> =
|
||||
item(key).convert(MetaConverter.number)
|
||||
|
||||
public fun MutableItemProvider.string(default: String, key: Name? = null): ReadWriteProperty<Any?, String> =
|
||||
public fun MutableMeta.string(default: String, key: Name? = null): ReadWriteProperty<Any?, String> =
|
||||
item(key).convert(MetaConverter.string) { default }
|
||||
|
||||
public fun MutableItemProvider.boolean(default: Boolean, key: Name? = null): ReadWriteProperty<Any?, Boolean> =
|
||||
public fun MutableMeta.boolean(default: Boolean, key: Name? = null): ReadWriteProperty<Any?, Boolean> =
|
||||
item(key).convert(MetaConverter.boolean) { default }
|
||||
|
||||
public fun MutableItemProvider.number(default: Number, key: Name? = null): ReadWriteProperty<Any?, Number> =
|
||||
public fun MutableMeta.number(default: Number, key: Name? = null): ReadWriteProperty<Any?, Number> =
|
||||
item(key).convert(MetaConverter.number) { default }
|
||||
|
||||
public fun MutableItemProvider.value(key: Name? = null, default: () -> Value): ReadWriteProperty<Any?, Value> =
|
||||
public fun MutableMeta.value(key: Name? = null, default: () -> Value): ReadWriteProperty<Any?, Value> =
|
||||
item(key).convert(MetaConverter.value, default)
|
||||
|
||||
public fun MutableItemProvider.string(key: Name? = null, default: () -> String): ReadWriteProperty<Any?, String> =
|
||||
public fun MutableMeta.string(key: Name? = null, default: () -> String): ReadWriteProperty<Any?, String> =
|
||||
item(key).convert(MetaConverter.string, default)
|
||||
|
||||
public fun MutableItemProvider.boolean(key: Name? = null, default: () -> Boolean): ReadWriteProperty<Any?, Boolean> =
|
||||
public fun MutableMeta.boolean(key: Name? = null, default: () -> Boolean): ReadWriteProperty<Any?, Boolean> =
|
||||
item(key).convert(MetaConverter.boolean, default)
|
||||
|
||||
public fun MutableItemProvider.number(key: Name? = null, default: () -> Number): ReadWriteProperty<Any?, Number> =
|
||||
public fun MutableMeta.number(key: Name? = null, default: () -> Number): ReadWriteProperty<Any?, Number> =
|
||||
item(key).convert(MetaConverter.number, default)
|
||||
|
||||
public inline fun <reified E : Enum<E>> MutableItemProvider.enum(
|
||||
public inline fun <reified E : Enum<E>> MutableMeta.enum(
|
||||
default: E,
|
||||
key: Name? = null,
|
||||
): ReadWriteProperty<Any?, E> =
|
||||
item(key).convert(MetaConverter.enum()) { default }
|
||||
|
||||
public fun MutableItemProvider.node(key: Name? = null): ReadWriteProperty<Any?, Meta?> = item(key).convert(
|
||||
reader = { it.node },
|
||||
writer = { it?.asMetaItem() }
|
||||
)
|
||||
|
||||
public inline fun <reified M : MutableMeta<M>> M.node(key: Name? = null): ReadWriteProperty<Any?, M?> =
|
||||
item(key).convert(reader = { it?.let { it.node as M } }, writer = { it?.let { MetaItemNode(it) } })
|
||||
|
||||
/* Number delegates */
|
||||
|
||||
public fun MutableItemProvider.int(key: Name? = null): ReadWriteProperty<Any?, Int?> =
|
||||
public fun MutableMeta.int(key: Name? = null): ReadWriteProperty<Any?, Int?> =
|
||||
item(key).convert(MetaConverter.int)
|
||||
|
||||
public fun MutableItemProvider.double(key: Name? = null): ReadWriteProperty<Any?, Double?> =
|
||||
public fun MutableMeta.double(key: Name? = null): ReadWriteProperty<Any?, Double?> =
|
||||
item(key).convert(MetaConverter.double)
|
||||
|
||||
public fun MutableItemProvider.long(key: Name? = null): ReadWriteProperty<Any?, Long?> =
|
||||
public fun MutableMeta.long(key: Name? = null): ReadWriteProperty<Any?, Long?> =
|
||||
item(key).convert(MetaConverter.long)
|
||||
|
||||
public fun MutableItemProvider.float(key: Name? = null): ReadWriteProperty<Any?, Float?> =
|
||||
public fun MutableMeta.float(key: Name? = null): ReadWriteProperty<Any?, Float?> =
|
||||
item(key).convert(MetaConverter.float)
|
||||
|
||||
|
||||
/* Safe number delegates*/
|
||||
|
||||
public fun MutableItemProvider.int(default: Int, key: Name? = null): ReadWriteProperty<Any?, Int> =
|
||||
public fun MutableMeta.int(default: Int, key: Name? = null): ReadWriteProperty<Any?, Int> =
|
||||
item(key).convert(MetaConverter.int) { default }
|
||||
|
||||
public fun MutableItemProvider.double(default: Double, key: Name? = null): ReadWriteProperty<Any?, Double> =
|
||||
public fun MutableMeta.double(default: Double, key: Name? = null): ReadWriteProperty<Any?, Double> =
|
||||
item(key).convert(MetaConverter.double) { default }
|
||||
|
||||
public fun MutableItemProvider.long(default: Long, key: Name? = null): ReadWriteProperty<Any?, Long> =
|
||||
public fun MutableMeta.long(default: Long, key: Name? = null): ReadWriteProperty<Any?, Long> =
|
||||
item(key).convert(MetaConverter.long) { default }
|
||||
|
||||
public fun MutableItemProvider.float(default: Float, key: Name? = null): ReadWriteProperty<Any?, Float> =
|
||||
public fun MutableMeta.float(default: Float, key: Name? = null): ReadWriteProperty<Any?, Float> =
|
||||
item(key).convert(MetaConverter.float) { default }
|
||||
|
||||
|
||||
/* Extra delegates for special cases */
|
||||
|
||||
public fun MutableItemProvider.stringList(
|
||||
public fun MutableMeta.stringList(
|
||||
vararg default: String,
|
||||
key: Name? = null,
|
||||
): ReadWriteProperty<Any?, List<String>> = item(key).convert(
|
||||
reader = { it?.stringList ?: listOf(*default) },
|
||||
writer = { it.map { str -> str.asValue() }.asValue().asMetaItem() }
|
||||
writer = { Meta(it.map { str -> str.asValue() }.asValue()) }
|
||||
)
|
||||
|
||||
public fun MutableItemProvider.stringList(
|
||||
public fun MutableMeta.stringList(
|
||||
key: Name? = null,
|
||||
): ReadWriteProperty<Any?, List<String>?> = item(key).convert(
|
||||
reader = { it?.stringList },
|
||||
writer = { it?.map { str -> str.asValue() }?.asValue()?.asMetaItem() }
|
||||
writer = { it?.map { str -> str.asValue() }?.asValue()?.let { Meta(it) } }
|
||||
)
|
||||
|
||||
public fun MutableItemProvider.numberList(
|
||||
public fun MutableMeta.numberList(
|
||||
vararg default: Number,
|
||||
key: Name? = null,
|
||||
): ReadWriteProperty<Any?, List<Number>> = item(key).convert(
|
||||
reader = { it?.value?.list?.map { value -> value.numberOrNull ?: Double.NaN } ?: listOf(*default) },
|
||||
writer = { it.map { num -> num.asValue() }.asValue().asMetaItem() }
|
||||
writer = { Meta(it.map { num -> num.asValue() }.asValue()) }
|
||||
)
|
||||
|
||||
/* A special delegate for double arrays */
|
||||
|
||||
|
||||
public fun MutableItemProvider.doubleArray(
|
||||
public fun MutableMeta.doubleArray(
|
||||
vararg default: Double,
|
||||
key: Name? = null,
|
||||
): ReadWriteProperty<Any?, DoubleArray> = item(key).convert(
|
||||
reader = { it?.value?.doubleArray ?: doubleArrayOf(*default) },
|
||||
writer = { DoubleArrayValue(it).asMetaItem() }
|
||||
writer = { Meta(DoubleArrayValue(it)) }
|
||||
)
|
||||
|
||||
public fun <T> MutableItemProvider.listValue(
|
||||
public fun <T> MutableMeta.listValue(
|
||||
key: Name? = null,
|
||||
writer: (T) -> Value = { Value.of(it) },
|
||||
reader: (Value) -> T,
|
@ -1,159 +0,0 @@
|
||||
package space.kscience.dataforge.meta
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import space.kscience.dataforge.names.*
|
||||
import kotlin.js.JsName
|
||||
import kotlin.jvm.Synchronized
|
||||
import kotlin.reflect.KProperty1
|
||||
|
||||
|
||||
internal data class ItemListener(
|
||||
val owner: Any? = null,
|
||||
val action: (name: Name, oldItem: MetaItem?, newItem: MetaItem?) -> Unit,
|
||||
)
|
||||
|
||||
/**
|
||||
* An item provider that could be observed and mutated
|
||||
*/
|
||||
public interface ObservableItemProvider : ItemProvider, MutableItemProvider {
|
||||
/**
|
||||
* Add change listener to this meta. Owner is declared to be able to remove listeners later. Listener without owner could not be removed
|
||||
*/
|
||||
public fun onChange(owner: Any?, action: (name: Name, oldItem: MetaItem?, newItem: MetaItem?) -> Unit)
|
||||
|
||||
/**
|
||||
* Remove all listeners belonging to given owner
|
||||
*/
|
||||
public fun removeListener(owner: Any?)
|
||||
}
|
||||
|
||||
private open class ObservableItemProviderWrapper(
|
||||
open val itemProvider: MutableItemProvider,
|
||||
open val parent: Pair<ObservableItemProviderWrapper, Name>? = null
|
||||
) : ObservableItemProvider {
|
||||
|
||||
override fun getItem(name: Name): MetaItem? = itemProvider.getItem(name)
|
||||
|
||||
private val listeners = HashSet<ItemListener>()
|
||||
|
||||
@Synchronized
|
||||
private fun itemChanged(name: Name, oldItem: MetaItem?, newItem: MetaItem?) {
|
||||
listeners.forEach { it.action(name, oldItem, newItem) }
|
||||
}
|
||||
|
||||
override fun setItem(name: Name, item: MetaItem?) {
|
||||
val oldItem = getItem(name)
|
||||
itemProvider.setItem(name, item)
|
||||
itemChanged(name, oldItem, item)
|
||||
|
||||
//Recursively send update to parent listeners
|
||||
parent?.let { (parentNode, token) ->
|
||||
parentNode.itemChanged(token + name, oldItem, item)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun onChange(owner: Any?, action: (Name, MetaItem?, MetaItem?) -> Unit) {
|
||||
listeners.add(ItemListener(owner, action))
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun removeListener(owner: Any?) {
|
||||
listeners.removeAll { it.owner === owner }
|
||||
}
|
||||
}
|
||||
|
||||
public fun MutableItemProvider.asObservable(): ObservableItemProvider =
|
||||
(this as? ObservableItemProvider) ?: ObservableItemProviderWrapper(this)
|
||||
|
||||
/**
|
||||
* A Meta instance that could be both mutated and observed.
|
||||
*/
|
||||
@Serializable(ObservableMetaSerializer::class)
|
||||
public interface ObservableMeta : ObservableItemProvider, MutableMeta<ObservableMeta>
|
||||
|
||||
/**
|
||||
* A wrapper class that creates observable meta node from regular meta node
|
||||
*/
|
||||
private class ObservableMetaWrapper<M : MutableMeta<M>>(
|
||||
override val itemProvider: M,
|
||||
override val parent: Pair<ObservableMetaWrapper<M>, Name>? = null
|
||||
) : ObservableItemProviderWrapper(itemProvider, parent), ObservableMeta {
|
||||
override fun equals(other: Any?): Boolean = (itemProvider == other)
|
||||
|
||||
override fun hashCode(): Int = itemProvider.hashCode()
|
||||
|
||||
override fun toString(): String = itemProvider.toString()
|
||||
|
||||
private fun wrapItem(name: Name, item: TypedMetaItem<M>): TypedMetaItem<ObservableMeta> {
|
||||
return when (item) {
|
||||
is MetaItemValue -> item
|
||||
is MetaItemNode<M> -> ObservableMetaWrapper(item.node, this to name).asMetaItem()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItem(name: Name): TypedMetaItem<ObservableMeta>? = itemProvider[name]?.let {
|
||||
wrapItem(name, it)
|
||||
}
|
||||
|
||||
override val items: Map<NameToken, TypedMetaItem<ObservableMeta>>
|
||||
get() = itemProvider.items.mapValues { (token, childItem: TypedMetaItem<M>) ->
|
||||
wrapItem(token.asName(), childItem)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If this meta is observable return itself. Otherwise return an observable wrapper. The changes of initial meta are
|
||||
* reflected on wrapper but are **NOT** observed.
|
||||
*/
|
||||
public fun <M : MutableMeta<M>> M.asObservable(): ObservableMeta =
|
||||
(this as? ObservableMeta) ?: ObservableMetaWrapper(this)
|
||||
|
||||
@JsName("buildObservableMeta")
|
||||
public fun ObservableMeta(): ObservableMeta = MetaBuilder().asObservable()
|
||||
|
||||
/**
|
||||
* 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 <O : ObservableItemProvider, T> O.useProperty(
|
||||
property: KProperty1<O, T>,
|
||||
owner: Any? = null,
|
||||
callBack: O.(T) -> Unit,
|
||||
) {
|
||||
//Pass initial value.
|
||||
callBack(property.get(this))
|
||||
onChange(owner) { name, oldItem, newItem ->
|
||||
if (name.startsWith(property.name.asName()) && oldItem != newItem) {
|
||||
callBack(property.get(this))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public object ObservableMetaSerializer : KSerializer<ObservableMeta> {
|
||||
public fun empty(): ObservableMeta = ObservableMeta()
|
||||
override val descriptor: SerialDescriptor get() = MetaSerializer.descriptor
|
||||
|
||||
override fun deserialize(decoder: Decoder): ObservableMeta {
|
||||
return MetaSerializer.deserialize(decoder).toMutableMeta().asObservable()
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, value: ObservableMeta) {
|
||||
MetaSerializer.serialize(encoder, value)
|
||||
}
|
||||
}
|
||||
|
||||
public operator fun ObservableMeta.get(token: NameToken): TypedMetaItem<ObservableMeta>? = items[token]
|
||||
|
||||
/**
|
||||
* Create a copy of this config, optionally applying the given [block].
|
||||
* The listeners of the original Config are not retained.
|
||||
*/
|
||||
public inline fun ObservableMeta.copy(block: ObservableMeta.() -> Unit = {}): ObservableMeta =
|
||||
asObservable().apply(block)
|
@ -0,0 +1,116 @@
|
||||
package space.kscience.dataforge.meta
|
||||
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.NameToken
|
||||
import space.kscience.dataforge.names.asName
|
||||
import space.kscience.dataforge.names.startsWith
|
||||
import space.kscience.dataforge.values.Value
|
||||
import kotlin.jvm.Synchronized
|
||||
import kotlin.reflect.KProperty1
|
||||
|
||||
|
||||
internal data class MetaListener(
|
||||
val owner: Any? = null,
|
||||
val callback: Meta.(name: Name) -> Unit,
|
||||
)
|
||||
|
||||
/**
|
||||
* An item provider that could be observed and mutated
|
||||
*/
|
||||
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
|
||||
*/
|
||||
public fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit)
|
||||
|
||||
/**
|
||||
* Remove all listeners belonging to given owner
|
||||
*/
|
||||
public fun removeListener(owner: Any?)
|
||||
}
|
||||
|
||||
/**
|
||||
* A [Meta] which is both observable and mutable
|
||||
*/
|
||||
public interface ObservableMutableMeta : ObservableMeta, MutableTypedMeta<ObservableMutableMeta>
|
||||
|
||||
private class ObservableMetaWrapper<M : MutableTypedMeta<M>>(
|
||||
val origin: M,
|
||||
) : ObservableMutableMeta, Meta by origin {
|
||||
|
||||
private val listeners = HashSet<MetaListener>()
|
||||
|
||||
private fun changed(name: Name) {
|
||||
listeners.forEach { it.callback(this, name) }
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit) {
|
||||
listeners.add(MetaListener(owner, callback))
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun removeListener(owner: Any?) {
|
||||
listeners.removeAll { it.owner === owner }
|
||||
}
|
||||
|
||||
override val items: Map<NameToken, ObservableMetaWrapper<M>>
|
||||
get() = origin.items.mapValues { ObservableMetaWrapper(it.value) }
|
||||
|
||||
override var value: Value?
|
||||
get() = origin.value
|
||||
set(value) {
|
||||
origin.value = value
|
||||
changed(Name.EMPTY)
|
||||
}
|
||||
|
||||
override fun attach(name: Name, node: ObservableMutableMeta) {
|
||||
origin.attach(name, node.origin)
|
||||
changed(name)
|
||||
}
|
||||
|
||||
override fun getOrCreate(name: Name): ObservableMutableMeta =
|
||||
get(name) ?: ObservableMetaWrapper(origin.getOrCreate(name))
|
||||
|
||||
|
||||
override fun removeNode(name: Name) {
|
||||
origin.removeNode(name)
|
||||
changed(name)
|
||||
}
|
||||
|
||||
override fun set(name: Name, meta: Meta) {
|
||||
val oldMeta = get(name)
|
||||
origin[name] = meta
|
||||
if (oldMeta != meta) {
|
||||
changed(name)
|
||||
}
|
||||
}
|
||||
|
||||
override fun toMeta(): Meta {
|
||||
return origin.toMeta()
|
||||
}
|
||||
}
|
||||
|
||||
public fun <M : MutableTypedMeta<M>> MutableTypedMeta<M>.asObservable(): ObservableMeta =
|
||||
(this as? ObservableMeta) ?: ObservableMetaWrapper(self)
|
||||
|
||||
|
||||
/**
|
||||
* 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 <O : ObservableMeta, T> O.useProperty(
|
||||
property: KProperty1<O, T>,
|
||||
owner: Any? = null,
|
||||
callBack: O.(T) -> Unit,
|
||||
) {
|
||||
//Pass initial value.
|
||||
callBack(property.get(this))
|
||||
onChange(owner) { name ->
|
||||
if (name.startsWith(property.name.asName())) {
|
||||
callBack(property.get(this@useProperty))
|
||||
}
|
||||
}
|
||||
}
|
@ -1,54 +1,38 @@
|
||||
package space.kscience.dataforge.meta
|
||||
|
||||
import space.kscience.dataforge.meta.descriptors.Described
|
||||
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
|
||||
import space.kscience.dataforge.meta.descriptors.get
|
||||
import space.kscience.dataforge.meta.descriptors.validateItem
|
||||
import space.kscience.dataforge.meta.descriptors.*
|
||||
import space.kscience.dataforge.names.Name
|
||||
import kotlin.jvm.Synchronized
|
||||
|
||||
/**
|
||||
* A base for delegate-based or descriptor-based scheme. [Scheme] has an empty constructor to simplify usage from [Specification].
|
||||
* Default item provider and [NodeDescriptor] are optional
|
||||
*/
|
||||
public open class Scheme(
|
||||
private var items: ObservableItemProvider = ObservableMeta(),
|
||||
final override var descriptor: NodeDescriptor? = null
|
||||
) : Described, MetaRepr, ObservableItemProvider {
|
||||
public open class Scheme internal constructor(
|
||||
source: MutableMeta = MutableMeta()
|
||||
) : Described, ObservableMutableMeta, Meta by source {
|
||||
|
||||
/**
|
||||
* Add a listener to this scheme changes. If the inner provider is observable, then listening will be delegated to it.
|
||||
* Otherwise, local listeners will be created.
|
||||
*/
|
||||
@Synchronized
|
||||
override fun onChange(owner: Any?, action: (Name, MetaItem?, MetaItem?) -> Unit) {
|
||||
items.onChange(owner, action)
|
||||
}
|
||||
private var source = source.asObservable()
|
||||
|
||||
final override var descriptor: MetaDescriptor? = null
|
||||
internal set
|
||||
|
||||
/**
|
||||
* Remove all listeners belonging to given owner
|
||||
*/
|
||||
@Synchronized
|
||||
override fun removeListener(owner: Any?) {
|
||||
items.removeListener(owner)
|
||||
}
|
||||
|
||||
internal fun wrap(
|
||||
items: MutableItemProvider,
|
||||
items: MutableMeta,
|
||||
preserveDefault: Boolean = false
|
||||
) {
|
||||
this.items = if (preserveDefault) items.withDefault(this.items).asObservable() else items.asObservable()
|
||||
this.source = if (preserveDefault) items.withDefault(this.source) else items
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a property with default
|
||||
*/
|
||||
override fun getItem(name: Name): MetaItem? = items[name] ?: descriptor?.get(name)?.defaultValue
|
||||
//
|
||||
// /**
|
||||
// * Get a property with default
|
||||
// */
|
||||
// override fun getItem(name: Name): MetaItem? = source[name] ?: descriptor?.get(name)?.defaultValue
|
||||
|
||||
/**
|
||||
* Check if property with given [name] could be assigned to [item]
|
||||
*/
|
||||
public open fun validateItem(name: Name, item: MetaItem?): Boolean {
|
||||
public open fun validate(name: Name, item: Meta?): Boolean {
|
||||
val descriptor = descriptor?.get(name)
|
||||
return descriptor?.validateItem(item) ?: true
|
||||
}
|
||||
@ -56,30 +40,25 @@ public open class Scheme(
|
||||
/**
|
||||
* Set a configurable property
|
||||
*/
|
||||
override fun setItem(name: Name, item: MetaItem?) {
|
||||
val oldItem = items[name]
|
||||
if (oldItem != item) {
|
||||
if (validateItem(name, item)) {
|
||||
items[name] = item
|
||||
override fun set(name: Name, meta: Meta) {
|
||||
val oldItem = source[name]
|
||||
if (oldItem != meta) {
|
||||
if (validate(name, meta)) {
|
||||
source[name] = meta
|
||||
} else {
|
||||
error("Validation failed for property $name with value $item")
|
||||
error("Validation failed for property $name with value $meta")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun toMeta(): Laminate = Laminate(items.rootNode, descriptor?.defaultMeta)
|
||||
override fun toMeta(): Laminate = Laminate(source, descriptor?.defaultMeta)
|
||||
}
|
||||
|
||||
/**
|
||||
* The scheme is considered empty only if its root item does not exist.
|
||||
*/
|
||||
public fun Scheme.isEmpty(): Boolean = rootItem == null
|
||||
|
||||
/**
|
||||
* Relocate scheme target onto given [MutableItemProvider]. Old provider does not get updates anymore.
|
||||
* Relocate scheme target onto given [MutableTypedMeta]. Old provider does not get updates anymore.
|
||||
* Current state of the scheme used as a default.
|
||||
*/
|
||||
public fun <T : Scheme> T.retarget(provider: MutableItemProvider): T = apply {
|
||||
public fun <T : Scheme> T.retarget(provider: MutableMeta): T = apply {
|
||||
wrap(provider, true)
|
||||
}
|
||||
|
||||
@ -95,16 +74,16 @@ public open class SchemeSpec<out T : Scheme>(
|
||||
private val builder: () -> T,
|
||||
) : Specification<T>, Described {
|
||||
|
||||
override fun read(items: ItemProvider): T = empty().also {
|
||||
it.wrap(ObservableMeta().withDefault(items))
|
||||
override fun read(items: Meta): T = empty().also {
|
||||
it.wrap(MutableMeta().withDefault(items))
|
||||
}
|
||||
|
||||
override fun write(target: MutableItemProvider): T = empty().also {
|
||||
override fun write(target: MutableMeta): T = empty().also {
|
||||
it.wrap(target)
|
||||
}
|
||||
|
||||
//TODO Generate descriptor from Scheme class
|
||||
override val descriptor: NodeDescriptor? get() = null
|
||||
override val descriptor: MetaDescriptor? get() = null
|
||||
|
||||
override fun empty(): T = builder().also {
|
||||
it.descriptor = descriptor
|
||||
|
@ -1,6 +1,7 @@
|
||||
package space.kscience.dataforge.meta
|
||||
|
||||
import space.kscience.dataforge.names.NameToken
|
||||
import space.kscience.dataforge.values.Value
|
||||
|
||||
/**
|
||||
* The meta implementation which is guaranteed to be immutable.
|
||||
@ -8,16 +9,25 @@ import space.kscience.dataforge.names.NameToken
|
||||
* If the argument is possibly mutable node, it is copied on creation
|
||||
*/
|
||||
public class SealedMeta internal constructor(
|
||||
override val items: Map<NameToken, TypedMetaItem<SealedMeta>>,
|
||||
) : AbstractTypedMeta<SealedMeta>()
|
||||
override val value: Value?,
|
||||
override val items: Map<NameToken, SealedMeta>
|
||||
) : TypedMeta<SealedMeta> {
|
||||
override fun toString(): String = Meta.toString(this)
|
||||
override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta)
|
||||
override fun hashCode(): Int = Meta.hashCode(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate sealed node from [this]. If it is already sealed return it as is
|
||||
*/
|
||||
public fun Meta.seal(): SealedMeta = this as? SealedMeta ?: SealedMeta(items.mapValues { entry -> entry.value.seal() })
|
||||
public fun Meta.seal(): SealedMeta = this as? SealedMeta ?: SealedMeta(value, items.mapValues { entry ->
|
||||
entry.value.seal()
|
||||
})
|
||||
|
||||
@Suppress("FunctionName")
|
||||
public fun Meta(value: Value): SealedMeta = SealedMeta(value, emptyMap())
|
||||
|
||||
@Suppress("FunctionName")
|
||||
public fun Meta(builder: MutableMeta.() -> Unit): SealedMeta =
|
||||
MutableMeta(builder).seal()
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
public fun MetaItem.seal(): TypedMetaItem<SealedMeta> = when (this) {
|
||||
is MetaItemValue -> this
|
||||
is MetaItemNode -> MetaItemNode(node.seal())
|
||||
}
|
@ -6,13 +6,13 @@ import space.kscience.dataforge.names.asName
|
||||
import kotlin.properties.ReadWriteProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
public interface ReadOnlySpecification<out T : ItemProvider> {
|
||||
public interface ReadOnlySpecification<out T : Any> {
|
||||
|
||||
/**
|
||||
* 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(items: ItemProvider): T
|
||||
|
||||
public fun read(source: Meta): T
|
||||
|
||||
/**
|
||||
* Generate an empty object
|
||||
@ -31,48 +31,49 @@ public interface ReadOnlySpecification<out T : ItemProvider> {
|
||||
* By convention [Scheme] companion should inherit this class
|
||||
*
|
||||
*/
|
||||
public interface Specification<out T : MutableItemProvider> : ReadOnlySpecification<T> {
|
||||
public interface Specification<out T : Any> : ReadOnlySpecification<T> {
|
||||
/**
|
||||
* Wrap [MutableItemProvider], using it as inner storage (changes to [Specification] are reflected on [MutableItemProvider]
|
||||
* Wrap [MutableTypedMeta], using it as inner storage (changes to [Specification] are reflected on [MutableTypedMeta]
|
||||
*/
|
||||
public fun write(target: MutableItemProvider): T
|
||||
public fun write(target: MutableMeta): T
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a [MutableItemProvider] using given specification
|
||||
* Update a [MutableTypedMeta] using given specification
|
||||
*/
|
||||
public fun <T : MutableItemProvider> MutableItemProvider.update(spec: Specification<T>, action: T.() -> Unit) {
|
||||
spec.write(this).apply(action)
|
||||
}
|
||||
public fun <M : MutableTypedMeta<M>, T : Any> M.update(
|
||||
spec: Specification<T>,
|
||||
action: T.() -> Unit
|
||||
): T = spec.write(this).apply(action)
|
||||
|
||||
|
||||
/**
|
||||
* Update configuration using given specification
|
||||
*/
|
||||
public fun <C : MutableItemProvider, S : Specification<C>> Configurable.update(
|
||||
spec: S,
|
||||
action: C.() -> Unit,
|
||||
) {
|
||||
config.update(spec, action)
|
||||
}
|
||||
public fun <T : Any> Configurable.update(
|
||||
spec: Specification<T>,
|
||||
action: T.() -> Unit,
|
||||
): T = spec.write(config).apply(action)
|
||||
|
||||
public fun <T : MutableItemProvider> TypedMetaItem<MutableMeta<*>>.withSpec(spec: Specification<T>): T? =
|
||||
node?.let { spec.write(it) }
|
||||
//
|
||||
//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> MutableItemProvider.spec(
|
||||
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 getChild(name).let { spec.write(it) }
|
||||
return spec.write(getOrCreate(name))
|
||||
}
|
||||
|
||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
||||
val name = key ?: property.name.asName()
|
||||
set(name, value.toMeta().asMetaItem())
|
||||
set(name, value.toMeta())
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,21 +83,13 @@ public fun <T : Scheme> MutableItemProvider.spec(
|
||||
* The list is a snapshot of children state, so change in structure is not reflected on its composition.
|
||||
*/
|
||||
@DFExperimental
|
||||
public fun <T : Scheme> MutableItemProvider.listOfSpec(
|
||||
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).map {
|
||||
when (val value = it.value) {
|
||||
is MetaItemNode<*> -> when (value.node) {
|
||||
is MutableItemProvider -> spec.write(value.node)
|
||||
else -> spec.read(value.node)
|
||||
}
|
||||
is MetaItemValue -> spec.read(value)
|
||||
}
|
||||
}
|
||||
return getIndexed(name).values.map { spec.write(it as MutableMeta) }
|
||||
}
|
||||
|
||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: List<T>) {
|
||||
|
@ -1,56 +0,0 @@
|
||||
package space.kscience.dataforge.meta
|
||||
|
||||
import kotlinx.serialization.json.Json
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.NameToken
|
||||
import space.kscience.dataforge.names.toName
|
||||
|
||||
/**
|
||||
* A meta node that ensures that all of its descendants has at least the same type
|
||||
*/
|
||||
public interface TypedMeta<out M : TypedMeta<M>> : Meta {
|
||||
override val items: Map<NameToken, TypedMetaItem<M>>
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun getItem(name: Name): TypedMetaItem<M>? = super.getItem(name)?.let { it as TypedMetaItem<M> }
|
||||
//Typed meta guarantees that all children have M type
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The same as [Meta.get], but with specific node type
|
||||
*/
|
||||
public operator fun <M : TypedMeta<M>> M.get(name: Name): TypedMetaItem<M>? = getItem(name)
|
||||
|
||||
|
||||
public operator fun <M : TypedMeta<M>> M.get(key: String): TypedMetaItem<M>? = this[key.toName()]
|
||||
public operator fun <M : TypedMeta<M>> M.get(key: NameToken): TypedMetaItem<M>? = items[key]
|
||||
|
||||
/**
|
||||
* Equals, hashcode and to string for any meta
|
||||
*/
|
||||
public abstract class MetaBase : Meta {
|
||||
|
||||
override fun equals(other: Any?): Boolean = if (other is Meta) {
|
||||
Meta.equals(this, other)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = items.hashCode()
|
||||
|
||||
override fun toString(): String = json.encodeToString(MetaSerializer, this)
|
||||
|
||||
public companion object{
|
||||
private val json = Json {
|
||||
prettyPrint = true
|
||||
useArrayPolymorphism = true
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Equals and hash code implementation for meta node
|
||||
*/
|
||||
public abstract class AbstractTypedMeta<M : TypedMeta<M>> : TypedMeta<M>, MetaBase()
|
@ -4,7 +4,7 @@ package space.kscience.dataforge.meta.descriptors
|
||||
* An object which provides its descriptor
|
||||
*/
|
||||
public interface Described {
|
||||
public val descriptor: ItemDescriptor?
|
||||
public val descriptor: MetaDescriptor?
|
||||
|
||||
public companion object {
|
||||
//public const val DESCRIPTOR_NODE: String = "@descriptor"
|
||||
|
@ -1,126 +1,127 @@
|
||||
package space.kscience.dataforge.meta.descriptors
|
||||
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.misc.DFBuilder
|
||||
import space.kscience.dataforge.names.*
|
||||
|
||||
/**
|
||||
* A common parent for [ValueDescriptor] and [NodeDescriptor]. Describes a single [TypedMetaItem] or a group of same-name-siblings.
|
||||
*/
|
||||
public sealed interface ItemDescriptor: MetaRepr {
|
||||
|
||||
/**
|
||||
* True if same name siblings with this name are allowed
|
||||
*/
|
||||
public val multiple: Boolean
|
||||
|
||||
/**
|
||||
* The item description text
|
||||
*/
|
||||
public val info: String?
|
||||
|
||||
/**
|
||||
* True if the item is required
|
||||
*/
|
||||
public val required: Boolean
|
||||
|
||||
|
||||
/**
|
||||
* Additional attributes of an item. For example validation and widget parameters
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public val attributes: Meta?
|
||||
|
||||
/**
|
||||
* An index field by which this node is identified in case of same name siblings construct
|
||||
*/
|
||||
public val indexKey: String
|
||||
|
||||
/**
|
||||
* Compute and cache the default [MetaItem] value described by this descriptor
|
||||
*/
|
||||
public val defaultValue: MetaItem?
|
||||
|
||||
public companion object {
|
||||
public const val DEFAULT_INDEX_KEY: String = "@index"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The builder for [ItemDescriptor]
|
||||
*/
|
||||
@DFBuilder
|
||||
public sealed class ItemDescriptorBuilder(final override val config: ObservableMeta) : Configurable, ItemDescriptor {
|
||||
|
||||
/**
|
||||
* True if same name siblings with this name are allowed
|
||||
*/
|
||||
override var multiple: Boolean by config.boolean(false)
|
||||
|
||||
/**
|
||||
* The item description text
|
||||
*/
|
||||
override var info: String? by config.string()
|
||||
|
||||
/**
|
||||
* True if the item is required
|
||||
*/
|
||||
abstract override var required: Boolean
|
||||
|
||||
|
||||
/**
|
||||
* Additional attributes of an item. For example validation and widget parameters
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
override var attributes: ObservableMeta? by config.node()
|
||||
|
||||
/**
|
||||
* An index field by which this node is identified in case of same name siblings construct
|
||||
*/
|
||||
override var indexKey: String by config.string(DEFAULT_INDEX_KEY)
|
||||
|
||||
public abstract fun build(): ItemDescriptor
|
||||
|
||||
override fun toMeta(): Meta = config
|
||||
|
||||
public companion object {
|
||||
public const val DEFAULT_INDEX_KEY: String = "@index"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure attributes of the descriptor, creating an attributes node if needed.
|
||||
*/
|
||||
public inline fun ItemDescriptorBuilder.attributes(block: ObservableMeta.() -> Unit) {
|
||||
(attributes ?: ObservableMeta().also { this.attributes = it }).apply(block)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if given item suits the descriptor
|
||||
*/
|
||||
public fun ItemDescriptor.validateItem(item: MetaItem?): Boolean {
|
||||
if (item == null) return !required
|
||||
return when (this) {
|
||||
is ValueDescriptor -> isAllowedValue(item.value ?: return false)
|
||||
is NodeDescriptor -> items.all { (key, d) ->
|
||||
d.validateItem(item.node[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a descriptor item associated with given name or null if item for given name not provided
|
||||
*/
|
||||
public 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.firstOrNull()!!.toString()]?.get(name.cutFirst())
|
||||
}
|
||||
}
|
||||
|
||||
public operator fun ItemDescriptor.get(name: String): ItemDescriptor? = get(name.toName())
|
||||
|
||||
//package space.kscience.dataforge.meta.descriptors
|
||||
//
|
||||
//import space.kscience.dataforge.meta.*
|
||||
//import space.kscience.dataforge.misc.DFBuilder
|
||||
//import space.kscience.dataforge.names.*
|
||||
//import space.kscience.dataforge.values.Value
|
||||
//
|
||||
///**
|
||||
// * A common parent for [ValueDescriptor] and [NodeDescriptor]. Describes a single [TypedMetaItem] or a group of same-name-siblings.
|
||||
// */
|
||||
//public sealed interface ItemDescriptor: MetaRepr {
|
||||
//
|
||||
// /**
|
||||
// * True if same name siblings with this name are allowed
|
||||
// */
|
||||
// public val multiple: Boolean
|
||||
//
|
||||
// /**
|
||||
// * The item description text
|
||||
// */
|
||||
// public val info: String?
|
||||
//
|
||||
// /**
|
||||
// * True if the item is required
|
||||
// */
|
||||
// public val required: Boolean
|
||||
//
|
||||
//
|
||||
// /**
|
||||
// * Additional attributes of an item. For example validation and widget parameters
|
||||
// *
|
||||
// * @return
|
||||
// */
|
||||
// public val attributes: Meta?
|
||||
//
|
||||
// /**
|
||||
// * An index field by which this node is identified in case of same name siblings construct
|
||||
// */
|
||||
// public val indexKey: String
|
||||
//
|
||||
// /**
|
||||
// * Compute and cache the default [Meta] value described by this descriptor
|
||||
// */
|
||||
// public val defaultValue: Meta?
|
||||
//
|
||||
// public companion object {
|
||||
// public const val DEFAULT_INDEX_KEY: String = "@index"
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//
|
||||
///**
|
||||
// * The builder for [ItemDescriptor]
|
||||
// */
|
||||
//@DFBuilder
|
||||
//public sealed class ItemDescriptorBuilder(final override val config: MutableMeta) : Configurable, ItemDescriptor {
|
||||
//
|
||||
// /**
|
||||
// * True if same name siblings with this name are allowed
|
||||
// */
|
||||
// override var multiple: Boolean by config.boolean(false)
|
||||
//
|
||||
// /**
|
||||
// * The item description text
|
||||
// */
|
||||
// override var info: String? by config.string()
|
||||
//
|
||||
// /**
|
||||
// * True if the item is required
|
||||
// */
|
||||
// abstract override var required: Boolean
|
||||
//
|
||||
//
|
||||
// /**
|
||||
// * Additional attributes of an item. For example validation and widget parameters
|
||||
// *
|
||||
// * @return
|
||||
// */
|
||||
// override var attributes: MutableMeta? by config.node()
|
||||
//
|
||||
// /**
|
||||
// * An index field by which this node is identified in case of same name siblings construct
|
||||
// */
|
||||
// override var indexKey: String by config.string(DEFAULT_INDEX_KEY)
|
||||
//
|
||||
// public abstract fun build(): ItemDescriptor
|
||||
//
|
||||
// override fun toMeta(): Meta = config
|
||||
//
|
||||
// public companion object {
|
||||
// public const val DEFAULT_INDEX_KEY: String = "@index"
|
||||
// }
|
||||
//}
|
||||
//
|
||||
///**
|
||||
// * Configure attributes of the descriptor, creating an attributes node if needed.
|
||||
// */
|
||||
//public inline fun ItemDescriptorBuilder.attributes(block: MutableMeta.() -> Unit) {
|
||||
// (attributes ?: MutableMeta().also { this.attributes = it }).apply(block)
|
||||
//}
|
||||
//
|
||||
///**
|
||||
// * Check if given item suits the descriptor
|
||||
// */
|
||||
//public fun ItemDescriptor.validateItem(item: MetaItem?): Boolean {
|
||||
// if (item == null) return !required
|
||||
// return when (this) {
|
||||
// is ValueDescriptor -> isAllowedValue(item.value ?: return false)
|
||||
// is NodeDescriptor -> items.all { (key, d) ->
|
||||
// d.validateItem(item.node[key])
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
///**
|
||||
// * Get a descriptor item associated with given name or null if item for given name not provided
|
||||
// */
|
||||
//public 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.firstOrNull()!!.toString()]?.get(name.cutFirst())
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//public operator fun ItemDescriptor.get(name: String): ItemDescriptor? = get(name.toName())
|
||||
//
|
||||
|
@ -0,0 +1,60 @@
|
||||
package space.kscience.dataforge.meta.descriptors
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.names.*
|
||||
import space.kscience.dataforge.values.Value
|
||||
import space.kscience.dataforge.values.ValueType
|
||||
|
||||
/**
|
||||
* The descriptor for a meta
|
||||
* @param info description text
|
||||
* @param children child descriptors for this node
|
||||
* @param multiple True if same name siblings with this name are allowed
|
||||
* @param required True if the item is required
|
||||
* @param type list of allowed types for [Meta.value], null if all values are allowed
|
||||
* @param indexKey An index field by which this node is identified in case of same name siblings construct
|
||||
* @param defaultValue the default [Meta.value] for the node
|
||||
* @param attributes additional attributes of this descriptor. For example validation and widget parameters
|
||||
*/
|
||||
@Serializable
|
||||
public data class MetaDescriptor(
|
||||
public val info: String? = null,
|
||||
public val children: Map<String, MetaDescriptor> = emptyMap(),
|
||||
public val multiple: Boolean = false,
|
||||
public val required: Boolean = false,
|
||||
public val type: List<ValueType>? = null,
|
||||
public val indexKey: String = Meta.INDEX_KEY,
|
||||
public val defaultValue: Value? = null,
|
||||
public val attributes: Meta = Meta.EMPTY,
|
||||
)
|
||||
|
||||
public operator fun MetaDescriptor.get(name: Name): MetaDescriptor? = when (name.length) {
|
||||
0 -> this
|
||||
1 -> children[name.firstOrNull()!!.toString()]
|
||||
else -> get(name.firstOrNull()!!.asName())?.get(name.cutFirst())
|
||||
}
|
||||
|
||||
public operator fun MetaDescriptor.get(name: String): MetaDescriptor? = get(name.toName())
|
||||
|
||||
public class MetaDescriptorBuilder {
|
||||
public var info: String? = null
|
||||
public var children: MutableMap<String, MetaDescriptor> = hashMapOf()
|
||||
public var multiple: Boolean = false
|
||||
public var required: Boolean = false
|
||||
public var type: List<ValueType>? = null
|
||||
public var indexKey: String = Meta.INDEX_KEY
|
||||
public var default: Value? = null
|
||||
public var attributes: Meta = Meta.EMPTY
|
||||
|
||||
internal fun build(): MetaDescriptor = MetaDescriptor(
|
||||
info = info,
|
||||
children = children,
|
||||
multiple = multiple,
|
||||
required = required,
|
||||
type = type,
|
||||
indexKey = indexKey,
|
||||
defaultValue = default,
|
||||
attributes = attributes
|
||||
)
|
||||
}
|
@ -1,222 +1,222 @@
|
||||
package space.kscience.dataforge.meta.descriptors
|
||||
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.misc.DFBuilder
|
||||
import space.kscience.dataforge.names.*
|
||||
|
||||
|
||||
/**
|
||||
* A [Meta] that is constructed from [NodeDescriptor]
|
||||
*/
|
||||
private class DescriptorMeta(val descriptor: NodeDescriptor) : Meta, MetaBase() {
|
||||
override val items: Map<NameToken, MetaItem>
|
||||
get() = buildMap {
|
||||
descriptor.items.forEach { (token, descriptorItem) ->
|
||||
val item = descriptorItem.defaultValue
|
||||
if (item != null) {
|
||||
put(NameToken(token), item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Descriptor for meta node. Could contain additional information for viewing
|
||||
* and editing.
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
@DFBuilder
|
||||
public sealed interface NodeDescriptor : ItemDescriptor {
|
||||
/**
|
||||
* True if the node is required
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
override val required: Boolean
|
||||
|
||||
/**
|
||||
* The default for this node. Null if there is no default.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public val default: Meta?
|
||||
|
||||
/**
|
||||
* The map of children item descriptors (both nodes and values)
|
||||
*/
|
||||
public val items: Map<String, ItemDescriptor>
|
||||
|
||||
/**
|
||||
* The map of children node descriptors
|
||||
*/
|
||||
public val nodes: Map<String, NodeDescriptor>
|
||||
|
||||
/**
|
||||
* The list of children value descriptors
|
||||
*/
|
||||
public val values: Map<String, ValueDescriptor>
|
||||
|
||||
/**
|
||||
* Generate a laminate representing default item set generated by this descriptor
|
||||
*/
|
||||
public val defaultMeta: Laminate
|
||||
|
||||
public companion object {
|
||||
|
||||
internal val ITEM_KEY: Name = "item".asName()
|
||||
internal val IS_NODE_KEY: Name = "@isNode".asName()
|
||||
|
||||
//TODO infer descriptor from spec
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@DFBuilder
|
||||
public class NodeDescriptorBuilder(config: ObservableMeta = ObservableMeta()) : ItemDescriptorBuilder(config), NodeDescriptor {
|
||||
init {
|
||||
config[IS_NODE_KEY] = true
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the node is required
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
override var required: Boolean by config.boolean { default == null }
|
||||
|
||||
/**
|
||||
* The default for this node. Null if there is no default.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
override var default: ObservableMeta? by config.node()
|
||||
|
||||
/**
|
||||
* The map of children item descriptors (both nodes and values)
|
||||
*/
|
||||
override val items: Map<String, ItemDescriptor>
|
||||
get() = config.getIndexed(ITEM_KEY).entries.associate { (name, item) ->
|
||||
if (name == null) error("Child item index should not be null")
|
||||
val node = item.node ?: error("Node descriptor must be a node")
|
||||
if (node[IS_NODE_KEY].boolean == true) {
|
||||
name to NodeDescriptorBuilder(node as ObservableMeta)
|
||||
} else {
|
||||
name to ValueDescriptorBuilder(node as ObservableMeta)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The map of children node descriptors
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override val nodes: Map<String, NodeDescriptor>
|
||||
get() = config.getIndexed(ITEM_KEY).entries.filter {
|
||||
it.value.node[IS_NODE_KEY].boolean == true
|
||||
}.associate { (name, item) ->
|
||||
if (name == null) error("Child node index should not be null")
|
||||
val node = item.node ?: error("Node descriptor must be a node")
|
||||
name to NodeDescriptorBuilder(node as ObservableMeta)
|
||||
}
|
||||
|
||||
/**
|
||||
* The list of children value descriptors
|
||||
*/
|
||||
override val values: Map<String, ValueDescriptor>
|
||||
get() = config.getIndexed(ITEM_KEY).entries.filter {
|
||||
it.value.node[IS_NODE_KEY].boolean != true
|
||||
}.associate { (name, item) ->
|
||||
if (name == null) error("Child value index should not be null")
|
||||
val node = item.node ?: error("Node descriptor must be a node")
|
||||
name to ValueDescriptorBuilder(node as ObservableMeta)
|
||||
}
|
||||
|
||||
private fun buildNode(name: Name): NodeDescriptorBuilder {
|
||||
return when (name.length) {
|
||||
0 -> this
|
||||
1 -> {
|
||||
val token = NameToken(ITEM_KEY.toString(), name.toString())
|
||||
val config: ObservableMeta = config[token].node ?: ObservableMeta().also {
|
||||
it[IS_NODE_KEY] = true
|
||||
config[token] = it
|
||||
}
|
||||
NodeDescriptorBuilder(config)
|
||||
}
|
||||
else -> buildNode(name.firstOrNull()?.asName()!!).buildNode(name.cutFirst())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a child item descriptor for this node
|
||||
*/
|
||||
private fun newItem(key: String, descriptor: ItemDescriptor) {
|
||||
if (items.keys.contains(key)) error("The key $key already exists in descriptor")
|
||||
val token = ITEM_KEY.withIndex(key)
|
||||
config[token] = descriptor.toMeta()
|
||||
}
|
||||
|
||||
public fun item(name: Name, descriptor: ItemDescriptor) {
|
||||
buildNode(name.cutLast()).newItem(name.lastOrNull().toString(), descriptor)
|
||||
}
|
||||
|
||||
public fun item(name: String, descriptor: ItemDescriptor) {
|
||||
item(name.toName(), descriptor)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and configure a child node descriptor
|
||||
*/
|
||||
public fun node(name: Name, block: NodeDescriptorBuilder.() -> Unit) {
|
||||
item(name, NodeDescriptorBuilder().apply(block))
|
||||
}
|
||||
|
||||
public fun node(name: String, block: NodeDescriptorBuilder.() -> Unit) {
|
||||
node(name.toName(), block)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and configure child value descriptor
|
||||
*/
|
||||
public fun value(name: Name, block: ValueDescriptorBuilder.() -> Unit) {
|
||||
require(name.length >= 1) { "Name length for value descriptor must be non-empty" }
|
||||
item(name, ValueDescriptorBuilder().apply(block))
|
||||
}
|
||||
|
||||
public fun value(name: String, block: ValueDescriptorBuilder.() -> Unit) {
|
||||
value(name.toName(), block)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a laminate representing default item set generated by this descriptor
|
||||
*/
|
||||
override val defaultMeta: Laminate by lazy { Laminate(default, DescriptorMeta(this)) }
|
||||
|
||||
/**
|
||||
* Build a default [MetaItemNode] from this node descriptor
|
||||
*/
|
||||
override val defaultValue: MetaItem get() = MetaItemNode(defaultMeta)
|
||||
|
||||
override fun build(): NodeDescriptor = NodeDescriptorBuilder(config.copy())
|
||||
|
||||
public companion object {
|
||||
|
||||
internal val ITEM_KEY: Name = "item".asName()
|
||||
internal val IS_NODE_KEY: Name = "@isNode".asName()
|
||||
|
||||
//TODO infer descriptor from spec
|
||||
}
|
||||
}
|
||||
|
||||
public inline fun NodeDescriptor(block: NodeDescriptorBuilder.() -> Unit): NodeDescriptor =
|
||||
NodeDescriptorBuilder().apply(block)
|
||||
|
||||
/**
|
||||
* Merge two node descriptors into one using first one as primary
|
||||
*/
|
||||
public operator fun NodeDescriptor.plus(other: NodeDescriptor): NodeDescriptor {
|
||||
return NodeDescriptorBuilder().apply {
|
||||
config.update(other.toMeta())
|
||||
config.update(this@plus.toMeta())
|
||||
}
|
||||
}
|
||||
//package space.kscience.dataforge.meta.descriptors
|
||||
//
|
||||
//import space.kscience.dataforge.meta.*
|
||||
//import space.kscience.dataforge.misc.DFBuilder
|
||||
//import space.kscience.dataforge.names.*
|
||||
//
|
||||
//
|
||||
///**
|
||||
// * A [Meta] that is constructed from [NodeDescriptor]
|
||||
// */
|
||||
//private class DescriptorMeta(val descriptor: NodeDescriptor) : AbstractMeta() {
|
||||
// override val items: Map<NameToken, MetaItem>
|
||||
// get() = buildMap {
|
||||
// descriptor.items.forEach { (token, descriptorItem) ->
|
||||
// val item = descriptorItem.defaultValue
|
||||
// if (item != null) {
|
||||
// put(NameToken(token), item)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//
|
||||
///**
|
||||
// * Descriptor for meta node. Could contain additional information for viewing
|
||||
// * and editing.
|
||||
// *
|
||||
// * @author Alexander Nozik
|
||||
// */
|
||||
//@DFBuilder
|
||||
//public sealed interface NodeDescriptor : ItemDescriptor {
|
||||
// /**
|
||||
// * True if the node is required
|
||||
// *
|
||||
// * @return
|
||||
// */
|
||||
// override val required: Boolean
|
||||
//
|
||||
// /**
|
||||
// * The default for this node. Null if there is no default.
|
||||
// *
|
||||
// * @return
|
||||
// */
|
||||
// public val default: Meta?
|
||||
//
|
||||
// /**
|
||||
// * The map of children item descriptors (both nodes and values)
|
||||
// */
|
||||
// public val items: Map<String, ItemDescriptor>
|
||||
//
|
||||
// /**
|
||||
// * The map of children node descriptors
|
||||
// */
|
||||
// public val nodes: Map<String, NodeDescriptor>
|
||||
//
|
||||
// /**
|
||||
// * The list of children value descriptors
|
||||
// */
|
||||
// public val values: Map<String, ValueDescriptor>
|
||||
//
|
||||
// /**
|
||||
// * Generate a laminate representing default item set generated by this descriptor
|
||||
// */
|
||||
// public val defaultMeta: Laminate
|
||||
//
|
||||
// public companion object {
|
||||
//
|
||||
// internal val ITEM_KEY: Name = "item".asName()
|
||||
// internal val IS_NODE_KEY: Name = "@isNode".asName()
|
||||
//
|
||||
// //TODO infer descriptor from spec
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//
|
||||
//@DFBuilder
|
||||
//public class NodeDescriptorBuilder(config: MutableMeta = MutableMeta()) : ItemDescriptorBuilder(config), NodeDescriptor {
|
||||
// init {
|
||||
// config[IS_NODE_KEY] = true
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * True if the node is required
|
||||
// *
|
||||
// * @return
|
||||
// */
|
||||
// override var required: Boolean by config.boolean { default == null }
|
||||
//
|
||||
// /**
|
||||
// * The default for this node. Null if there is no default.
|
||||
// *
|
||||
// * @return
|
||||
// */
|
||||
// override var default: MutableMeta? by config.node()
|
||||
//
|
||||
// /**
|
||||
// * The map of children item descriptors (both nodes and values)
|
||||
// */
|
||||
// override val items: Map<String, ItemDescriptor>
|
||||
// get() = config.getIndexed(ITEM_KEY).entries.associate { (name, item) ->
|
||||
// if (name == null) error("Child item index should not be null")
|
||||
// val node = item.node ?: error("Node descriptor must be a node")
|
||||
// if (node[IS_NODE_KEY].boolean == true) {
|
||||
// name to NodeDescriptorBuilder(node as MutableMeta)
|
||||
// } else {
|
||||
// name to ValueDescriptorBuilder(node as MutableMeta)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * The map of children node descriptors
|
||||
// */
|
||||
// @Suppress("UNCHECKED_CAST")
|
||||
// override val nodes: Map<String, NodeDescriptor>
|
||||
// get() = config.getIndexed(ITEM_KEY).entries.filter {
|
||||
// it.value.node[IS_NODE_KEY].boolean == true
|
||||
// }.associate { (name, item) ->
|
||||
// if (name == null) error("Child node index should not be null")
|
||||
// val node = item.node ?: error("Node descriptor must be a node")
|
||||
// name to NodeDescriptorBuilder(node as MutableMeta)
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * The list of children value descriptors
|
||||
// */
|
||||
// override val values: Map<String, ValueDescriptor>
|
||||
// get() = config.getIndexed(ITEM_KEY).entries.filter {
|
||||
// it.value.node[IS_NODE_KEY].boolean != true
|
||||
// }.associate { (name, item) ->
|
||||
// if (name == null) error("Child value index should not be null")
|
||||
// val node = item.node ?: error("Node descriptor must be a node")
|
||||
// name to ValueDescriptorBuilder(node as MutableMeta)
|
||||
// }
|
||||
//
|
||||
// private fun buildNode(name: Name): NodeDescriptorBuilder {
|
||||
// return when (name.length) {
|
||||
// 0 -> this
|
||||
// 1 -> {
|
||||
// val token = NameToken(ITEM_KEY.toString(), name.toString())
|
||||
// val config: MutableMeta = config[token].node ?: MutableMeta().also {
|
||||
// it[IS_NODE_KEY] = true
|
||||
// config[token] = it
|
||||
// }
|
||||
// NodeDescriptorBuilder(config)
|
||||
// }
|
||||
// else -> buildNode(name.firstOrNull()?.asName()!!).buildNode(name.cutFirst())
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Define a child item descriptor for this node
|
||||
// */
|
||||
// private fun newItem(key: String, descriptor: ItemDescriptor) {
|
||||
// if (items.keys.contains(key)) error("The key $key already exists in descriptor")
|
||||
// val token = ITEM_KEY.withIndex(key)
|
||||
// config[token] = descriptor.toMeta()
|
||||
// }
|
||||
//
|
||||
// public fun item(name: Name, descriptor: ItemDescriptor) {
|
||||
// buildNode(name.cutLast()).newItem(name.lastOrNull().toString(), descriptor)
|
||||
// }
|
||||
//
|
||||
// public fun item(name: String, descriptor: ItemDescriptor) {
|
||||
// item(name.toName(), descriptor)
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Create and configure a child node descriptor
|
||||
// */
|
||||
// public fun node(name: Name, block: NodeDescriptorBuilder.() -> Unit) {
|
||||
// item(name, NodeDescriptorBuilder().apply(block))
|
||||
// }
|
||||
//
|
||||
// public fun node(name: String, block: NodeDescriptorBuilder.() -> Unit) {
|
||||
// node(name.toName(), block)
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Create and configure child value descriptor
|
||||
// */
|
||||
// public fun value(name: Name, block: ValueDescriptorBuilder.() -> Unit) {
|
||||
// require(name.length >= 1) { "Name length for value descriptor must be non-empty" }
|
||||
// item(name, ValueDescriptorBuilder().apply(block))
|
||||
// }
|
||||
//
|
||||
// public fun value(name: String, block: ValueDescriptorBuilder.() -> Unit) {
|
||||
// value(name.toName(), block)
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Generate a laminate representing default item set generated by this descriptor
|
||||
// */
|
||||
// override val defaultMeta: Laminate by lazy { Laminate(default, DescriptorMeta(this)) }
|
||||
//
|
||||
// /**
|
||||
// * Build a default [MetaItemNode] from this node descriptor
|
||||
// */
|
||||
// override val defaultValue: MetaItem get() = MetaItemNode(defaultMeta)
|
||||
//
|
||||
// override fun build(): NodeDescriptor = NodeDescriptorBuilder(config.copy())
|
||||
//
|
||||
// public companion object {
|
||||
//
|
||||
// internal val ITEM_KEY: Name = "item".asName()
|
||||
// internal val IS_NODE_KEY: Name = "@isNode".asName()
|
||||
//
|
||||
// //TODO infer descriptor from spec
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//public inline fun NodeDescriptor(block: NodeDescriptorBuilder.() -> Unit): NodeDescriptor =
|
||||
// NodeDescriptorBuilder().apply(block)
|
||||
//
|
||||
///**
|
||||
// * Merge two node descriptors into one using first one as primary
|
||||
// */
|
||||
//public operator fun NodeDescriptor.plus(other: NodeDescriptor): NodeDescriptor {
|
||||
// return NodeDescriptorBuilder().apply {
|
||||
// config.update(other.toMeta())
|
||||
// config.update(this@plus.toMeta())
|
||||
// }
|
||||
//}
|
@ -1,139 +1,139 @@
|
||||
package space.kscience.dataforge.meta.descriptors
|
||||
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.misc.DFBuilder
|
||||
import space.kscience.dataforge.values.*
|
||||
|
||||
|
||||
/**
|
||||
* A descriptor for meta value
|
||||
*
|
||||
* Descriptor can have non-atomic path. It is resolved when descriptor is added to the node
|
||||
*
|
||||
* @author Alexander Nozik
|
||||
*/
|
||||
@DFBuilder
|
||||
public sealed interface ValueDescriptor : ItemDescriptor {
|
||||
|
||||
/**
|
||||
* True if the value is required
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
override val required: Boolean
|
||||
|
||||
/**
|
||||
* The default for this value. Null if there is no default.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public val default: Value?
|
||||
|
||||
|
||||
/**
|
||||
* A list of allowed ValueTypes. Empty if any value type allowed
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public val type: List<ValueType>?
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public fun isAllowedValue(value: Value): Boolean =
|
||||
(type?.let { it.contains(ValueType.STRING) || it.contains(value.type) } ?: true)
|
||||
&& (allowedValues.isEmpty() || allowedValues.contains(value))
|
||||
|
||||
/**
|
||||
* A list of allowed values with descriptions. If empty than any value is
|
||||
* allowed.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public val allowedValues: List<Value>
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder fir [ValueDescriptor]
|
||||
*/
|
||||
@DFBuilder
|
||||
public class ValueDescriptorBuilder(
|
||||
config: ObservableMeta = ObservableMeta()
|
||||
) : ItemDescriptorBuilder(config), ValueDescriptor {
|
||||
|
||||
/**
|
||||
* True if the value is required
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
override var required: Boolean by config.boolean { default == null }
|
||||
|
||||
/**
|
||||
* The default for this value. Null if there is no default.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
override var default: Value? by config.value()
|
||||
|
||||
public fun default(v: Any) {
|
||||
this.default = Value.of(v)
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of allowed ValueTypes. Empty if any value type allowed
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
override var type: List<ValueType>? by config.listValue { ValueType.valueOf(it.string) }
|
||||
|
||||
public fun type(vararg t: ValueType) {
|
||||
this.type = listOf(*t)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
override fun isAllowedValue(value: Value): Boolean {
|
||||
return (type?.let { it.contains(ValueType.STRING) || it.contains(value.type) } ?: true)
|
||||
&& (allowedValues.isEmpty() || allowedValues.contains(value))
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of allowed values with descriptions. If empty than any value is
|
||||
* allowed.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
override var allowedValues: List<Value> by config.item().convert(
|
||||
reader = {
|
||||
val value = it.value
|
||||
when {
|
||||
value?.list != null -> value.list
|
||||
type?.let { type -> type.size == 1 && type[0] === ValueType.BOOLEAN } ?: false -> listOf(True, False)
|
||||
else -> emptyList()
|
||||
}
|
||||
},
|
||||
writer = {
|
||||
MetaItemValue(it.asValue())
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* Allow given list of value and forbid others
|
||||
*/
|
||||
public fun allow(vararg v: Any) {
|
||||
this.allowedValues = v.map { Value.of(it) }
|
||||
}
|
||||
|
||||
override val defaultValue: MetaItem? get() = default?.asMetaItem()
|
||||
|
||||
override fun build(): ValueDescriptor = ValueDescriptorBuilder(config.copy())
|
||||
}
|
||||
//package space.kscience.dataforge.meta.descriptors
|
||||
//
|
||||
//import space.kscience.dataforge.meta.*
|
||||
//import space.kscience.dataforge.misc.DFBuilder
|
||||
//import space.kscience.dataforge.values.*
|
||||
//
|
||||
//
|
||||
///**
|
||||
// * A descriptor for meta value
|
||||
// *
|
||||
// * Descriptor can have non-atomic path. It is resolved when descriptor is added to the node
|
||||
// *
|
||||
// * @author Alexander Nozik
|
||||
// */
|
||||
//@DFBuilder
|
||||
//public sealed interface ValueDescriptor : ItemDescriptor {
|
||||
//
|
||||
// /**
|
||||
// * True if the value is required
|
||||
// *
|
||||
// * @return
|
||||
// */
|
||||
// override val required: Boolean
|
||||
//
|
||||
// /**
|
||||
// * The default for this value. Null if there is no default.
|
||||
// *
|
||||
// * @return
|
||||
// */
|
||||
// public val default: Value?
|
||||
//
|
||||
//
|
||||
// /**
|
||||
// * A list of allowed ValueTypes. Empty if any value type allowed
|
||||
// *
|
||||
// * @return
|
||||
// */
|
||||
// public val type: List<ValueType>?
|
||||
//
|
||||
// /**
|
||||
// * 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
|
||||
// */
|
||||
// public fun isAllowedValue(value: Value): Boolean =
|
||||
// (type?.let { it.contains(ValueType.STRING) || it.contains(value.type) } ?: true)
|
||||
// && (allowedValues.isEmpty() || allowedValues.contains(value))
|
||||
//
|
||||
// /**
|
||||
// * A list of allowed values with descriptions. If empty than any value is
|
||||
// * allowed.
|
||||
// *
|
||||
// * @return
|
||||
// */
|
||||
// public val allowedValues: List<Value>
|
||||
//}
|
||||
//
|
||||
///**
|
||||
// * A builder fir [ValueDescriptor]
|
||||
// */
|
||||
//@DFBuilder
|
||||
//public class ValueDescriptorBuilder(
|
||||
// config: MutableMeta = MutableMeta()
|
||||
//) : ItemDescriptorBuilder(config), ValueDescriptor {
|
||||
//
|
||||
// /**
|
||||
// * True if the value is required
|
||||
// *
|
||||
// * @return
|
||||
// */
|
||||
// override var required: Boolean by config.boolean { default == null }
|
||||
//
|
||||
// /**
|
||||
// * The default for this value. Null if there is no default.
|
||||
// *
|
||||
// * @return
|
||||
// */
|
||||
// override var default: Value? by config.value()
|
||||
//
|
||||
// public fun default(v: Any) {
|
||||
// this.default = Value.of(v)
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * A list of allowed ValueTypes. Empty if any value type allowed
|
||||
// *
|
||||
// * @return
|
||||
// */
|
||||
// override var type: List<ValueType>? by config.listValue { ValueType.valueOf(it.string) }
|
||||
//
|
||||
// public fun type(vararg t: ValueType) {
|
||||
// this.type = listOf(*t)
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 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
|
||||
// */
|
||||
// override fun isAllowedValue(value: Value): Boolean {
|
||||
// return (type?.let { it.contains(ValueType.STRING) || it.contains(value.type) } ?: true)
|
||||
// && (allowedValues.isEmpty() || allowedValues.contains(value))
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * A list of allowed values with descriptions. If empty than any value is
|
||||
// * allowed.
|
||||
// *
|
||||
// * @return
|
||||
// */
|
||||
// override var allowedValues: List<Value> by config.item().convert(
|
||||
// reader = {
|
||||
// val value = it.value
|
||||
// when {
|
||||
// value?.list != null -> value.list
|
||||
// type?.let { type -> type.size == 1 && type[0] === ValueType.BOOLEAN } ?: false -> listOf(True, False)
|
||||
// else -> emptyList()
|
||||
// }
|
||||
// },
|
||||
// writer = {
|
||||
// MetaItemValue(it.asValue())
|
||||
// }
|
||||
// )
|
||||
//
|
||||
// /**
|
||||
// * Allow given list of value and forbid others
|
||||
// */
|
||||
// public fun allow(vararg v: Any) {
|
||||
// this.allowedValues = v.map { Value.of(it) }
|
||||
// }
|
||||
//
|
||||
// override val defaultValue: MetaItem? get() = default?.asMetaItem()
|
||||
//
|
||||
// override fun build(): ValueDescriptor = ValueDescriptorBuilder(config.copy())
|
||||
//}
|
||||
|
@ -4,10 +4,10 @@ import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.values.ValueType
|
||||
import space.kscience.dataforge.values.asValue
|
||||
|
||||
public inline fun <reified E : Enum<E>> NodeDescriptorBuilder.enum(
|
||||
public inline fun <reified E : Enum<E>> MetaDescriptorBuilder.enum(
|
||||
key: Name,
|
||||
default: E?,
|
||||
crossinline modifier: ValueDescriptor.() -> Unit = {},
|
||||
crossinline modifier: MetaDescriptor.() -> Unit = {},
|
||||
): Unit = value(key) {
|
||||
type(ValueType.STRING)
|
||||
default?.let {
|
||||
|
@ -1,6 +1,6 @@
|
||||
package space.kscience.dataforge.meta
|
||||
|
||||
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
|
||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.names.toName
|
||||
import space.kscience.dataforge.values.ListValue
|
||||
@ -9,7 +9,7 @@ import space.kscience.dataforge.values.Value
|
||||
/**
|
||||
* Convert meta to map of maps
|
||||
*/
|
||||
public fun Meta.toMap(descriptor: NodeDescriptor? = null): Map<String, Any?> {
|
||||
public fun Meta.toMap(descriptor: MetaDescriptor? = null): Map<String, Any?> {
|
||||
return items.entries.associate { (token, item) ->
|
||||
token.toString() to when (item) {
|
||||
is MetaItemNode -> item.node.toMap()
|
||||
@ -23,7 +23,7 @@ public fun Meta.toMap(descriptor: NodeDescriptor? = null): Map<String, Any?> {
|
||||
* All other values will be converted to values.
|
||||
*/
|
||||
@DFExperimental
|
||||
public fun Map<String, Any?>.toMeta(descriptor: NodeDescriptor? = null): Meta = Meta {
|
||||
public fun Map<String, Any?>.toMeta(descriptor: MetaDescriptor? = null): Meta = Meta {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun toItem(value: Any?): MetaItem = when (value) {
|
||||
is MetaItem -> value
|
||||
|
@ -21,12 +21,12 @@ public interface TransformationRule {
|
||||
* @return a sequence of item paths to be transformed
|
||||
*/
|
||||
public fun selectItems(meta: Meta): Sequence<Name> =
|
||||
meta.itemSequence().filter { matches(it.first, it.second) }.map { it.first }
|
||||
meta.nodeSequence().filter { matches(it.first, it.second) }.map { it.first }
|
||||
|
||||
/**
|
||||
* Apply transformation for a single item (Node or Value) to the target
|
||||
*/
|
||||
public fun <M : MutableMeta<M>> transformItem(name: Name, item: MetaItem?, target: M): Unit
|
||||
public fun transformItem(name: Name, item: MetaItem?, target: MutableTypedMeta): Unit
|
||||
}
|
||||
|
||||
/**
|
||||
@ -39,9 +39,9 @@ public data class KeepTransformationRule(val selector: (Name) -> Boolean) :
|
||||
}
|
||||
|
||||
override fun selectItems(meta: Meta): Sequence<Name> =
|
||||
meta.itemSequence().map { it.first }.filter(selector)
|
||||
meta.nodeSequence().map { it.first }.filter(selector)
|
||||
|
||||
override fun <M : MutableMeta<M>> transformItem(name: Name, item: MetaItem?, target: M) {
|
||||
override fun transformItem(name: Name, item: MetaItem?, target: MutableTypedMeta) {
|
||||
if (selector(name)) target.set(name, item)
|
||||
}
|
||||
}
|
||||
@ -51,7 +51,7 @@ public data class KeepTransformationRule(val selector: (Name) -> Boolean) :
|
||||
*/
|
||||
public data class SingleItemTransformationRule(
|
||||
val from: Name,
|
||||
val transform: MutableMeta<*>.(Name, MetaItem?) -> Unit,
|
||||
val transform: MutableTypedMeta.(Name, MetaItem?) -> Unit,
|
||||
) : TransformationRule {
|
||||
override fun matches(name: Name, item: MetaItem?): Boolean {
|
||||
return name == from
|
||||
@ -59,7 +59,7 @@ public data class SingleItemTransformationRule(
|
||||
|
||||
override fun selectItems(meta: Meta): Sequence<Name> = sequenceOf(from)
|
||||
|
||||
override fun <M : MutableMeta<M>> transformItem(name: Name, item: MetaItem?, target: M) {
|
||||
override fun transformItem(name: Name, item: MetaItem?, target: MutableTypedMeta) {
|
||||
if (name == this.from) {
|
||||
target.transform(name, item)
|
||||
}
|
||||
@ -68,13 +68,13 @@ public data class SingleItemTransformationRule(
|
||||
|
||||
public data class RegexItemTransformationRule(
|
||||
val from: Regex,
|
||||
val transform: MutableMeta<*>.(name: Name, MatchResult, MetaItem?) -> Unit,
|
||||
val transform: MutableTypedMeta.(name: Name, MatchResult, MetaItem?) -> Unit,
|
||||
) : TransformationRule {
|
||||
override fun matches(name: Name, item: MetaItem?): Boolean {
|
||||
return from.matches(name.toString())
|
||||
}
|
||||
|
||||
override fun <M : MutableMeta<M>> transformItem(name: Name, item: MetaItem?, target: M) {
|
||||
override fun transformItem(name: Name, item: MetaItem?, target: MutableTypedMeta) {
|
||||
val match = from.matchEntire(name.toString())
|
||||
if (match != null) {
|
||||
target.transform(name, match, item)
|
||||
@ -105,7 +105,7 @@ public value class MetaTransformation(private val transformations: Collection<Tr
|
||||
* Generate an observable configuration that contains only elements defined by transformation rules and changes with the source
|
||||
*/
|
||||
@DFExperimental
|
||||
public fun generate(source: ObservableMeta): ObservableItemProvider = ObservableMeta().apply {
|
||||
public fun generate(source: MutableMeta): ObservableMeta = MutableMeta().apply {
|
||||
transformations.forEach { rule ->
|
||||
rule.selectItems(source).forEach { name ->
|
||||
rule.transformItem(name, source[name], this)
|
||||
@ -131,7 +131,7 @@ public value class MetaTransformation(private val transformations: Collection<Tr
|
||||
/**
|
||||
* Listens for changes in the source node and translates them into second node if transformation set contains a corresponding rule.
|
||||
*/
|
||||
public fun <M : MutableMeta<M>> bind(source: ObservableMeta, target: M) {
|
||||
public fun bind(source: ObservableMeta, target: MutableTypedMeta) {
|
||||
source.onChange(target) { name, _, newItem ->
|
||||
transformations.forEach { t ->
|
||||
if (t.matches(name, newItem)) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
package space.kscience.dataforge.values
|
||||
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.MetaBuilder
|
||||
import space.kscience.dataforge.meta.MutableMeta
|
||||
|
||||
/**
|
||||
* Check if value is null
|
||||
@ -35,4 +35,4 @@ public val Value.doubleArray: DoubleArray
|
||||
}
|
||||
|
||||
|
||||
public fun Value.toMeta(): MetaBuilder = Meta { Meta.VALUE_KEY put this }
|
||||
public fun Value.toMeta(): MutableMeta = Meta { Meta.VALUE_KEY put this }
|
@ -6,7 +6,7 @@ import kotlin.test.assertEquals
|
||||
class ConfigTest {
|
||||
@Test
|
||||
fun testIndexedWrite(){
|
||||
val config = MetaBuilder()
|
||||
val config = MutableMeta()
|
||||
config["a[1].b"] = 1
|
||||
assertEquals(null, config["a.b"].int)
|
||||
assertEquals(1, config["a[1].b"].int)
|
||||
|
@ -14,7 +14,7 @@ class MutableMetaTest{
|
||||
"b" put 22
|
||||
"c" put "StringValue"
|
||||
}
|
||||
}.asObservable()
|
||||
}
|
||||
|
||||
meta.remove("aNode.c")
|
||||
assertEquals(meta["aNode.c"], null)
|
||||
|
@ -8,7 +8,7 @@ import kotlin.test.assertEquals
|
||||
class SchemeTest {
|
||||
@Test
|
||||
fun testSchemeWrappingBeforeEdit() {
|
||||
val config = MetaBuilder()
|
||||
val config = MutableMeta()
|
||||
val scheme = TestScheme.write(config)
|
||||
scheme.a = 29
|
||||
assertEquals(29, config["a"].int)
|
||||
@ -18,7 +18,7 @@ class SchemeTest {
|
||||
fun testSchemeWrappingAfterEdit() {
|
||||
val scheme = TestScheme.empty()
|
||||
scheme.a = 29
|
||||
val config = MetaBuilder()
|
||||
val config = MutableMeta()
|
||||
scheme.retarget(config)
|
||||
assertEquals(29, scheme.a)
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ class SpecificationTest {
|
||||
|
||||
@Test
|
||||
fun testChildModification() {
|
||||
val config = MetaBuilder()
|
||||
val config = MutableMeta()
|
||||
val child = config.getChild("child")
|
||||
val scheme = TestScheme.write(child)
|
||||
scheme.a = 22
|
||||
@ -60,7 +60,7 @@ class SpecificationTest {
|
||||
|
||||
@Test
|
||||
fun testChildUpdate() {
|
||||
val config = MetaBuilder()
|
||||
val config = MutableMeta()
|
||||
val child = config.getChild("child")
|
||||
child.update(TestScheme) {
|
||||
a = 22
|
||||
|
@ -38,7 +38,7 @@ public fun Meta.toDynamic(): dynamic {
|
||||
return res
|
||||
}
|
||||
|
||||
public class DynamicMeta(internal val obj: dynamic) : MetaBase() {
|
||||
public class DynamicMeta(internal val obj: dynamic) : AbstractTypedMeta() {
|
||||
private fun keys(): Array<String> = js("Object").keys(obj)
|
||||
|
||||
private fun isArray(@Suppress("UNUSED_PARAMETER") obj: dynamic): Boolean =
|
||||
|
@ -2,7 +2,7 @@ package space.kscience.dataforge.workspace
|
||||
|
||||
import space.kscience.dataforge.context.ContextAware
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.MetaBuilder
|
||||
import space.kscience.dataforge.meta.MutableMeta
|
||||
import space.kscience.dataforge.misc.Type
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.toName
|
||||
@ -55,5 +55,5 @@ public suspend fun Workspace.produce(task: String, target: String): TaskResult<*
|
||||
public suspend fun Workspace.produce(task: String, meta: Meta): TaskResult<*> =
|
||||
produce(task.toName(), meta)
|
||||
|
||||
public suspend fun Workspace.produce(task: String, block: MetaBuilder.() -> Unit = {}): TaskResult<*> =
|
||||
public suspend fun Workspace.produce(task: String, block: MutableMeta.() -> Unit = {}): TaskResult<*> =
|
||||
produce(task, Meta(block))
|
||||
|
@ -8,7 +8,7 @@ import space.kscience.dataforge.data.DataSet
|
||||
import space.kscience.dataforge.data.DataSetBuilder
|
||||
import space.kscience.dataforge.data.DataTree
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.MetaBuilder
|
||||
import space.kscience.dataforge.meta.MutableMeta
|
||||
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
|
||||
import space.kscience.dataforge.misc.DFBuilder
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
@ -87,8 +87,8 @@ public class WorkspaceBuilder(private val parentContext: Context = Global) : Tas
|
||||
/**
|
||||
* Define a new target with a builder
|
||||
*/
|
||||
public inline fun WorkspaceBuilder.target(name: String, metaBuilder: MetaBuilder.() -> Unit): Unit =
|
||||
target(name, Meta(metaBuilder))
|
||||
public inline fun WorkspaceBuilder.target(name: String, mutableMeta: MutableMeta.() -> Unit): Unit =
|
||||
target(name, Meta(mutableMeta))
|
||||
|
||||
@DFBuilder
|
||||
public fun Workspace(parentContext: Context = Global, builder: WorkspaceBuilder.() -> Unit): Workspace =
|
||||
|
@ -28,7 +28,7 @@ public inline fun <reified P : Plugin> P.toFactory(): PluginFactory<P> = object
|
||||
override val type: KClass<out P> = P::class
|
||||
}
|
||||
|
||||
public fun Workspace.runBlocking(task: String, block: MetaBuilder.() -> Unit = {}): DataSet<Any> = runBlocking {
|
||||
public fun Workspace.runBlocking(task: String, block: MutableMeta.() -> Unit = {}): DataSet<Any> = runBlocking {
|
||||
produce(task, block)
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ pluginManagement {
|
||||
gradlePluginPortal()
|
||||
}
|
||||
|
||||
val toolsVersion = "0.10.0"
|
||||
val toolsVersion = "0.10.2"
|
||||
|
||||
plugins {
|
||||
id("ru.mipt.npm.gradle.project") version toolsVersion
|
||||
|
Loading…
Reference in New Issue
Block a user