WIP full refactor of Meta

This commit is contained in:
Alexander Nozik 2021-07-24 19:58:19 +03:00
parent 679175391a
commit 9f5b010847
55 changed files with 1608 additions and 1845 deletions

View File

@ -4,7 +4,7 @@ plugins {
allprojects { allprojects {
group = "space.kscience" group = "space.kscience"
version = "0.5.0-dev-2" version = "0.5.0-dev-3"
} }
subprojects { subprojects {
@ -15,10 +15,6 @@ readme {
readmeTemplate = file("docs/templates/README-TEMPLATE.md") readmeTemplate = file("docs/templates/README-TEMPLATE.md")
} }
changelog{
version = project.version.toString()
}
ksciencePublish { ksciencePublish {
github("dataforge-core") github("dataforge-core")
space("https://maven.pkg.jetbrains.space/mipt-npm/p/sci/maven") space("https://maven.pkg.jetbrains.space/mipt-npm/p/sci/maven")

View File

@ -3,10 +3,7 @@ package space.kscience.dataforge.context
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import space.kscience.dataforge.meta.Laminate import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaRepr
import space.kscience.dataforge.meta.itemSequence
import space.kscience.dataforge.misc.Named import space.kscience.dataforge.misc.Named
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.provider.Provider 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> { public fun content(target: String, inherit: Boolean): Map<Name, Any> {
return if (inherit) { return if (inherit) {
when (target) { when (target) {
PROPERTY_TARGET -> properties.itemSequence().toMap() PROPERTY_TARGET -> properties.nodeSequence().toMap()
Plugin.TARGET -> plugins.list(true).associateBy { it.name } Plugin.TARGET -> plugins.list(true).associateBy { it.name }
else -> emptyMap() else -> emptyMap()
} }
} else { } else {
when (target) { 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 } Plugin.TARGET -> plugins.list(false).associateBy { it.name }
else -> emptyMap() else -> emptyMap()
} }
@ -99,7 +96,7 @@ public open class Context internal constructor(
override fun toMeta(): Meta = Meta { override fun toMeta(): Meta = Meta {
"parent" to parent?.name "parent" to parent?.name
"properties" put properties.layers.firstOrNull() "properties" put properties.layers.firstOrNull()
"plugins" put plugins.map { it.toMeta() } "plugins" put plugins.map { it.toMeta().asMetaItem() }
} }
public companion object { public companion object {

View File

@ -1,7 +1,7 @@
package space.kscience.dataforge.context package space.kscience.dataforge.context
import space.kscience.dataforge.meta.Meta 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.seal
import space.kscience.dataforge.meta.toMutableMeta import space.kscience.dataforge.meta.toMutableMeta
import space.kscience.dataforge.misc.DFBuilder import space.kscience.dataforge.misc.DFBuilder
@ -25,7 +25,7 @@ public class ContextBuilder internal constructor(
internal val factories = HashMap<PluginFactory<*>, Meta>() internal val factories = HashMap<PluginFactory<*>, Meta>()
internal var meta = meta.toMutableMeta() internal var meta = meta.toMutableMeta()
public fun properties(action: MetaBuilder.() -> Unit) { public fun properties(action: MutableMeta.() -> Unit) {
meta.action() meta.action()
} }
@ -38,20 +38,20 @@ public class ContextBuilder internal constructor(
parent.gatherInSequence<PluginFactory<*>>(PluginFactory.TYPE).values parent.gatherInSequence<PluginFactory<*>>(PluginFactory.TYPE).values
.find { it.tag.matches(tag) } ?: error("Can't resolve plugin factory for $tag") .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) val factory = findPluginFactory(tag)
factories[factory] = Meta(metaBuilder) factories[factory] = Meta(mutableMeta)
} }
public fun plugin(factory: PluginFactory<*>, meta: Meta) { public fun plugin(factory: PluginFactory<*>, meta: Meta) {
factories[factory] = meta factories[factory] = meta
} }
public fun plugin(factory: PluginFactory<*>, metaBuilder: MetaBuilder.() -> Unit = {}) { public fun plugin(factory: PluginFactory<*>, mutableMeta: MutableMeta.() -> Unit = {}) {
factories[factory] = Meta(metaBuilder) 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) plugin(PluginTag(name, group, version), action)
} }

View File

@ -1,18 +1,17 @@
package space.kscience.dataforge.properties 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.get
import space.kscience.dataforge.meta.set
import space.kscience.dataforge.meta.transformations.MetaConverter import space.kscience.dataforge.meta.transformations.MetaConverter
import space.kscience.dataforge.meta.transformations.nullableItemToObject import space.kscience.dataforge.meta.transformations.nullableItemToObject
import space.kscience.dataforge.meta.transformations.nullableObjectToMetaItem import space.kscience.dataforge.meta.transformations.nullableObjectToMetaItem
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.startsWith
@DFExperimental @DFExperimental
public class MetaProperty<T : Any>( public class MetaProperty<T : Any>(
public val meta: ObservableMeta, public val meta: MutableMeta,
public val name: Name, public val name: Name,
public val converter: MetaConverter<T>, public val converter: MetaConverter<T>,
) : Property<T?> { ) : Property<T?> {

View File

@ -1,14 +1,13 @@
package space.kscience.dataforge.properties 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.misc.DFExperimental
import space.kscience.dataforge.names.startsWith
import space.kscience.dataforge.names.toName import space.kscience.dataforge.names.toName
import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KMutableProperty1
@DFExperimental @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?> { object : Property<T?> {
override var value: T? override var value: T?
get() = property.get(this@property) get() = property.get(this@property)

View File

@ -33,11 +33,7 @@ public value class Path(public val tokens: List<PathToken>) : Iterable<PathToken
public companion object { public companion object {
public const val PATH_SEGMENT_SEPARATOR: String = "/" public const val PATH_SEGMENT_SEPARATOR: String = "/"
public fun parse(path: String): Path { public fun parse(path: String): Path = Path(path.split(PATH_SEGMENT_SEPARATOR).map { PathToken.parse(it) })
val head = path.substringBefore(PATH_SEGMENT_SEPARATOR)
val tail = path.substringAfter(PATH_SEGMENT_SEPARATOR)
return PathToken.parse(head).asPath() + parse(tail)
}
} }
} }

View File

@ -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)
}
}

View File

@ -6,7 +6,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import space.kscience.dataforge.data.* import space.kscience.dataforge.data.*
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaBuilder import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.seal import space.kscience.dataforge.meta.seal
import space.kscience.dataforge.meta.toMutableMeta import space.kscience.dataforge.meta.toMutableMeta
import space.kscience.dataforge.misc.DFBuilder import space.kscience.dataforge.misc.DFBuilder
@ -29,7 +29,7 @@ public data class ActionEnv(
* Action environment * Action environment
*/ */
@DFBuilder @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 public lateinit var result: suspend ActionEnv.(T) -> R
/** /**

View File

@ -6,7 +6,7 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.fold import kotlinx.coroutines.flow.fold
import space.kscience.dataforge.data.* import space.kscience.dataforge.data.*
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaBuilder import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.misc.DFBuilder import space.kscience.dataforge.misc.DFBuilder
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.misc.DFInternal 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 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 public lateinit var result: suspend ActionEnv.(Map<Name, T>) -> R

View File

@ -7,7 +7,7 @@ import kotlinx.coroutines.launch
import space.kscience.dataforge.data.* import space.kscience.dataforge.data.*
import space.kscience.dataforge.meta.Laminate import space.kscience.dataforge.meta.Laminate
import space.kscience.dataforge.meta.Meta 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.meta.toMutableMeta
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.misc.DFInternal 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 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 lateinit var result: suspend (T) -> R
public fun result(f: suspend (T) -> R) { public fun result(f: suspend (T) -> R) {

View File

@ -4,7 +4,7 @@ import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import space.kscience.dataforge.meta.Meta 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.misc.DFExperimental
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.plus 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( public suspend inline fun <reified T : Any> DataSetBuilder<T>.static(
name: String, name: String,
data: T, data: T,
metaBuilder: MetaBuilder.() -> Unit, mutableMeta: MutableMeta.() -> Unit,
): Unit = emit(name.toName(), Data.static(data, Meta(metaBuilder))) ): Unit = emit(name.toName(), Data.static(data, Meta(mutableMeta)))
/** /**
* Update data with given node data and meta with node meta. * Update data with given node data and meta with node meta.

View File

@ -1,7 +1,7 @@
package space.kscience.dataforge.data package space.kscience.dataforge.data
import space.kscience.dataforge.meta.Meta 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] * 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))

View File

@ -2,7 +2,7 @@ package space.kscience.dataforge.data
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import space.kscience.dataforge.meta.Meta 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.seal
import space.kscience.dataforge.meta.toMutableMeta import space.kscience.dataforge.meta.toMutableMeta
import space.kscience.dataforge.misc.DFInternal 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( public suspend fun <T : Any, R : Any> DataSet<T>.map(
outputType: KType, outputType: KType,
coroutineContext: CoroutineContext = EmptyCoroutineContext, coroutineContext: CoroutineContext = EmptyCoroutineContext,
metaTransform: MetaBuilder.() -> Unit = {}, metaTransform: MutableMeta.() -> Unit = {},
block: suspend (T) -> R, block: suspend (T) -> R,
): DataTree<R> = DataTree<R>(outputType) { ): DataTree<R> = DataTree<R>(outputType) {
populate( populate(
@ -156,7 +156,7 @@ public suspend fun <T : Any, R : Any> DataSet<T>.map(
@OptIn(DFInternal::class) @OptIn(DFInternal::class)
public suspend inline fun <T : Any, reified R : Any> DataSet<T>.map( public suspend inline fun <T : Any, reified R : Any> DataSet<T>.map(
coroutineContext: CoroutineContext = EmptyCoroutineContext, coroutineContext: CoroutineContext = EmptyCoroutineContext,
noinline metaTransform: MetaBuilder.() -> Unit = {}, noinline metaTransform: MutableMeta.() -> Unit = {},
noinline block: suspend (T) -> R, noinline block: suspend (T) -> R,
): DataTree<R> = map(typeOf<R>(), coroutineContext, metaTransform, block) ): DataTree<R> = map(typeOf<R>(), coroutineContext, metaTransform, block)

View File

@ -34,7 +34,7 @@ public fun Meta.toYaml(): YamlMap {
return YamlMap(map) 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> { private fun buildItems(): Map<NameToken, MetaItem> {
val map = LinkedHashMap<NameToken, MetaItem>() val map = LinkedHashMap<NameToken, MetaItem>()

View File

@ -100,7 +100,7 @@ public object BinaryMetaFormat : MetaFormat, MetaFormatFactory {
} }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
public fun Input.readMetaItem(): TypedMetaItem<MetaBuilder> { public fun Input.readMetaItem(): TypedMetaItem<MutableMeta> {
return when (val keyChar = readByte().toInt().toChar()) { return when (val keyChar = readByte().toInt().toChar()) {
'S' -> MetaItemValue(StringValue(readString())) 'S' -> MetaItemValue(StringValue(readString()))
'N' -> MetaItemValue(Null) 'N' -> MetaItemValue(Null)

View File

@ -4,7 +4,7 @@ import io.ktor.utils.io.core.Output
import space.kscience.dataforge.meta.* import space.kscience.dataforge.meta.*
public class EnvelopeBuilder : Envelope { public class EnvelopeBuilder : Envelope {
private val metaBuilder = MetaBuilder() private val metaBuilder = MutableMeta()
override var data: Binary? = null override var data: Binary? = null
override var meta: Meta override var meta: Meta
@ -13,7 +13,7 @@ public class EnvelopeBuilder : Envelope {
metaBuilder.update(value) metaBuilder.update(value)
} }
public fun meta(block: MetaBuilder.() -> Unit) { public fun meta(block: MutableMeta.() -> Unit) {
metaBuilder.apply(block) metaBuilder.apply(block)
} }

View File

@ -11,9 +11,8 @@ import space.kscience.dataforge.context.Context
import space.kscience.dataforge.io.IOFormat.Companion.NAME_KEY import space.kscience.dataforge.io.IOFormat.Companion.NAME_KEY
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.descriptors.NodeDescriptor import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.meta.node
import space.kscience.dataforge.meta.toJson 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.KType
import kotlin.reflect.typeOf 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 { override fun readMeta(input: Input, descriptor: NodeDescriptor?): Meta {
val str = input.readUtf8String()//readByteArray().decodeToString() val str = input.readUtf8String()//readByteArray().decodeToString()
val jsonElement = json.parseToJsonElement(str) val jsonElement = json.parseToJsonElement(str)
val item = jsonElement.toMetaItem(descriptor) val item = jsonElement.toMeta(descriptor)
return item.node ?: Meta.EMPTY return item.node ?: Meta.EMPTY
} }

View File

@ -4,7 +4,6 @@ import kotlinx.serialization.json.*
import space.kscience.dataforge.meta.* import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.JsonMeta.Companion.JSON_ARRAY_KEY import space.kscience.dataforge.meta.JsonMeta.Companion.JSON_ARRAY_KEY
import space.kscience.dataforge.values.ListValue import space.kscience.dataforge.values.ListValue
import space.kscience.dataforge.values.number
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -72,7 +71,7 @@ class MetaFormatTest {
add(JsonPrimitive(3.0)) 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(true, meta["$JSON_ARRAY_KEY[0].$JSON_ARRAY_KEY[1].d"].boolean)
assertEquals("value", meta["$JSON_ARRAY_KEY[1]"].string) assertEquals("value", meta["$JSON_ARRAY_KEY[1]"].string)
@ -98,7 +97,7 @@ class MetaFormatTest {
} }
""".trimIndent() """.trimIndent()
val json = Json.parseToJsonElement(jsonString) val json = Json.parseToJsonElement(jsonString)
val meta = json.toMetaItem().node!! val meta = json.toMeta().node!!
assertEquals(ListValue.EMPTY, meta["comments"].value) assertEquals(ListValue.EMPTY, meta["comments"].value)
} }

View File

@ -1,8 +1,6 @@
package space.kscience.dataforge.meta package space.kscience.dataforge.meta
import space.kscience.dataforge.misc.DFBuilder import space.kscience.dataforge.misc.DFBuilder
import space.kscience.dataforge.names.Name
import kotlin.properties.ReadWriteProperty
/** /**
* A container that holds a [ObservableMeta]. * A container that holds a [ObservableMeta].
@ -11,15 +9,11 @@ public interface Configurable {
/** /**
* Backing config * Backing config
*/ */
public val config: ObservableMeta public val config: MutableMeta
} }
public fun <T : Configurable> T.configure(meta: Meta): T = this.apply { config.update(meta) } public fun <T : Configurable> T.configure(meta: Meta): T = this.apply { config.update(meta) }
@DFBuilder @DFBuilder
public inline fun <T : Configurable> T.configure(action: ObservableMeta.() -> Unit): T = apply { config.apply(action) } public inline fun <T : Configurable> T.configure(action: MutableMeta.() -> Unit): T = apply { config.apply(action) }
/* Node delegates */
public fun Configurable.config(key: Name? = null): ReadWriteProperty<Any?, ObservableMeta?> = config.node(key)

View File

@ -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)

View File

@ -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())

View File

@ -3,20 +3,15 @@
package space.kscience.dataforge.meta package space.kscience.dataforge.meta
import kotlinx.serialization.json.* import kotlinx.serialization.json.*
import space.kscience.dataforge.meta.JsonMeta.Companion.JSON_ARRAY_KEY import space.kscience.dataforge.meta.descriptors.MetaDescriptor
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.names.NameToken import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.withIndex
import space.kscience.dataforge.values.* import space.kscience.dataforge.values.*
/** /**
* @param descriptor reserved for custom serialization in future * @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.NUMBER -> JsonPrimitive(numberOrNull)
ValueType.STRING -> JsonPrimitive(string) ValueType.STRING -> JsonPrimitive(string)
ValueType.BOOLEAN -> JsonPrimitive(boolean) 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 //Use these methods to customize JSON key mapping
@Suppress("NULLABLE_EXTENSION_OPERATOR_WITH_SAFE_CALL_RECEIVER") @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) private fun Meta.toJsonWithIndex(descriptor: MetaDescriptor?, index: String?): JsonElement = if (items.isEmpty()) {
value?.toJson(descriptor) ?: JsonNull
/** } else {
* Convert given [Meta] to [JsonObject]. Primitives and nodes are copied as is, same name siblings are treated as json arrays val pairs: MutableList<Pair<String, JsonElement>> = items.entries.groupBy {
*/ it.key.body
private fun Meta.toJsonWithIndex(descriptor: NodeDescriptor?, indexValue: String?): JsonObject { }.mapTo(ArrayList()) { (body, list) ->
val childDescriptor = descriptor?.children?.get(body)
val elementMap = HashMap<String, JsonElement>() if (list.size == 1) {
val (token, element) = list.first()
fun MetaItem.toJsonElement(itemDescriptor: ItemDescriptor?, index: String?): JsonElement = when (this) { val child: JsonElement = element.toJsonWithIndex(childDescriptor, token.index)
is MetaItemValue -> { body to child
value.toJson(itemDescriptor as? ValueDescriptor) } else {
} val elements: List<JsonElement> = list.sortedBy { it.key.index }.mapIndexed { index, entry ->
is MetaItemNode -> { //Use index if it is not equal to the item order
node.toJsonWithIndex(itemDescriptor as? NodeDescriptor, index) 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) { //Add index if needed
val itemDescriptor = descriptor?.items?.get(key) if (index != null) {
val jsonKey = key.toJsonKey(itemDescriptor) pairs += Meta.INDEX_KEY to JsonPrimitive(index)
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
}
}
} }
((descriptor?.items?.keys ?: emptySet()) + items.keys.map { it.body }).forEach(::addElement) //Add value if needed
if (value != null) {
pairs += Meta.VALUE_KEY to value!!.toJson(null)
if (indexValue != null) {
val indexKey = descriptor?.indexKey ?: DEFAULT_INDEX_KEY
elementMap[indexKey] = JsonPrimitive(indexValue)
} }
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) { return when (this) {
JsonNull -> Null JsonNull -> Null
else -> { else -> {
@ -105,79 +74,47 @@ public fun JsonPrimitive.toValue(descriptor: ValueDescriptor?): Value {
} }
} }
public fun JsonElement.toMetaItem(descriptor: ItemDescriptor? = null): TypedMetaItem<JsonMeta> = when (this) { public fun JsonElement.toMeta(descriptor: MetaDescriptor? = null): TypedMeta<JsonMeta> = JsonMeta(this, descriptor)
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)
}
}
}
/** /**
* A meta wrapping json object * 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>> { private val indexName by lazy { descriptor?.indexKey ?: Meta.INDEX_KEY }
val map = LinkedHashMap<NameToken, TypedMetaItem<JsonMeta>>()
json.forEach { (jsonKey, value) -> override val value: Value? by lazy {
val key = NameToken(jsonKey) when (json) {
val itemDescriptor = descriptor?.items?.get(jsonKey) is JsonPrimitive -> json.toValue(descriptor)
when (value) { is JsonObject -> json[Meta.VALUE_KEY]?.let { JsonMeta(it).value }
is JsonPrimitive -> { is JsonArray -> if (json.all { it is JsonPrimitive }) {
map[key] = MetaItemValue(value.toValue(itemDescriptor as? ValueDescriptor)) //convert array of primitives to ListValue
} json.map { (it as JsonPrimitive).toValue(descriptor) }.asValue()
is JsonObject -> { } else {
map[key] = MetaItemNode( null
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)
}
} }
} }
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 { public companion object {
/** /**

View File

@ -2,12 +2,15 @@ package space.kscience.dataforge.meta
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken 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]. * 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. * 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 { public val layers: List<Meta> = layers.flatMap {
if (it is Laminate) { 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.map { it.items.keys }.flatten().associateWith { key ->
layers.asSequence().map { it.items[key] }.filterNotNull().let(replaceRule) 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 -> val items = layers.map { it.items.keys }.flatten().associateWith { key ->
layers.asSequence().map { it.items[key] }.filterNotNull().merge() layers.asSequence().map { it.items[key] }.filterNotNull().merge()
} }
return SealedMeta(items) return SealedMeta(value, items)
} }
public companion object { public companion object {
@ -40,33 +43,21 @@ public class Laminate(layers: List<Meta>) : MetaBase() {
* *
* TODO add picture * 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> { private fun Sequence<Meta>.merge(): SealedMeta {
return when { val value = firstNotNullOfOrNull { it.value }
all { it is MetaItemValue } -> //If all items are values, take first //list nodes in item
first().seal() val nodes = toList()
all { it is MetaItemNode } -> { //represent as key->value entries
//list nodes in item val entries = nodes.flatMap { it.items.entries.asSequence() }
val nodes = map { (it as MetaItemNode).node } //group by keys
//represent as key->value entries val groups = entries.groupBy { it.key }
val entries = nodes.flatMap { it.items.entries.asSequence() } // recursively apply the rule
//group by keys val items = groups.mapValues { entry ->
val groups = entries.groupBy { it.key } entry.value.asSequence().map { it.value }.merge()
// 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()
} }
return SealedMeta(value,items)
} }
@ -74,7 +65,7 @@ public class Laminate(layers: List<Meta>) : MetaBase() {
* The values a replaced but meta children are joined * The values a replaced but meta children are joined
* TODO add picture * 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 * Performance optimized version of get method
*/ */
public fun Laminate.getFirst(name: Name): MetaItem? { public fun Laminate.getFirst(name: Name): Meta? {
layers.forEach { layer -> layers.forEach { layer ->
layer[name]?.let { return it } layer[name]?.let { return it }
} }

View File

@ -1,51 +1,37 @@
package space.kscience.dataforge.meta package space.kscience.dataforge.meta
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import space.kscience.dataforge.misc.Type
import space.kscience.dataforge.names.* 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. * 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. * Meaning that two states with the same meta are equal.
*/ */
@Serializable(MetaSerializer::class)
public interface MetaRepr { public interface MetaRepr {
public fun toMeta(): Meta public fun toMeta(): Meta
} }
/** /**
* Generic meta tree representation. Elements are [TypedMetaItem] objects that could be represented by three different entities: * A meta node
* * [MetaItemValue] (leaf) * TODO add documentation
* * [MetaItemNode] single node * Same name siblings are supported via elements with the same [Name] but different indices.
*
* * Same name siblings are supported via elements with the same [Name] but different queries
*/ */
@Type(Meta.TYPE)
@Serializable(MetaSerializer::class) @Serializable(MetaSerializer::class)
public interface Meta : MetaRepr, ItemProvider { public interface Meta : MetaRepr {
/** public val value: Value?
* Top level items of meta tree public val items: Map<NameToken, Meta>
*/
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)
}
}
}
override fun toMeta(): Meta = this override fun toMeta(): Meta = this
override fun equals(other: Any?): Boolean
override fun hashCode(): Int
override fun toString(): String override fun toString(): String
override fun equals(other: Any?): Boolean
override fun hashCode(): Int
public companion object { public companion object {
public const val TYPE: String = "meta" public const val TYPE: String = "meta"
@ -54,43 +40,179 @@ public interface Meta : MetaRepr, ItemProvider {
* A key for single value node * A key for single value node
*/ */
public const val VALUE_KEY: String = "@value" 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 fun hashCode(meta: Meta): Int {
var result = meta.value?.hashCode() ?: 0
public val EMPTY: Meta = object : MetaBase() { result = 31 * result + meta.items.hashCode()
override val items: Map<NameToken, MetaItem> = emptyMap() 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>> { public tailrec operator fun Meta.get(name: Name): Meta? = if (name.isEmpty()) {
return items.asSequence().flatMap { (key, item) -> this
when (item) { } else {
is MetaItemValue -> sequenceOf(key.asName() to item.value) get(name.firstOrNull()!!)?.get(name.cutFirst())
is MetaItemNode -> item.node.valueSequence().map { pair -> (key.asName() + pair.first) to pair.second } }
/**
* 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) -> items.forEach { (key, item) ->
yield(key.asName() to item) yield(key.asName() to item)
if (item is MetaItemNode) { yieldAll(item.nodeSequence().map { (innerKey, innerItem) ->
yieldAll(item.node.itemSequence().map { (innerKey, innerItem) -> (key + innerKey) to 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)
}

View File

@ -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)

View File

@ -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)

View File

@ -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
}

View File

@ -1,80 +1,46 @@
package space.kscience.dataforge.meta package space.kscience.dataforge.meta
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.InternalSerializationApi
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.MapSerializer import kotlinx.serialization.builtins.MapSerializer
import kotlinx.serialization.descriptors.* import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.* import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.json.JsonDecoder import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.JsonEncoder import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import space.kscience.dataforge.names.NameToken import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.NameTokenSerializer 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 * Serialized for meta
*/ */
public object MetaSerializer : KSerializer<Meta> { public object MetaSerializer : KSerializer<Meta> {
private val mapSerializer: KSerializer<Map<NameToken, TypedMetaItem<Meta>>> = MapSerializer( private val itemsSerializer: KSerializer<Map<NameToken, Meta>> = MapSerializer(
NameTokenSerializer, 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 { override fun deserialize(decoder: Decoder): Meta = JsonElement.serializer().deserialize(decoder).toMeta()
return if (decoder is JsonDecoder) {
JsonObject.serializer().deserialize(decoder).toMeta()
} else {
object : MetaBase() {
override val items: Map<NameToken, MetaItem> = mapSerializer.deserialize(decoder)
}
}
}
override fun serialize(encoder: Encoder, value: Meta) { override fun serialize(encoder: Encoder, value: Meta) {
if (encoder is JsonEncoder) { JsonElement.serializer().serialize(encoder, value.toJson())
JsonObject.serializer().serialize(encoder, value.toJson()) }
} else { }
mapSerializer.serialize(encoder, value.items)
} /**
* 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)
} }
} }

View File

@ -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)
}

View File

@ -1,74 +1,352 @@
package space.kscience.dataforge.meta 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.misc.DFExperimental
import space.kscience.dataforge.names.* 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. * Mutable variant of [Meta]
* * TODO documentation
* Changes in Meta are not thread safe.
*/ */
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) { * Get existing node or create a new one
children.remove(key) */
} else { public fun getOrCreate(name: Name): MutableMeta
children[key] = newItem
//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) { override fun attach(name: Name, node: ObservableMutableMeta) {
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?) {
when (name.length) { 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 -> { 1 -> {
val token = name.firstOrNull()!! val key = name.firstOrNull()!!
val oldItem: TypedMetaItem<M>? = getItem(name) children[key] = node
replaceItem(token, oldItem, wrapItem(item)) adoptBy(this, key)
} changed(name)
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)
} }
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 * 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" } require(!name.isEmpty()) { "Name could not be empty for append operation" }
val newIndex = name.lastOrNull()!!.index val newIndex = name.lastOrNull()!!.index
if (newIndex != null) { 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 Meta.toMutableMeta(): ObservableMutableMeta = MutableMetaImpl(value, items)
public fun <M : AbstractMutableMeta<M>> M.edit(name: Name, builder: M.() -> Unit) {
val item = when (val existingItem = get(name)) { public fun Meta.asMutableMeta(): MutableMeta = (this as? MutableMeta) ?: toMutableMeta()
null -> empty().also { set(name, it) }
is MetaItemNode<M> -> existingItem.node /**
else -> error("Can't edit value meta item") * Build a [MutableMeta] using given transformation
} */
item.apply(builder) @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)

View File

@ -9,14 +9,14 @@ import kotlin.reflect.KProperty
/* Read-write delegates */ /* 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 { public fun MutableMeta.item(key: Name? = null): MutableMetaDelegate = object : MutableMetaDelegate {
override fun getValue(thisRef: Any?, property: KProperty<*>): MetaItem? { override fun getValue(thisRef: Any?, property: KProperty<*>): Meta? {
return get(key ?: property.name.asName()) 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() val name = key ?: property.name.asName()
set(name, value) set(name, value)
} }
@ -27,7 +27,7 @@ public fun MutableItemProvider.item(key: Name? = null): MutableItemDelegate = ob
/** /**
* A type converter for a mutable [TypedMetaItem] delegate * A type converter for a mutable [TypedMetaItem] delegate
*/ */
public fun <R : Any> MutableItemDelegate.convert( public fun <R : Any> MutableMetaDelegate.convert(
converter: MetaConverter<R>, converter: MetaConverter<R>,
): ReadWriteProperty<Any?, R?> = object : ReadWriteProperty<Any?, 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>, converter: MetaConverter<R>,
default: () -> R, default: () -> R,
): ReadWriteProperty<Any?, R> = object : ReadWriteProperty<Any?, R> { ): ReadWriteProperty<Any?, R> = object : ReadWriteProperty<Any?, R> {
@ -54,9 +54,9 @@ public fun <R : Any> MutableItemDelegate.convert(
} }
} }
public fun <R> MutableItemDelegate.convert( public fun <R> MutableMetaDelegate.convert(
reader: (MetaItem?) -> R, reader: (Meta?) -> R,
writer: (R) -> MetaItem?, writer: (R) -> Meta?,
): ReadWriteProperty<Any?, R> = object : ReadWriteProperty<Any?, R> { ): ReadWriteProperty<Any?, R> = object : ReadWriteProperty<Any?, R> {
override fun getValue(thisRef: Any?, property: KProperty<*>): 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 * 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) 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) 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) 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) 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 } 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 } 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 } 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) 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) 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) 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) 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, default: E,
key: Name? = null, key: Name? = null,
): ReadWriteProperty<Any?, E> = ): ReadWriteProperty<Any?, E> =
item(key).convert(MetaConverter.enum()) { default } 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 */ /* 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) 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) 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) 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) item(key).convert(MetaConverter.float)
/* Safe number delegates*/ /* 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 } 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 } 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 } 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 } item(key).convert(MetaConverter.float) { default }
/* Extra delegates for special cases */ /* Extra delegates for special cases */
public fun MutableItemProvider.stringList( public fun MutableMeta.stringList(
vararg default: String, vararg default: String,
key: Name? = null, key: Name? = null,
): ReadWriteProperty<Any?, List<String>> = item(key).convert( ): ReadWriteProperty<Any?, List<String>> = item(key).convert(
reader = { it?.stringList ?: listOf(*default) }, 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, key: Name? = null,
): ReadWriteProperty<Any?, List<String>?> = item(key).convert( ): ReadWriteProperty<Any?, List<String>?> = item(key).convert(
reader = { it?.stringList }, 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, vararg default: Number,
key: Name? = null, key: Name? = null,
): ReadWriteProperty<Any?, List<Number>> = item(key).convert( ): ReadWriteProperty<Any?, List<Number>> = item(key).convert(
reader = { it?.value?.list?.map { value -> value.numberOrNull ?: Double.NaN } ?: listOf(*default) }, 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 */ /* A special delegate for double arrays */
public fun MutableItemProvider.doubleArray( public fun MutableMeta.doubleArray(
vararg default: Double, vararg default: Double,
key: Name? = null, key: Name? = null,
): ReadWriteProperty<Any?, DoubleArray> = item(key).convert( ): ReadWriteProperty<Any?, DoubleArray> = item(key).convert(
reader = { it?.value?.doubleArray ?: doubleArrayOf(*default) }, 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, key: Name? = null,
writer: (T) -> Value = { Value.of(it) }, writer: (T) -> Value = { Value.of(it) },
reader: (Value) -> T, reader: (Value) -> T,

View File

@ -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)

View File

@ -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))
}
}
}

View File

@ -1,54 +1,38 @@
package space.kscience.dataforge.meta package space.kscience.dataforge.meta
import space.kscience.dataforge.meta.descriptors.Described import space.kscience.dataforge.meta.descriptors.*
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.names.Name 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]. * 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 * Default item provider and [NodeDescriptor] are optional
*/ */
public open class Scheme( public open class Scheme internal constructor(
private var items: ObservableItemProvider = ObservableMeta(), source: MutableMeta = MutableMeta()
final override var descriptor: NodeDescriptor? = null ) : Described, ObservableMutableMeta, Meta by source {
) : Described, MetaRepr, ObservableItemProvider {
/** private var source = source.asObservable()
* 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. final override var descriptor: MetaDescriptor? = null
*/ internal set
@Synchronized
override fun onChange(owner: Any?, action: (Name, MetaItem?, MetaItem?) -> Unit) {
items.onChange(owner, action)
}
/**
* Remove all listeners belonging to given owner
*/
@Synchronized
override fun removeListener(owner: Any?) {
items.removeListener(owner)
}
internal fun wrap( internal fun wrap(
items: MutableItemProvider, items: MutableMeta,
preserveDefault: Boolean = false 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 // * Get a property with default
*/ // */
override fun getItem(name: Name): MetaItem? = items[name] ?: descriptor?.get(name)?.defaultValue // override fun getItem(name: Name): MetaItem? = source[name] ?: descriptor?.get(name)?.defaultValue
/** /**
* Check if property with given [name] could be assigned to [item] * 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) val descriptor = descriptor?.get(name)
return descriptor?.validateItem(item) ?: true return descriptor?.validateItem(item) ?: true
} }
@ -56,30 +40,25 @@ public open class Scheme(
/** /**
* Set a configurable property * Set a configurable property
*/ */
override fun setItem(name: Name, item: MetaItem?) { override fun set(name: Name, meta: Meta) {
val oldItem = items[name] val oldItem = source[name]
if (oldItem != item) { if (oldItem != meta) {
if (validateItem(name, item)) { if (validate(name, meta)) {
items[name] = item source[name] = meta
} else { } 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. * Relocate scheme target onto given [MutableTypedMeta]. Old provider does not get updates anymore.
*/
public fun Scheme.isEmpty(): Boolean = rootItem == null
/**
* Relocate scheme target onto given [MutableItemProvider]. Old provider does not get updates anymore.
* Current state of the scheme used as a default. * 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) wrap(provider, true)
} }
@ -95,16 +74,16 @@ public open class SchemeSpec<out T : Scheme>(
private val builder: () -> T, private val builder: () -> T,
) : Specification<T>, Described { ) : Specification<T>, Described {
override fun read(items: ItemProvider): T = empty().also { override fun read(items: Meta): T = empty().also {
it.wrap(ObservableMeta().withDefault(items)) it.wrap(MutableMeta().withDefault(items))
} }
override fun write(target: MutableItemProvider): T = empty().also { override fun write(target: MutableMeta): T = empty().also {
it.wrap(target) it.wrap(target)
} }
//TODO Generate descriptor from Scheme class //TODO Generate descriptor from Scheme class
override val descriptor: NodeDescriptor? get() = null override val descriptor: MetaDescriptor? get() = null
override fun empty(): T = builder().also { override fun empty(): T = builder().also {
it.descriptor = descriptor it.descriptor = descriptor

View File

@ -1,6 +1,7 @@
package space.kscience.dataforge.meta package space.kscience.dataforge.meta
import space.kscience.dataforge.names.NameToken import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.values.Value
/** /**
* The meta implementation which is guaranteed to be immutable. * 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 * If the argument is possibly mutable node, it is copied on creation
*/ */
public class SealedMeta internal constructor( public class SealedMeta internal constructor(
override val items: Map<NameToken, TypedMetaItem<SealedMeta>>, override val value: Value?,
) : AbstractTypedMeta<SealedMeta>() 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 * 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())
}

View File

@ -6,13 +6,13 @@ import space.kscience.dataforge.names.asName
import kotlin.properties.ReadWriteProperty import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty 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. * 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 * Generate an empty object
@ -31,48 +31,49 @@ public interface ReadOnlySpecification<out T : ItemProvider> {
* By convention [Scheme] companion should inherit this class * 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) { public fun <M : MutableTypedMeta<M>, T : Any> M.update(
spec.write(this).apply(action) spec: Specification<T>,
} action: T.() -> Unit
): T = spec.write(this).apply(action)
/** /**
* Update configuration using given specification * Update configuration using given specification
*/ */
public fun <C : MutableItemProvider, S : Specification<C>> Configurable.update( public fun <T : Any> Configurable.update(
spec: S, spec: Specification<T>,
action: C.() -> Unit, action: T.() -> Unit,
) { ): T = spec.write(config).apply(action)
config.update(spec, 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 * 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>, spec: Specification<T>,
key: Name? = null, key: Name? = null,
): ReadWriteProperty<Any?, T> = object : ReadWriteProperty<Any?, T> { ): ReadWriteProperty<Any?, T> = object : ReadWriteProperty<Any?, T> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T { override fun getValue(thisRef: Any?, property: KProperty<*>): T {
val name = key ?: property.name.asName() val name = key ?: property.name.asName()
return getChild(name).let { spec.write(it) } return spec.write(getOrCreate(name))
} }
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
val name = key ?: property.name.asName() val name = key ?: property.name.asName()
set(name, value.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. * The list is a snapshot of children state, so change in structure is not reflected on its composition.
*/ */
@DFExperimental @DFExperimental
public fun <T : Scheme> MutableItemProvider.listOfSpec( public fun <T : Scheme> MutableMeta.listOfSpec(
spec: Specification<T>, spec: Specification<T>,
key: Name? = null, key: Name? = null,
): ReadWriteProperty<Any?, List<T>> = object : ReadWriteProperty<Any?, List<T>> { ): ReadWriteProperty<Any?, List<T>> = object : ReadWriteProperty<Any?, List<T>> {
override fun getValue(thisRef: Any?, property: KProperty<*>): List<T> { override fun getValue(thisRef: Any?, property: KProperty<*>): List<T> {
val name = key ?: property.name.asName() val name = key ?: property.name.asName()
return getIndexed(name).map { return getIndexed(name).values.map { spec.write(it as MutableMeta) }
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)
}
}
} }
override fun setValue(thisRef: Any?, property: KProperty<*>, value: List<T>) { override fun setValue(thisRef: Any?, property: KProperty<*>, value: List<T>) {

View File

@ -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()

View File

@ -4,7 +4,7 @@ package space.kscience.dataforge.meta.descriptors
* An object which provides its descriptor * An object which provides its descriptor
*/ */
public interface Described { public interface Described {
public val descriptor: ItemDescriptor? public val descriptor: MetaDescriptor?
public companion object { public companion object {
//public const val DESCRIPTOR_NODE: String = "@descriptor" //public const val DESCRIPTOR_NODE: String = "@descriptor"

View File

@ -1,126 +1,127 @@
package space.kscience.dataforge.meta.descriptors //package space.kscience.dataforge.meta.descriptors
//
import space.kscience.dataforge.meta.* //import space.kscience.dataforge.meta.*
import space.kscience.dataforge.misc.DFBuilder //import space.kscience.dataforge.misc.DFBuilder
import space.kscience.dataforge.names.* //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. ///**
*/ // * A common parent for [ValueDescriptor] and [NodeDescriptor]. Describes a single [TypedMetaItem] or a group of same-name-siblings.
public sealed interface ItemDescriptor: MetaRepr { // */
//public sealed interface ItemDescriptor: MetaRepr {
/** //
* True if same name siblings with this name are allowed // /**
*/ // * True if same name siblings with this name are allowed
public val multiple: Boolean // */
// public val multiple: Boolean
/** //
* The item description text // /**
*/ // * The item description text
public val info: String? // */
// public val info: String?
/** //
* True if the item is required // /**
*/ // * True if the item is required
public val required: Boolean // */
// public val required: Boolean
//
/** //
* Additional attributes of an item. For example validation and widget parameters // /**
* // * Additional attributes of an item. For example validation and widget parameters
* @return // *
*/ // * @return
public val attributes: Meta? // */
// public val attributes: Meta?
/** //
* An index field by which this node is identified in case of same name siblings construct // /**
*/ // * An index field by which this node is identified in case of same name siblings construct
public val indexKey: String // */
// public val indexKey: String
/** //
* Compute and cache the default [MetaItem] value described by this descriptor // /**
*/ // * Compute and cache the default [Meta] value described by this descriptor
public val defaultValue: MetaItem? // */
// public val defaultValue: Meta?
public companion object { //
public const val DEFAULT_INDEX_KEY: String = "@index" // public companion object {
} // public const val DEFAULT_INDEX_KEY: String = "@index"
} // }
//}
//
/** //
* The builder for [ItemDescriptor] ///**
*/ // * The builder for [ItemDescriptor]
@DFBuilder // */
public sealed class ItemDescriptorBuilder(final override val config: ObservableMeta) : Configurable, ItemDescriptor { //@DFBuilder
//public sealed class ItemDescriptorBuilder(final override val config: MutableMeta) : Configurable, ItemDescriptor {
/** //
* True if same name siblings with this name are allowed // /**
*/ // * True if same name siblings with this name are allowed
override var multiple: Boolean by config.boolean(false) // */
// override var multiple: Boolean by config.boolean(false)
/** //
* The item description text // /**
*/ // * The item description text
override var info: String? by config.string() // */
// override var info: String? by config.string()
/** //
* True if the item is required // /**
*/ // * True if the item is required
abstract override var required: Boolean // */
// abstract override var required: Boolean
//
/** //
* Additional attributes of an item. For example validation and widget parameters // /**
* // * Additional attributes of an item. For example validation and widget parameters
* @return // *
*/ // * @return
override var attributes: ObservableMeta? by config.node() // */
// override var attributes: MutableMeta? by config.node()
/** //
* An index field by which this node is identified in case of same name siblings construct // /**
*/ // * 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) // */
// override var indexKey: String by config.string(DEFAULT_INDEX_KEY)
public abstract fun build(): ItemDescriptor //
// public abstract fun build(): ItemDescriptor
override fun toMeta(): Meta = config //
// override fun toMeta(): Meta = config
public companion object { //
public const val DEFAULT_INDEX_KEY: String = "@index" // public companion object {
} // public const val DEFAULT_INDEX_KEY: String = "@index"
} // }
//}
/** //
* Configure attributes of the descriptor, creating an attributes node if needed. ///**
*/ // * 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) //public inline fun ItemDescriptorBuilder.attributes(block: MutableMeta.() -> Unit) {
} // (attributes ?: MutableMeta().also { this.attributes = it }).apply(block)
//}
/** //
* Check if given item suits the descriptor ///**
*/ // * Check if given item suits the descriptor
public fun ItemDescriptor.validateItem(item: MetaItem?): Boolean { // */
if (item == null) return !required //public fun ItemDescriptor.validateItem(item: MetaItem?): Boolean {
return when (this) { // if (item == null) return !required
is ValueDescriptor -> isAllowedValue(item.value ?: return false) // return when (this) {
is NodeDescriptor -> items.all { (key, d) -> // is ValueDescriptor -> isAllowedValue(item.value ?: return false)
d.validateItem(item.node[key]) // 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 ///**
*/ // * 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 //public operator fun ItemDescriptor.get(name: Name): ItemDescriptor? {
return when (this) { // if (name.isEmpty()) return this
is ValueDescriptor -> null // empty name already checked // return when (this) {
is NodeDescriptor -> items[name.firstOrNull()!!.toString()]?.get(name.cutFirst()) // 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()) //
//public operator fun ItemDescriptor.get(name: String): ItemDescriptor? = get(name.toName())
//

View File

@ -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
)
}

View File

@ -1,222 +1,222 @@
package space.kscience.dataforge.meta.descriptors //package space.kscience.dataforge.meta.descriptors
//
import space.kscience.dataforge.meta.* //import space.kscience.dataforge.meta.*
import space.kscience.dataforge.misc.DFBuilder //import space.kscience.dataforge.misc.DFBuilder
import space.kscience.dataforge.names.* //import space.kscience.dataforge.names.*
//
//
/** ///**
* A [Meta] that is constructed from [NodeDescriptor] // * A [Meta] that is constructed from [NodeDescriptor]
*/ // */
private class DescriptorMeta(val descriptor: NodeDescriptor) : Meta, MetaBase() { //private class DescriptorMeta(val descriptor: NodeDescriptor) : AbstractMeta() {
override val items: Map<NameToken, MetaItem> // override val items: Map<NameToken, MetaItem>
get() = buildMap { // get() = buildMap {
descriptor.items.forEach { (token, descriptorItem) -> // descriptor.items.forEach { (token, descriptorItem) ->
val item = descriptorItem.defaultValue // val item = descriptorItem.defaultValue
if (item != null) { // if (item != null) {
put(NameToken(token), item) // put(NameToken(token), item)
} // }
} // }
} // }
} //}
//
//
/** ///**
* Descriptor for meta node. Could contain additional information for viewing // * Descriptor for meta node. Could contain additional information for viewing
* and editing. // * and editing.
* // *
* @author Alexander Nozik // * @author Alexander Nozik
*/ // */
@DFBuilder //@DFBuilder
public sealed interface NodeDescriptor : ItemDescriptor { //public sealed interface NodeDescriptor : ItemDescriptor {
/** // /**
* True if the node is required // * True if the node is required
* // *
* @return // * @return
*/ // */
override val required: Boolean // override val required: Boolean
//
/** // /**
* The default for this node. Null if there is no default. // * The default for this node. Null if there is no default.
* // *
* @return // * @return
*/ // */
public val default: Meta? // public val default: Meta?
//
/** // /**
* The map of children item descriptors (both nodes and values) // * The map of children item descriptors (both nodes and values)
*/ // */
public val items: Map<String, ItemDescriptor> // public val items: Map<String, ItemDescriptor>
//
/** // /**
* The map of children node descriptors // * The map of children node descriptors
*/ // */
public val nodes: Map<String, NodeDescriptor> // public val nodes: Map<String, NodeDescriptor>
//
/** // /**
* The list of children value descriptors // * The list of children value descriptors
*/ // */
public val values: Map<String, ValueDescriptor> // public val values: Map<String, ValueDescriptor>
//
/** // /**
* Generate a laminate representing default item set generated by this descriptor // * Generate a laminate representing default item set generated by this descriptor
*/ // */
public val defaultMeta: Laminate // public val defaultMeta: Laminate
//
public companion object { // public companion object {
//
internal val ITEM_KEY: Name = "item".asName() // internal val ITEM_KEY: Name = "item".asName()
internal val IS_NODE_KEY: Name = "@isNode".asName() // internal val IS_NODE_KEY: Name = "@isNode".asName()
//
//TODO infer descriptor from spec // //TODO infer descriptor from spec
} // }
} //}
//
//
@DFBuilder //@DFBuilder
public class NodeDescriptorBuilder(config: ObservableMeta = ObservableMeta()) : ItemDescriptorBuilder(config), NodeDescriptor { //public class NodeDescriptorBuilder(config: MutableMeta = MutableMeta()) : ItemDescriptorBuilder(config), NodeDescriptor {
init { // init {
config[IS_NODE_KEY] = true // config[IS_NODE_KEY] = true
} // }
//
/** // /**
* True if the node is required // * True if the node is required
* // *
* @return // * @return
*/ // */
override var required: Boolean by config.boolean { default == null } // override var required: Boolean by config.boolean { default == null }
//
/** // /**
* The default for this node. Null if there is no default. // * The default for this node. Null if there is no default.
* // *
* @return // * @return
*/ // */
override var default: ObservableMeta? by config.node() // override var default: MutableMeta? by config.node()
//
/** // /**
* The map of children item descriptors (both nodes and values) // * The map of children item descriptors (both nodes and values)
*/ // */
override val items: Map<String, ItemDescriptor> // override val items: Map<String, ItemDescriptor>
get() = config.getIndexed(ITEM_KEY).entries.associate { (name, item) -> // get() = config.getIndexed(ITEM_KEY).entries.associate { (name, item) ->
if (name == null) error("Child item index should not be null") // if (name == null) error("Child item index should not be null")
val node = item.node ?: error("Node descriptor must be a node") // val node = item.node ?: error("Node descriptor must be a node")
if (node[IS_NODE_KEY].boolean == true) { // if (node[IS_NODE_KEY].boolean == true) {
name to NodeDescriptorBuilder(node as ObservableMeta) // name to NodeDescriptorBuilder(node as MutableMeta)
} else { // } else {
name to ValueDescriptorBuilder(node as ObservableMeta) // name to ValueDescriptorBuilder(node as MutableMeta)
} // }
} // }
//
/** // /**
* The map of children node descriptors // * The map of children node descriptors
*/ // */
@Suppress("UNCHECKED_CAST") // @Suppress("UNCHECKED_CAST")
override val nodes: Map<String, NodeDescriptor> // override val nodes: Map<String, NodeDescriptor>
get() = config.getIndexed(ITEM_KEY).entries.filter { // get() = config.getIndexed(ITEM_KEY).entries.filter {
it.value.node[IS_NODE_KEY].boolean == true // it.value.node[IS_NODE_KEY].boolean == true
}.associate { (name, item) -> // }.associate { (name, item) ->
if (name == null) error("Child node index should not be null") // if (name == null) error("Child node index should not be null")
val node = item.node ?: error("Node descriptor must be a node") // val node = item.node ?: error("Node descriptor must be a node")
name to NodeDescriptorBuilder(node as ObservableMeta) // name to NodeDescriptorBuilder(node as MutableMeta)
} // }
//
/** // /**
* The list of children value descriptors // * The list of children value descriptors
*/ // */
override val values: Map<String, ValueDescriptor> // override val values: Map<String, ValueDescriptor>
get() = config.getIndexed(ITEM_KEY).entries.filter { // get() = config.getIndexed(ITEM_KEY).entries.filter {
it.value.node[IS_NODE_KEY].boolean != true // it.value.node[IS_NODE_KEY].boolean != true
}.associate { (name, item) -> // }.associate { (name, item) ->
if (name == null) error("Child value index should not be null") // if (name == null) error("Child value index should not be null")
val node = item.node ?: error("Node descriptor must be a node") // val node = item.node ?: error("Node descriptor must be a node")
name to ValueDescriptorBuilder(node as ObservableMeta) // name to ValueDescriptorBuilder(node as MutableMeta)
} // }
//
private fun buildNode(name: Name): NodeDescriptorBuilder { // private fun buildNode(name: Name): NodeDescriptorBuilder {
return when (name.length) { // return when (name.length) {
0 -> this // 0 -> this
1 -> { // 1 -> {
val token = NameToken(ITEM_KEY.toString(), name.toString()) // val token = NameToken(ITEM_KEY.toString(), name.toString())
val config: ObservableMeta = config[token].node ?: ObservableMeta().also { // val config: MutableMeta = config[token].node ?: MutableMeta().also {
it[IS_NODE_KEY] = true // it[IS_NODE_KEY] = true
config[token] = it // config[token] = it
} // }
NodeDescriptorBuilder(config) // NodeDescriptorBuilder(config)
} // }
else -> buildNode(name.firstOrNull()?.asName()!!).buildNode(name.cutFirst()) // else -> buildNode(name.firstOrNull()?.asName()!!).buildNode(name.cutFirst())
} // }
} // }
//
/** // /**
* Define a child item descriptor for this node // * Define a child item descriptor for this node
*/ // */
private fun newItem(key: String, descriptor: ItemDescriptor) { // private fun newItem(key: String, descriptor: ItemDescriptor) {
if (items.keys.contains(key)) error("The key $key already exists in descriptor") // if (items.keys.contains(key)) error("The key $key already exists in descriptor")
val token = ITEM_KEY.withIndex(key) // val token = ITEM_KEY.withIndex(key)
config[token] = descriptor.toMeta() // config[token] = descriptor.toMeta()
} // }
//
public fun item(name: Name, descriptor: ItemDescriptor) { // public fun item(name: Name, descriptor: ItemDescriptor) {
buildNode(name.cutLast()).newItem(name.lastOrNull().toString(), descriptor) // buildNode(name.cutLast()).newItem(name.lastOrNull().toString(), descriptor)
} // }
//
public fun item(name: String, descriptor: ItemDescriptor) { // public fun item(name: String, descriptor: ItemDescriptor) {
item(name.toName(), descriptor) // item(name.toName(), descriptor)
} // }
//
/** // /**
* Create and configure a child node descriptor // * Create and configure a child node descriptor
*/ // */
public fun node(name: Name, block: NodeDescriptorBuilder.() -> Unit) { // public fun node(name: Name, block: NodeDescriptorBuilder.() -> Unit) {
item(name, NodeDescriptorBuilder().apply(block)) // item(name, NodeDescriptorBuilder().apply(block))
} // }
//
public fun node(name: String, block: NodeDescriptorBuilder.() -> Unit) { // public fun node(name: String, block: NodeDescriptorBuilder.() -> Unit) {
node(name.toName(), block) // node(name.toName(), block)
} // }
//
/** // /**
* Create and configure child value descriptor // * Create and configure child value descriptor
*/ // */
public fun value(name: Name, block: ValueDescriptorBuilder.() -> Unit) { // public fun value(name: Name, block: ValueDescriptorBuilder.() -> Unit) {
require(name.length >= 1) { "Name length for value descriptor must be non-empty" } // require(name.length >= 1) { "Name length for value descriptor must be non-empty" }
item(name, ValueDescriptorBuilder().apply(block)) // item(name, ValueDescriptorBuilder().apply(block))
} // }
//
public fun value(name: String, block: ValueDescriptorBuilder.() -> Unit) { // public fun value(name: String, block: ValueDescriptorBuilder.() -> Unit) {
value(name.toName(), block) // value(name.toName(), block)
} // }
//
/** // /**
* Generate a laminate representing default item set generated by this descriptor // * Generate a laminate representing default item set generated by this descriptor
*/ // */
override val defaultMeta: Laminate by lazy { Laminate(default, DescriptorMeta(this)) } // override val defaultMeta: Laminate by lazy { Laminate(default, DescriptorMeta(this)) }
//
/** // /**
* Build a default [MetaItemNode] from this node descriptor // * Build a default [MetaItemNode] from this node descriptor
*/ // */
override val defaultValue: MetaItem get() = MetaItemNode(defaultMeta) // override val defaultValue: MetaItem get() = MetaItemNode(defaultMeta)
//
override fun build(): NodeDescriptor = NodeDescriptorBuilder(config.copy()) // override fun build(): NodeDescriptor = NodeDescriptorBuilder(config.copy())
//
public companion object { // public companion object {
//
internal val ITEM_KEY: Name = "item".asName() // internal val ITEM_KEY: Name = "item".asName()
internal val IS_NODE_KEY: Name = "@isNode".asName() // internal val IS_NODE_KEY: Name = "@isNode".asName()
//
//TODO infer descriptor from spec // //TODO infer descriptor from spec
} // }
} //}
//
public inline fun NodeDescriptor(block: NodeDescriptorBuilder.() -> Unit): NodeDescriptor = //public inline fun NodeDescriptor(block: NodeDescriptorBuilder.() -> Unit): NodeDescriptor =
NodeDescriptorBuilder().apply(block) // NodeDescriptorBuilder().apply(block)
//
/** ///**
* Merge two node descriptors into one using first one as primary // * Merge two node descriptors into one using first one as primary
*/ // */
public operator fun NodeDescriptor.plus(other: NodeDescriptor): NodeDescriptor { //public operator fun NodeDescriptor.plus(other: NodeDescriptor): NodeDescriptor {
return NodeDescriptorBuilder().apply { // return NodeDescriptorBuilder().apply {
config.update(other.toMeta()) // config.update(other.toMeta())
config.update(this@plus.toMeta()) // config.update(this@plus.toMeta())
} // }
} //}

View File

@ -1,139 +1,139 @@
package space.kscience.dataforge.meta.descriptors //package space.kscience.dataforge.meta.descriptors
//
import space.kscience.dataforge.meta.* //import space.kscience.dataforge.meta.*
import space.kscience.dataforge.misc.DFBuilder //import space.kscience.dataforge.misc.DFBuilder
import space.kscience.dataforge.values.* //import space.kscience.dataforge.values.*
//
//
/** ///**
* A descriptor for meta value // * A descriptor for meta value
* // *
* Descriptor can have non-atomic path. It is resolved when descriptor is added to the node // * Descriptor can have non-atomic path. It is resolved when descriptor is added to the node
* // *
* @author Alexander Nozik // * @author Alexander Nozik
*/ // */
@DFBuilder //@DFBuilder
public sealed interface ValueDescriptor : ItemDescriptor { //public sealed interface ValueDescriptor : ItemDescriptor {
//
/** // /**
* True if the value is required // * True if the value is required
* // *
* @return // * @return
*/ // */
override val required: Boolean // override val required: Boolean
//
/** // /**
* The default for this value. Null if there is no default. // * The default for this value. Null if there is no default.
* // *
* @return // * @return
*/ // */
public val default: Value? // public val default: Value?
//
//
/** // /**
* A list of allowed ValueTypes. Empty if any value type allowed // * A list of allowed ValueTypes. Empty if any value type allowed
* // *
* @return // * @return
*/ // */
public val type: List<ValueType>? // public val type: List<ValueType>?
//
/** // /**
* Check if given value is allowed for here. The type should be allowed and // * Check if given value is allowed for here. The type should be allowed and
* if it is value should be within allowed values // * if it is value should be within allowed values
* // *
* @param value // * @param value
* @return // * @return
*/ // */
public fun isAllowedValue(value: Value): Boolean = // public fun isAllowedValue(value: Value): Boolean =
(type?.let { it.contains(ValueType.STRING) || it.contains(value.type) } ?: true) // (type?.let { it.contains(ValueType.STRING) || it.contains(value.type) } ?: true)
&& (allowedValues.isEmpty() || allowedValues.contains(value)) // && (allowedValues.isEmpty() || allowedValues.contains(value))
//
/** // /**
* A list of allowed values with descriptions. If empty than any value is // * A list of allowed values with descriptions. If empty than any value is
* allowed. // * allowed.
* // *
* @return // * @return
*/ // */
public val allowedValues: List<Value> // public val allowedValues: List<Value>
} //}
//
/** ///**
* A builder fir [ValueDescriptor] // * A builder fir [ValueDescriptor]
*/ // */
@DFBuilder //@DFBuilder
public class ValueDescriptorBuilder( //public class ValueDescriptorBuilder(
config: ObservableMeta = ObservableMeta() // config: MutableMeta = MutableMeta()
) : ItemDescriptorBuilder(config), ValueDescriptor { //) : ItemDescriptorBuilder(config), ValueDescriptor {
//
/** // /**
* True if the value is required // * True if the value is required
* // *
* @return // * @return
*/ // */
override var required: Boolean by config.boolean { default == null } // override var required: Boolean by config.boolean { default == null }
//
/** // /**
* The default for this value. Null if there is no default. // * The default for this value. Null if there is no default.
* // *
* @return // * @return
*/ // */
override var default: Value? by config.value() // override var default: Value? by config.value()
//
public fun default(v: Any) { // public fun default(v: Any) {
this.default = Value.of(v) // this.default = Value.of(v)
} // }
//
/** // /**
* A list of allowed ValueTypes. Empty if any value type allowed // * A list of allowed ValueTypes. Empty if any value type allowed
* // *
* @return // * @return
*/ // */
override var type: List<ValueType>? by config.listValue { ValueType.valueOf(it.string) } // override var type: List<ValueType>? by config.listValue { ValueType.valueOf(it.string) }
//
public fun type(vararg t: ValueType) { // public fun type(vararg t: ValueType) {
this.type = listOf(*t) // this.type = listOf(*t)
} // }
//
/** // /**
* Check if given value is allowed for here. The type should be allowed and // * Check if given value is allowed for here. The type should be allowed and
* if it is value should be within allowed values // * if it is value should be within allowed values
* // *
* @param value // * @param value
* @return // * @return
*/ // */
override fun isAllowedValue(value: Value): Boolean { // override fun isAllowedValue(value: Value): Boolean {
return (type?.let { it.contains(ValueType.STRING) || it.contains(value.type) } ?: true) // return (type?.let { it.contains(ValueType.STRING) || it.contains(value.type) } ?: true)
&& (allowedValues.isEmpty() || allowedValues.contains(value)) // && (allowedValues.isEmpty() || allowedValues.contains(value))
} // }
//
/** // /**
* A list of allowed values with descriptions. If empty than any value is // * A list of allowed values with descriptions. If empty than any value is
* allowed. // * allowed.
* // *
* @return // * @return
*/ // */
override var allowedValues: List<Value> by config.item().convert( // override var allowedValues: List<Value> by config.item().convert(
reader = { // reader = {
val value = it.value // val value = it.value
when { // when {
value?.list != null -> value.list // value?.list != null -> value.list
type?.let { type -> type.size == 1 && type[0] === ValueType.BOOLEAN } ?: false -> listOf(True, False) // type?.let { type -> type.size == 1 && type[0] === ValueType.BOOLEAN } ?: false -> listOf(True, False)
else -> emptyList() // else -> emptyList()
} // }
}, // },
writer = { // writer = {
MetaItemValue(it.asValue()) // MetaItemValue(it.asValue())
} // }
) // )
//
/** // /**
* Allow given list of value and forbid others // * Allow given list of value and forbid others
*/ // */
public fun allow(vararg v: Any) { // public fun allow(vararg v: Any) {
this.allowedValues = v.map { Value.of(it) } // this.allowedValues = v.map { Value.of(it) }
} // }
//
override val defaultValue: MetaItem? get() = default?.asMetaItem() // override val defaultValue: MetaItem? get() = default?.asMetaItem()
//
override fun build(): ValueDescriptor = ValueDescriptorBuilder(config.copy()) // override fun build(): ValueDescriptor = ValueDescriptorBuilder(config.copy())
} //}

View File

@ -4,10 +4,10 @@ import space.kscience.dataforge.names.Name
import space.kscience.dataforge.values.ValueType import space.kscience.dataforge.values.ValueType
import space.kscience.dataforge.values.asValue 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, key: Name,
default: E?, default: E?,
crossinline modifier: ValueDescriptor.() -> Unit = {}, crossinline modifier: MetaDescriptor.() -> Unit = {},
): Unit = value(key) { ): Unit = value(key) {
type(ValueType.STRING) type(ValueType.STRING)
default?.let { default?.let {

View File

@ -1,6 +1,6 @@
package space.kscience.dataforge.meta 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.misc.DFExperimental
import space.kscience.dataforge.names.toName import space.kscience.dataforge.names.toName
import space.kscience.dataforge.values.ListValue import space.kscience.dataforge.values.ListValue
@ -9,7 +9,7 @@ import space.kscience.dataforge.values.Value
/** /**
* Convert meta to map of maps * 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) -> return items.entries.associate { (token, item) ->
token.toString() to when (item) { token.toString() to when (item) {
is MetaItemNode -> item.node.toMap() 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. * All other values will be converted to values.
*/ */
@DFExperimental @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") @Suppress("UNCHECKED_CAST")
fun toItem(value: Any?): MetaItem = when (value) { fun toItem(value: Any?): MetaItem = when (value) {
is MetaItem -> value is MetaItem -> value

View File

@ -21,12 +21,12 @@ public interface TransformationRule {
* @return a sequence of item paths to be transformed * @return a sequence of item paths to be transformed
*/ */
public fun selectItems(meta: Meta): Sequence<Name> = 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 * 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> = 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) if (selector(name)) target.set(name, item)
} }
} }
@ -51,7 +51,7 @@ public data class KeepTransformationRule(val selector: (Name) -> Boolean) :
*/ */
public data class SingleItemTransformationRule( public data class SingleItemTransformationRule(
val from: Name, val from: Name,
val transform: MutableMeta<*>.(Name, MetaItem?) -> Unit, val transform: MutableTypedMeta.(Name, MetaItem?) -> Unit,
) : TransformationRule { ) : TransformationRule {
override fun matches(name: Name, item: MetaItem?): Boolean { override fun matches(name: Name, item: MetaItem?): Boolean {
return name == from return name == from
@ -59,7 +59,7 @@ public data class SingleItemTransformationRule(
override fun selectItems(meta: Meta): Sequence<Name> = sequenceOf(from) 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) { if (name == this.from) {
target.transform(name, item) target.transform(name, item)
} }
@ -68,13 +68,13 @@ public data class SingleItemTransformationRule(
public data class RegexItemTransformationRule( public data class RegexItemTransformationRule(
val from: Regex, val from: Regex,
val transform: MutableMeta<*>.(name: Name, MatchResult, MetaItem?) -> Unit, val transform: MutableTypedMeta.(name: Name, MatchResult, MetaItem?) -> Unit,
) : TransformationRule { ) : TransformationRule {
override fun matches(name: Name, item: MetaItem?): Boolean { override fun matches(name: Name, item: MetaItem?): Boolean {
return from.matches(name.toString()) 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()) val match = from.matchEntire(name.toString())
if (match != null) { if (match != null) {
target.transform(name, match, item) 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 * Generate an observable configuration that contains only elements defined by transformation rules and changes with the source
*/ */
@DFExperimental @DFExperimental
public fun generate(source: ObservableMeta): ObservableItemProvider = ObservableMeta().apply { public fun generate(source: MutableMeta): ObservableMeta = MutableMeta().apply {
transformations.forEach { rule -> transformations.forEach { rule ->
rule.selectItems(source).forEach { name -> rule.selectItems(source).forEach { name ->
rule.transformItem(name, source[name], this) 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. * 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 -> source.onChange(target) { name, _, newItem ->
transformations.forEach { t -> transformations.forEach { t ->
if (t.matches(name, newItem)) { if (t.matches(name, newItem)) {

View File

@ -1,7 +1,7 @@
package space.kscience.dataforge.values package space.kscience.dataforge.values
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaBuilder import space.kscience.dataforge.meta.MutableMeta
/** /**
* Check if value is null * 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 }

View File

@ -6,7 +6,7 @@ import kotlin.test.assertEquals
class ConfigTest { class ConfigTest {
@Test @Test
fun testIndexedWrite(){ fun testIndexedWrite(){
val config = MetaBuilder() val config = MutableMeta()
config["a[1].b"] = 1 config["a[1].b"] = 1
assertEquals(null, config["a.b"].int) assertEquals(null, config["a.b"].int)
assertEquals(1, config["a[1].b"].int) assertEquals(1, config["a[1].b"].int)

View File

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

View File

@ -8,7 +8,7 @@ import kotlin.test.assertEquals
class SchemeTest { class SchemeTest {
@Test @Test
fun testSchemeWrappingBeforeEdit() { fun testSchemeWrappingBeforeEdit() {
val config = MetaBuilder() val config = MutableMeta()
val scheme = TestScheme.write(config) val scheme = TestScheme.write(config)
scheme.a = 29 scheme.a = 29
assertEquals(29, config["a"].int) assertEquals(29, config["a"].int)
@ -18,7 +18,7 @@ class SchemeTest {
fun testSchemeWrappingAfterEdit() { fun testSchemeWrappingAfterEdit() {
val scheme = TestScheme.empty() val scheme = TestScheme.empty()
scheme.a = 29 scheme.a = 29
val config = MetaBuilder() val config = MutableMeta()
scheme.retarget(config) scheme.retarget(config)
assertEquals(29, scheme.a) assertEquals(29, scheme.a)
} }

View File

@ -49,7 +49,7 @@ class SpecificationTest {
@Test @Test
fun testChildModification() { fun testChildModification() {
val config = MetaBuilder() val config = MutableMeta()
val child = config.getChild("child") val child = config.getChild("child")
val scheme = TestScheme.write(child) val scheme = TestScheme.write(child)
scheme.a = 22 scheme.a = 22
@ -60,7 +60,7 @@ class SpecificationTest {
@Test @Test
fun testChildUpdate() { fun testChildUpdate() {
val config = MetaBuilder() val config = MutableMeta()
val child = config.getChild("child") val child = config.getChild("child")
child.update(TestScheme) { child.update(TestScheme) {
a = 22 a = 22

View File

@ -38,7 +38,7 @@ public fun Meta.toDynamic(): dynamic {
return res 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 keys(): Array<String> = js("Object").keys(obj)
private fun isArray(@Suppress("UNUSED_PARAMETER") obj: dynamic): Boolean = private fun isArray(@Suppress("UNUSED_PARAMETER") obj: dynamic): Boolean =

View File

@ -2,7 +2,7 @@ package space.kscience.dataforge.workspace
import space.kscience.dataforge.context.ContextAware import space.kscience.dataforge.context.ContextAware
import space.kscience.dataforge.meta.Meta 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.misc.Type
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.toName 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<*> = public suspend fun Workspace.produce(task: String, meta: Meta): TaskResult<*> =
produce(task.toName(), meta) 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)) produce(task, Meta(block))

View File

@ -8,7 +8,7 @@ import space.kscience.dataforge.data.DataSet
import space.kscience.dataforge.data.DataSetBuilder import space.kscience.dataforge.data.DataSetBuilder
import space.kscience.dataforge.data.DataTree import space.kscience.dataforge.data.DataTree
import space.kscience.dataforge.meta.Meta 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.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.misc.DFBuilder import space.kscience.dataforge.misc.DFBuilder
import space.kscience.dataforge.misc.DFExperimental 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 * Define a new target with a builder
*/ */
public inline fun WorkspaceBuilder.target(name: String, metaBuilder: MetaBuilder.() -> Unit): Unit = public inline fun WorkspaceBuilder.target(name: String, mutableMeta: MutableMeta.() -> Unit): Unit =
target(name, Meta(metaBuilder)) target(name, Meta(mutableMeta))
@DFBuilder @DFBuilder
public fun Workspace(parentContext: Context = Global, builder: WorkspaceBuilder.() -> Unit): Workspace = public fun Workspace(parentContext: Context = Global, builder: WorkspaceBuilder.() -> Unit): Workspace =

View File

@ -28,7 +28,7 @@ public inline fun <reified P : Plugin> P.toFactory(): PluginFactory<P> = object
override val type: KClass<out P> = P::class 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) produce(task, block)
} }

View File

@ -5,7 +5,7 @@ pluginManagement {
gradlePluginPortal() gradlePluginPortal()
} }
val toolsVersion = "0.10.0" val toolsVersion = "0.10.2"
plugins { plugins {
id("ru.mipt.npm.gradle.project") version toolsVersion id("ru.mipt.npm.gradle.project") version toolsVersion