WIP full refactor of Meta
This commit is contained in:
parent
679175391a
commit
9f5b010847
@ -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")
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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?> {
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
package space.kscience.dataforge.provider
|
||||||
|
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class PathTest {
|
||||||
|
@Test
|
||||||
|
fun testParse(){
|
||||||
|
val nameString = "a.b.c.d"
|
||||||
|
val pathString = "a.b/c.d"
|
||||||
|
assertEquals(1, Path.parse(nameString).length)
|
||||||
|
assertEquals(2, Path.parse(pathString).length)
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,7 @@ import kotlinx.coroutines.flow.map
|
|||||||
import kotlinx.coroutines.launch
|
import 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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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.
|
||||||
|
@ -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))
|
@ -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)
|
||||||
|
|
||||||
|
@ -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>()
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
|
@ -1,111 +0,0 @@
|
|||||||
package space.kscience.dataforge.meta
|
|
||||||
|
|
||||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
|
||||||
import space.kscience.dataforge.names.Name
|
|
||||||
import space.kscience.dataforge.names.asName
|
|
||||||
import space.kscience.dataforge.values.Value
|
|
||||||
import kotlin.properties.ReadOnlyProperty
|
|
||||||
|
|
||||||
/* Meta delegates */
|
|
||||||
|
|
||||||
public typealias ItemDelegate = ReadOnlyProperty<Any?, MetaItem?>
|
|
||||||
|
|
||||||
public fun ItemProvider.item(key: Name? = null): ItemDelegate = ReadOnlyProperty { _, property ->
|
|
||||||
get(key ?: property.name.asName())
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO add caching for sealed nodes
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply a converter to this delegate creating a delegate with a custom type
|
|
||||||
*/
|
|
||||||
public fun <R : Any> ItemDelegate.convert(
|
|
||||||
converter: MetaConverter<R>,
|
|
||||||
): ReadOnlyProperty<Any?, R?> = ReadOnlyProperty { thisRef, property ->
|
|
||||||
this@convert.getValue(thisRef, property)?.let(converter::itemToObject)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public fun <R : Any> ItemDelegate.convert(
|
|
||||||
converter: MetaConverter<R>,
|
|
||||||
default: () -> R,
|
|
||||||
): ReadOnlyProperty<Any?, R> = ReadOnlyProperty<Any?, R> { thisRef, property ->
|
|
||||||
this@convert.getValue(thisRef, property)?.let(converter::itemToObject) ?: default()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A converter with a custom reader transformation
|
|
||||||
*/
|
|
||||||
public fun <R> ItemDelegate.convert(
|
|
||||||
reader: (MetaItem?) -> R,
|
|
||||||
): ReadOnlyProperty<Any?, R> = ReadOnlyProperty<Any?, R> { thisRef, property ->
|
|
||||||
this@convert.getValue(thisRef, property).let(reader)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Read-only delegates for [ItemProvider] */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A property delegate that uses custom key
|
|
||||||
*/
|
|
||||||
public fun ItemProvider.value(key: Name? = null): ReadOnlyProperty<Any?, Value?> =
|
|
||||||
item(key).convert(MetaConverter.value)
|
|
||||||
|
|
||||||
public fun ItemProvider.string(key: Name? = null): ReadOnlyProperty<Any?, String?> =
|
|
||||||
item(key).convert(MetaConverter.string)
|
|
||||||
|
|
||||||
public fun ItemProvider.boolean(key: Name? = null): ReadOnlyProperty<Any?, Boolean?> =
|
|
||||||
item(key).convert(MetaConverter.boolean)
|
|
||||||
|
|
||||||
public fun ItemProvider.number(key: Name? = null): ReadOnlyProperty<Any?, Number?> =
|
|
||||||
item(key).convert(MetaConverter.number)
|
|
||||||
|
|
||||||
public fun ItemProvider.double(key: Name? = null): ReadOnlyProperty<Any?, Double?> =
|
|
||||||
item(key).convert(MetaConverter.double)
|
|
||||||
|
|
||||||
public fun ItemProvider.float(key: Name? = null): ReadOnlyProperty<Any?, Float?> =
|
|
||||||
item(key).convert(MetaConverter.float)
|
|
||||||
|
|
||||||
public fun ItemProvider.int(key: Name? = null): ReadOnlyProperty<Any?, Int?> =
|
|
||||||
item(key).convert(MetaConverter.int)
|
|
||||||
|
|
||||||
public fun ItemProvider.long(key: Name? = null): ReadOnlyProperty<Any?, Long?> =
|
|
||||||
item(key).convert(MetaConverter.long)
|
|
||||||
|
|
||||||
public fun ItemProvider.node(key: Name? = null): ReadOnlyProperty<Any?, Meta?> =
|
|
||||||
item(key).convert(MetaConverter.meta)
|
|
||||||
|
|
||||||
public fun ItemProvider.string(default: String, key: Name? = null): ReadOnlyProperty<Any?, String> =
|
|
||||||
item(key).convert(MetaConverter.string) { default }
|
|
||||||
|
|
||||||
public fun ItemProvider.boolean(default: Boolean, key: Name? = null): ReadOnlyProperty<Any?, Boolean> =
|
|
||||||
item(key).convert(MetaConverter.boolean) { default }
|
|
||||||
|
|
||||||
public fun ItemProvider.number(default: Number, key: Name? = null): ReadOnlyProperty<Any?, Number> =
|
|
||||||
item(key).convert(MetaConverter.number) { default }
|
|
||||||
|
|
||||||
public fun ItemProvider.double(default: Double, key: Name? = null): ReadOnlyProperty<Any?, Double> =
|
|
||||||
item(key).convert(MetaConverter.double) { default }
|
|
||||||
|
|
||||||
public fun ItemProvider.float(default: Float, key: Name? = null): ReadOnlyProperty<Any?, Float> =
|
|
||||||
item(key).convert(MetaConverter.float) { default }
|
|
||||||
|
|
||||||
public fun ItemProvider.int(default: Int, key: Name? = null): ReadOnlyProperty<Any?, Int> =
|
|
||||||
item(key).convert(MetaConverter.int) { default }
|
|
||||||
|
|
||||||
public fun ItemProvider.long(default: Long, key: Name? = null): ReadOnlyProperty<Any?, Long> =
|
|
||||||
item(key).convert(MetaConverter.long) { default }
|
|
||||||
|
|
||||||
public inline fun <reified E : Enum<E>> ItemProvider.enum(default: E, key: Name? = null): ReadOnlyProperty<Any?, E> =
|
|
||||||
item(key).convert(MetaConverter.enum()) { default }
|
|
||||||
|
|
||||||
public fun ItemProvider.string(key: Name? = null, default: () -> String): ReadOnlyProperty<Any?, String> =
|
|
||||||
item(key).convert(MetaConverter.string, default)
|
|
||||||
|
|
||||||
public fun ItemProvider.boolean(key: Name? = null, default: () -> Boolean): ReadOnlyProperty<Any?, Boolean> =
|
|
||||||
item(key).convert(MetaConverter.boolean, default)
|
|
||||||
|
|
||||||
public fun ItemProvider.number(key: Name? = null, default: () -> Number): ReadOnlyProperty<Any?, Number> =
|
|
||||||
item(key).convert(MetaConverter.number, default)
|
|
@ -1,88 +0,0 @@
|
|||||||
package space.kscience.dataforge.meta
|
|
||||||
|
|
||||||
import space.kscience.dataforge.names.*
|
|
||||||
|
|
||||||
public fun interface ItemProvider {
|
|
||||||
//getItem used instead of get in order to provide extension freedom
|
|
||||||
public fun getItem(name: Name): MetaItem?
|
|
||||||
|
|
||||||
public companion object {
|
|
||||||
public val EMPTY: ItemProvider = ItemProvider { null }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Get operations*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform recursive item search using given [name]. Each [NameToken] is treated as a name in [Meta.items] of a parent node.
|
|
||||||
*
|
|
||||||
* If [name] is empty return current [Meta] as a [MetaItemNode]
|
|
||||||
*/
|
|
||||||
public operator fun ItemProvider?.get(name: Name): MetaItem? = this?.getItem(name)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Root item of this provider
|
|
||||||
*/
|
|
||||||
public val ItemProvider.rootItem: MetaItem? get() = get(Name.EMPTY)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The root node of this item provider if it is present
|
|
||||||
*/
|
|
||||||
public val ItemProvider.rootNode: Meta? get() = rootItem.node
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse [Name] from [key] using full name notation and pass it to [Meta.get]
|
|
||||||
*/
|
|
||||||
public operator fun ItemProvider?.get(key: String): MetaItem? = this?.get(key.toName())
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a provider that uses given provider for default values if those are not found in this provider
|
|
||||||
*/
|
|
||||||
public fun ItemProvider.withDefault(default: ItemProvider?): ItemProvider = if (default == null) {
|
|
||||||
this
|
|
||||||
} else {
|
|
||||||
ItemProvider {
|
|
||||||
this[it] ?: default[it]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all items matching given name. The index of the last element, if present is used as a [Regex],
|
|
||||||
* against which indexes of elements are matched.
|
|
||||||
*/
|
|
||||||
public fun ItemProvider.getIndexed(name: Name): Map<String?, MetaItem> {
|
|
||||||
val root: Meta = when (name.length) {
|
|
||||||
0 -> error("Can't use empty name for 'getIndexed'")
|
|
||||||
1 -> this.rootNode ?: return emptyMap()
|
|
||||||
else -> this[name.cutLast()].node ?: return emptyMap()
|
|
||||||
}
|
|
||||||
|
|
||||||
val (body, index) = name.lastOrNull()!!
|
|
||||||
return if (index == null) {
|
|
||||||
root.items.filter { it.key.body == body }.mapKeys { it.key.index }
|
|
||||||
} else {
|
|
||||||
val regex = index.toRegex()
|
|
||||||
root.items.filter { it.key.body == body && (regex.matches(it.key.index ?: "")) }
|
|
||||||
.mapKeys { it.key.index }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun ItemProvider.getIndexed(name: String): Map<String?, MetaItem> = getIndexed(name.toName())
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a provider referencing a child node
|
|
||||||
*/
|
|
||||||
public fun ItemProvider.getChild(childName: Name): ItemProvider = get(childName).node ?: ItemProvider.EMPTY
|
|
||||||
|
|
||||||
public fun ItemProvider.getChild(childName: String): ItemProvider = getChild(childName.toName())
|
|
||||||
|
|
||||||
///**
|
|
||||||
// * Get all items matching given name.
|
|
||||||
// */
|
|
||||||
//@Suppress("UNCHECKED_CAST")
|
|
||||||
//public fun <M : TypedMeta<M>> M.getIndexed(name: Name): Map<String?, MetaItem<M>> =
|
|
||||||
// (this as Meta).getIndexed(name) as Map<String?, MetaItem<M>>
|
|
||||||
//
|
|
||||||
//public fun <M : TypedMeta<M>> M.getIndexed(name: String): Map<String?, MetaItem<M>> =
|
|
||||||
// getIndexed(name.toName())
|
|
@ -3,20 +3,15 @@
|
|||||||
package space.kscience.dataforge.meta
|
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)
|
|
||||||
}
|
|
||||||
is MetaItemNode -> {
|
|
||||||
node.toJsonWithIndex(itemDescriptor as? NodeDescriptor, index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addElement(key: String) {
|
|
||||||
val itemDescriptor = descriptor?.items?.get(key)
|
|
||||||
val jsonKey = key.toJsonKey(itemDescriptor)
|
|
||||||
val items: Map<String?, MetaItem> = getIndexed(key)
|
|
||||||
when (items.size) {
|
|
||||||
0 -> {
|
|
||||||
//do nothing
|
|
||||||
}
|
|
||||||
1 -> {
|
|
||||||
val (index, item) = items.entries.first()
|
|
||||||
val element = item.toJsonElement(itemDescriptor, index)
|
|
||||||
if (index == null) {
|
|
||||||
elementMap[jsonKey] = element
|
|
||||||
} else {
|
} else {
|
||||||
//treat arrays with single element
|
val elements: List<JsonElement> = list.sortedBy { it.key.index }.mapIndexed { index, entry ->
|
||||||
elementMap[jsonKey] = buildJsonArray {
|
//Use index if it is not equal to the item order
|
||||||
add(element)
|
val actualIndex = if (index.toString() != entry.key.index) entry.key.index else null
|
||||||
}
|
entry.value.toJsonWithIndex(childDescriptor, actualIndex)
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
val array = buildJsonArray {
|
|
||||||
items.forEach { (index, item) ->
|
|
||||||
add(item.toJsonElement(itemDescriptor, index))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
elementMap[jsonKey] = array
|
|
||||||
}
|
}
|
||||||
|
body to JsonArray(elements)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
((descriptor?.items?.keys ?: emptySet()) + items.keys.map { it.body }).forEach(::addElement)
|
//Add index if needed
|
||||||
|
if (index != null) {
|
||||||
|
pairs += Meta.INDEX_KEY to JsonPrimitive(index)
|
||||||
if (indexValue != null) {
|
|
||||||
val indexKey = descriptor?.indexKey ?: DEFAULT_INDEX_KEY
|
|
||||||
elementMap[indexKey] = JsonPrimitive(indexValue)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return JsonObject(elementMap)
|
//Add value if needed
|
||||||
|
if (value != null) {
|
||||||
|
pairs += Meta.VALUE_KEY to value!!.toJson(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
/**
|
/**
|
||||||
|
@ -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,15 +43,12 @@ 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
|
|
||||||
first().seal()
|
|
||||||
all { it is MetaItemNode } -> {
|
|
||||||
//list nodes in item
|
//list nodes in item
|
||||||
val nodes = map { (it as MetaItemNode).node }
|
val nodes = toList()
|
||||||
//represent as key->value entries
|
//represent as key->value entries
|
||||||
val entries = nodes.flatMap { it.items.entries.asSequence() }
|
val entries = nodes.flatMap { it.items.entries.asSequence() }
|
||||||
//group by keys
|
//group by keys
|
||||||
@ -57,16 +57,7 @@ public class Laminate(layers: List<Meta>) : MetaBase() {
|
|||||||
val items = groups.mapValues { entry ->
|
val items = groups.mapValues { entry ->
|
||||||
entry.value.asSequence().map { it.value }.merge()
|
entry.value.asSequence().map { it.value }.merge()
|
||||||
}
|
}
|
||||||
MetaItemNode(SealedMeta(items))
|
return SealedMeta(value,items)
|
||||||
|
|
||||||
}
|
|
||||||
else -> map {
|
|
||||||
when (it) {
|
|
||||||
is MetaItemValue -> MetaItemNode(Meta { Meta.VALUE_KEY put it.value })
|
|
||||||
is MetaItemNode -> it
|
|
||||||
}
|
|
||||||
}.merge()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -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 }
|
||||||
}
|
}
|
||||||
|
@ -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, Meta>> = nodeSequence().iterator()
|
||||||
|
|
||||||
|
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 operator fun Meta.iterator(): Iterator<Pair<Name, MetaItem>> = itemSequence().iterator()
|
public val Meta.stringList: List<String>? get() = value?.list?.map { it.string }
|
||||||
|
|
||||||
public fun Meta.isEmpty(): Boolean = this === Meta.EMPTY || this.items.isEmpty()
|
/**
|
||||||
|
* Create a provider that uses given provider for default values if those are not found in this provider
|
||||||
|
*/
|
||||||
|
public fun Meta.withDefault(default: Meta?): Meta = if (default == null) {
|
||||||
|
this
|
||||||
|
} else {
|
||||||
|
Laminate(this, default)
|
||||||
|
}
|
@ -1,146 +0,0 @@
|
|||||||
package space.kscience.dataforge.meta
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import space.kscience.dataforge.misc.DFBuilder
|
|
||||||
import space.kscience.dataforge.names.Name
|
|
||||||
import space.kscience.dataforge.names.NameToken
|
|
||||||
import space.kscience.dataforge.names.asName
|
|
||||||
import space.kscience.dataforge.values.EnumValue
|
|
||||||
import space.kscience.dataforge.values.Value
|
|
||||||
import space.kscience.dataforge.values.asValue
|
|
||||||
import kotlin.jvm.JvmName
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DSL builder for meta. Is not intended to store mutable state
|
|
||||||
*/
|
|
||||||
@DFBuilder
|
|
||||||
@Serializable
|
|
||||||
public class MetaBuilder : AbstractMutableMeta<MetaBuilder>() {
|
|
||||||
override val children: MutableMap<NameToken, TypedMetaItem<MetaBuilder>> = LinkedHashMap()
|
|
||||||
|
|
||||||
override fun wrapNode(meta: Meta): MetaBuilder = if (meta is MetaBuilder) meta else meta.toMutableMeta()
|
|
||||||
override fun empty(): MetaBuilder = MetaBuilder()
|
|
||||||
|
|
||||||
public infix fun String.put(item: MetaItem?) {
|
|
||||||
set(this, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
public infix fun String.put(value: Value?) {
|
|
||||||
set(this, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
public infix fun String.put(string: String?) {
|
|
||||||
set(this, string?.asValue())
|
|
||||||
}
|
|
||||||
|
|
||||||
public infix fun String.put(number: Number?) {
|
|
||||||
set(this, number?.asValue())
|
|
||||||
}
|
|
||||||
|
|
||||||
public infix fun String.put(boolean: Boolean?) {
|
|
||||||
set(this, boolean?.asValue())
|
|
||||||
}
|
|
||||||
|
|
||||||
public infix fun String.put(enum: Enum<*>) {
|
|
||||||
set(this, EnumValue(enum))
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmName("putValues")
|
|
||||||
public infix fun String.put(iterable: Iterable<Value>) {
|
|
||||||
set(this, iterable.asValue())
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmName("putNumbers")
|
|
||||||
public infix fun String.put(iterable: Iterable<Number>) {
|
|
||||||
set(this, iterable.map { it.asValue() }.asValue())
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmName("putStrings")
|
|
||||||
public infix fun String.put(iterable: Iterable<String>) {
|
|
||||||
set(this, iterable.map { it.asValue() }.asValue())
|
|
||||||
}
|
|
||||||
|
|
||||||
public infix fun String.put(array: DoubleArray) {
|
|
||||||
set(this, array.asValue())
|
|
||||||
}
|
|
||||||
|
|
||||||
public infix fun String.put(meta: Meta?) {
|
|
||||||
this@MetaBuilder[this] = meta
|
|
||||||
}
|
|
||||||
|
|
||||||
public infix fun String.put(repr: MetaRepr?) {
|
|
||||||
set(this, repr?.toMeta())
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmName("putMetas")
|
|
||||||
public infix fun String.put(value: Iterable<Meta>) {
|
|
||||||
set(this,value.toList())
|
|
||||||
}
|
|
||||||
|
|
||||||
public inline infix fun String.put(metaBuilder: MetaBuilder.() -> Unit) {
|
|
||||||
this@MetaBuilder[this] = MetaBuilder().apply(metaBuilder)
|
|
||||||
}
|
|
||||||
|
|
||||||
public infix fun Name.put(value: Value?) {
|
|
||||||
set(this, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
public infix fun Name.put(string: String?) {
|
|
||||||
set(this, string?.asValue())
|
|
||||||
}
|
|
||||||
|
|
||||||
public infix fun Name.put(number: Number?) {
|
|
||||||
set(this, number?.asValue())
|
|
||||||
}
|
|
||||||
|
|
||||||
public infix fun Name.put(boolean: Boolean?) {
|
|
||||||
set(this, boolean?.asValue())
|
|
||||||
}
|
|
||||||
|
|
||||||
public infix fun Name.put(enum: Enum<*>) {
|
|
||||||
set(this, EnumValue(enum))
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmName("putValues")
|
|
||||||
public infix fun Name.put(iterable: Iterable<Value>) {
|
|
||||||
set(this, iterable.asValue())
|
|
||||||
}
|
|
||||||
|
|
||||||
public infix fun Name.put(meta: Meta?) {
|
|
||||||
this@MetaBuilder[this] = meta
|
|
||||||
}
|
|
||||||
|
|
||||||
public infix fun Name.put(repr: MetaRepr?) {
|
|
||||||
set(this, repr?.toMeta())
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmName("putMetas")
|
|
||||||
public infix fun Name.put(value: Iterable<Meta>) {
|
|
||||||
set(this, value.toList())
|
|
||||||
}
|
|
||||||
|
|
||||||
public infix fun Name.put(metaBuilder: MetaBuilder.() -> Unit) {
|
|
||||||
this@MetaBuilder[this] = MetaBuilder().apply(metaBuilder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For safety, builder always copies the initial meta even if it is builder itself
|
|
||||||
*/
|
|
||||||
public fun Meta.toMutableMeta(): MetaBuilder {
|
|
||||||
return MetaBuilder().also { builder ->
|
|
||||||
items.mapValues { entry ->
|
|
||||||
val item = entry.value
|
|
||||||
builder[entry.key.asName()] = when (item) {
|
|
||||||
is MetaItemValue -> item.value
|
|
||||||
is MetaItemNode -> MetaItemNode(item.node.toMutableMeta())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build a [MetaBuilder] using given transformation
|
|
||||||
*/
|
|
||||||
@Suppress("FunctionName")
|
|
||||||
public inline fun Meta(builder: MetaBuilder.() -> Unit): MetaBuilder = MetaBuilder().apply(builder)
|
|
@ -0,0 +1,111 @@
|
|||||||
|
package space.kscience.dataforge.meta
|
||||||
|
|
||||||
|
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||||
|
import space.kscience.dataforge.names.Name
|
||||||
|
import space.kscience.dataforge.names.asName
|
||||||
|
import space.kscience.dataforge.values.Value
|
||||||
|
import kotlin.properties.ReadOnlyProperty
|
||||||
|
|
||||||
|
/* Meta delegates */
|
||||||
|
|
||||||
|
public typealias MetaDelegate = ReadOnlyProperty<Any?, Meta?>
|
||||||
|
|
||||||
|
public fun Meta.item(key: Name? = null): MetaDelegate = ReadOnlyProperty { _, property ->
|
||||||
|
get(key ?: property.name.asName())
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO add caching for sealed nodes
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a converter to this delegate creating a delegate with a custom type
|
||||||
|
*/
|
||||||
|
public fun <R : Any> MetaDelegate.convert(
|
||||||
|
converter: MetaConverter<R>,
|
||||||
|
): ReadOnlyProperty<Any?, R?> = ReadOnlyProperty { thisRef, property ->
|
||||||
|
this@convert.getValue(thisRef, property)?.let(converter::itemToObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public fun <R : Any> MetaDelegate.convert(
|
||||||
|
converter: MetaConverter<R>,
|
||||||
|
default: () -> R,
|
||||||
|
): ReadOnlyProperty<Any?, R> = ReadOnlyProperty<Any?, R> { thisRef, property ->
|
||||||
|
this@convert.getValue(thisRef, property)?.let(converter::itemToObject) ?: default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A converter with a custom reader transformation
|
||||||
|
*/
|
||||||
|
public fun <R> MetaDelegate.convert(
|
||||||
|
reader: (Meta?) -> R,
|
||||||
|
): ReadOnlyProperty<Any?, R> = ReadOnlyProperty<Any?, R> { thisRef, property ->
|
||||||
|
this@convert.getValue(thisRef, property).let(reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read-only delegates for [Meta] */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A property delegate that uses custom key
|
||||||
|
*/
|
||||||
|
public fun Meta.value(key: Name? = null): ReadOnlyProperty<Any?, Value?> =
|
||||||
|
item(key).convert(MetaConverter.value)
|
||||||
|
|
||||||
|
public fun Meta.string(key: Name? = null): ReadOnlyProperty<Any?, String?> =
|
||||||
|
item(key).convert(MetaConverter.string)
|
||||||
|
|
||||||
|
public fun Meta.boolean(key: Name? = null): ReadOnlyProperty<Any?, Boolean?> =
|
||||||
|
item(key).convert(MetaConverter.boolean)
|
||||||
|
|
||||||
|
public fun Meta.number(key: Name? = null): ReadOnlyProperty<Any?, Number?> =
|
||||||
|
item(key).convert(MetaConverter.number)
|
||||||
|
|
||||||
|
public fun Meta.double(key: Name? = null): ReadOnlyProperty<Any?, Double?> =
|
||||||
|
item(key).convert(MetaConverter.double)
|
||||||
|
|
||||||
|
public fun Meta.float(key: Name? = null): ReadOnlyProperty<Any?, Float?> =
|
||||||
|
item(key).convert(MetaConverter.float)
|
||||||
|
|
||||||
|
public fun Meta.int(key: Name? = null): ReadOnlyProperty<Any?, Int?> =
|
||||||
|
item(key).convert(MetaConverter.int)
|
||||||
|
|
||||||
|
public fun Meta.long(key: Name? = null): ReadOnlyProperty<Any?, Long?> =
|
||||||
|
item(key).convert(MetaConverter.long)
|
||||||
|
|
||||||
|
public fun Meta.node(key: Name? = null): ReadOnlyProperty<Any?, Meta?> =
|
||||||
|
item(key).convert(MetaConverter.meta)
|
||||||
|
|
||||||
|
public fun Meta.string(default: String, key: Name? = null): ReadOnlyProperty<Any?, String> =
|
||||||
|
item(key).convert(MetaConverter.string) { default }
|
||||||
|
|
||||||
|
public fun Meta.boolean(default: Boolean, key: Name? = null): ReadOnlyProperty<Any?, Boolean> =
|
||||||
|
item(key).convert(MetaConverter.boolean) { default }
|
||||||
|
|
||||||
|
public fun Meta.number(default: Number, key: Name? = null): ReadOnlyProperty<Any?, Number> =
|
||||||
|
item(key).convert(MetaConverter.number) { default }
|
||||||
|
|
||||||
|
public fun Meta.double(default: Double, key: Name? = null): ReadOnlyProperty<Any?, Double> =
|
||||||
|
item(key).convert(MetaConverter.double) { default }
|
||||||
|
|
||||||
|
public fun Meta.float(default: Float, key: Name? = null): ReadOnlyProperty<Any?, Float> =
|
||||||
|
item(key).convert(MetaConverter.float) { default }
|
||||||
|
|
||||||
|
public fun Meta.int(default: Int, key: Name? = null): ReadOnlyProperty<Any?, Int> =
|
||||||
|
item(key).convert(MetaConverter.int) { default }
|
||||||
|
|
||||||
|
public fun Meta.long(default: Long, key: Name? = null): ReadOnlyProperty<Any?, Long> =
|
||||||
|
item(key).convert(MetaConverter.long) { default }
|
||||||
|
|
||||||
|
public inline fun <reified E : Enum<E>> Meta.enum(default: E, key: Name? = null): ReadOnlyProperty<Any?, E> =
|
||||||
|
item(key).convert(MetaConverter.enum()) { default }
|
||||||
|
|
||||||
|
public fun Meta.string(key: Name? = null, default: () -> String): ReadOnlyProperty<Any?, String> =
|
||||||
|
item(key).convert(MetaConverter.string, default)
|
||||||
|
|
||||||
|
public fun Meta.boolean(key: Name? = null, default: () -> Boolean): ReadOnlyProperty<Any?, Boolean> =
|
||||||
|
item(key).convert(MetaConverter.boolean, default)
|
||||||
|
|
||||||
|
public fun Meta.number(key: Name? = null, default: () -> Number): ReadOnlyProperty<Any?, Number> =
|
||||||
|
item(key).convert(MetaConverter.number, default)
|
@ -1,96 +0,0 @@
|
|||||||
package space.kscience.dataforge.meta
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import space.kscience.dataforge.names.Name
|
|
||||||
import space.kscience.dataforge.names.isEmpty
|
|
||||||
import space.kscience.dataforge.values.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A member of the meta tree. Could be represented as one of following:
|
|
||||||
* * a [MetaItemValue] (leaf)
|
|
||||||
* * a [MetaItemNode] (node)
|
|
||||||
*/
|
|
||||||
@Serializable(MetaItemSerializer::class)
|
|
||||||
public sealed class TypedMetaItem<out M : Meta> : ItemProvider {
|
|
||||||
|
|
||||||
abstract override fun equals(other: Any?): Boolean
|
|
||||||
|
|
||||||
abstract override fun hashCode(): Int
|
|
||||||
|
|
||||||
public companion object {
|
|
||||||
public fun of(arg: Any?): MetaItem {
|
|
||||||
return when (arg) {
|
|
||||||
null -> Null.asMetaItem()
|
|
||||||
is MetaItem -> arg
|
|
||||||
is Meta -> arg.asMetaItem()
|
|
||||||
is ItemProvider -> arg.rootItem ?: Null.asMetaItem()
|
|
||||||
else -> Value.of(arg).asMetaItem()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public typealias MetaItem = TypedMetaItem<*>
|
|
||||||
|
|
||||||
@Serializable(MetaItemSerializer::class)
|
|
||||||
public class MetaItemValue(public val value: Value) : TypedMetaItem<Nothing>() {
|
|
||||||
override fun getItem(name: Name): MetaItem? = if (name.isEmpty()) this else null
|
|
||||||
|
|
||||||
override fun toString(): String = value.toString()
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
return this.value == (other as? MetaItemValue)?.value
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
return value.hashCode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable(MetaItemSerializer::class)
|
|
||||||
public class MetaItemNode<M : Meta>(public val node: M) : TypedMetaItem<M>() {
|
|
||||||
override fun getItem(name: Name): MetaItem? = if (name.isEmpty()) this else node.getItem(name)
|
|
||||||
|
|
||||||
//Fixing serializer for node could cause class cast problems, but it should not since Meta descendants are not serializable
|
|
||||||
override fun toString(): String = node.toString()
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean = Meta.equals(node, (other as? MetaItemNode<*>)?.node)
|
|
||||||
|
|
||||||
override fun hashCode(): Int = node.hashCode()
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun Value.asMetaItem(): MetaItemValue = MetaItemValue(this)
|
|
||||||
public fun <M : Meta> M.asMetaItem(): MetaItemNode<M> = MetaItemNode(this)
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unsafe methods to access values and nodes directly from [TypedMetaItem]
|
|
||||||
*/
|
|
||||||
public val MetaItem?.value: Value?
|
|
||||||
get() = (this as? MetaItemValue)?.value
|
|
||||||
?: (this?.node?.get(Meta.VALUE_KEY) as? MetaItemValue)?.value
|
|
||||||
|
|
||||||
public val MetaItem?.string: String? get() = value?.string
|
|
||||||
public val MetaItem?.boolean: Boolean? get() = value?.boolean
|
|
||||||
public val MetaItem?.number: Number? get() = value?.numberOrNull
|
|
||||||
public val MetaItem?.double: Double? get() = number?.toDouble()
|
|
||||||
public val MetaItem?.float: Float? get() = number?.toFloat()
|
|
||||||
public val MetaItem?.int: Int? get() = number?.toInt()
|
|
||||||
public val MetaItem?.long: Long? get() = number?.toLong()
|
|
||||||
public val MetaItem?.short: Short? get() = number?.toShort()
|
|
||||||
|
|
||||||
public inline fun <reified E : Enum<E>> MetaItem?.enum(): E? =
|
|
||||||
if (this is MetaItemValue && this.value is EnumValue<*>) {
|
|
||||||
this.value.value as E
|
|
||||||
} else {
|
|
||||||
string?.let { enumValueOf<E>(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
public val MetaItem.stringList: List<String>? get() = value?.list?.map { it.string }
|
|
||||||
|
|
||||||
public val <M : Meta> TypedMetaItem<M>?.node: M?
|
|
||||||
get() = when (this) {
|
|
||||||
null -> null
|
|
||||||
is MetaItemValue -> null//error("Trying to interpret value meta item as node item")
|
|
||||||
is MetaItemNode -> node
|
|
||||||
}
|
|
@ -1,80 +1,46 @@
|
|||||||
package space.kscience.dataforge.meta
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,153 +0,0 @@
|
|||||||
package space.kscience.dataforge.meta
|
|
||||||
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
|
||||||
import kotlinx.serialization.encoding.Decoder
|
|
||||||
import kotlinx.serialization.encoding.Encoder
|
|
||||||
import space.kscience.dataforge.names.*
|
|
||||||
import space.kscience.dataforge.values.Value
|
|
||||||
|
|
||||||
@Serializable(MutableItemProviderSerializer::class)
|
|
||||||
public interface MutableItemProvider : ItemProvider {
|
|
||||||
public fun setItem(name: Name, item: MetaItem?)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A serializer form [MutableItemProvider]
|
|
||||||
*/
|
|
||||||
public class MutableItemProviderSerializer : KSerializer<MutableItemProvider> {
|
|
||||||
override val descriptor: SerialDescriptor = MetaSerializer.descriptor
|
|
||||||
|
|
||||||
|
|
||||||
override fun deserialize(decoder: Decoder): MutableItemProvider {
|
|
||||||
val meta = decoder.decodeSerializableValue(MetaSerializer)
|
|
||||||
return (meta as? MetaBuilder) ?: meta.toMutableMeta()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, value: MutableItemProvider) {
|
|
||||||
encoder.encodeSerializableValue(MetaSerializer, value.rootItem?.node ?: Meta.EMPTY)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public operator fun MutableItemProvider.set(name: Name, item: MetaItem?): Unit = setItem(name, item)
|
|
||||||
|
|
||||||
public operator fun MutableItemProvider.set(name: Name, value: Value?): Unit = set(name, value?.asMetaItem())
|
|
||||||
|
|
||||||
public operator fun MutableItemProvider.set(name: Name, meta: Meta?): Unit = set(name, meta?.asMetaItem())
|
|
||||||
|
|
||||||
public operator fun MutableItemProvider.set(key: String, item: MetaItem?): Unit = set(key.toName(), item)
|
|
||||||
|
|
||||||
public operator fun MutableItemProvider.set(key: String, meta: Meta?): Unit = set(key, meta?.asMetaItem())
|
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
|
||||||
public inline fun MutableItemProvider.remove(name: Name): Unit = setItem(name, null)
|
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
|
||||||
public inline fun MutableItemProvider.remove(name: String): Unit = remove(name.toName())
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Universal unsafe set method
|
|
||||||
*/
|
|
||||||
public operator fun MutableItemProvider.set(name: Name, value: Any?) {
|
|
||||||
when (value) {
|
|
||||||
null -> remove(name)
|
|
||||||
else -> set(name, MetaItem.of(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public operator fun MutableItemProvider.set(name: NameToken, value: Any?): Unit =
|
|
||||||
set(name.asName(), value)
|
|
||||||
|
|
||||||
public operator fun MutableItemProvider.set(key: String, value: Any?): Unit =
|
|
||||||
set(key.toName(), value)
|
|
||||||
|
|
||||||
public operator fun MutableItemProvider.set(key: String, index: String, value: Any?): Unit =
|
|
||||||
set(key.toName().withIndex(index), value)
|
|
||||||
|
|
||||||
|
|
||||||
/* Same name siblings generation */
|
|
||||||
|
|
||||||
public fun MutableItemProvider.setIndexedItems(
|
|
||||||
name: Name,
|
|
||||||
items: Iterable<MetaItem>,
|
|
||||||
indexFactory: (MetaItem, index: Int) -> String = { _, index -> index.toString() },
|
|
||||||
) {
|
|
||||||
val tokens = name.tokens.toMutableList()
|
|
||||||
val last = tokens.last()
|
|
||||||
items.forEachIndexed { index, meta ->
|
|
||||||
val indexedToken = NameToken(last.body, (last.index ?: "") + indexFactory(meta, index))
|
|
||||||
tokens[tokens.lastIndex] = indexedToken
|
|
||||||
set(Name(tokens), meta)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun MutableItemProvider.setIndexed(
|
|
||||||
name: Name,
|
|
||||||
metas: Iterable<Meta>,
|
|
||||||
indexFactory: (Meta, index: Int) -> String = { _, index -> index.toString() },
|
|
||||||
) {
|
|
||||||
setIndexedItems(name, metas.map { MetaItemNode(it) }) { item, index -> indexFactory(item.node!!, index) }
|
|
||||||
}
|
|
||||||
|
|
||||||
public operator fun MutableItemProvider.set(name: Name, metas: Iterable<Meta>): Unit = setIndexed(name, metas)
|
|
||||||
public operator fun MutableItemProvider.set(name: String, metas: Iterable<Meta>): Unit =
|
|
||||||
setIndexed(name.toName(), metas)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a [MutableItemProvider] referencing a child node
|
|
||||||
*/
|
|
||||||
public fun MutableItemProvider.getChild(childName: Name): MutableItemProvider {
|
|
||||||
fun createProvider() = object : MutableItemProvider {
|
|
||||||
override fun setItem(name: Name, item: MetaItem?) {
|
|
||||||
this@getChild.setItem(childName + name, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItem(name: Name): MetaItem? = this@getChild.getItem(childName + name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return when {
|
|
||||||
childName.isEmpty() -> this
|
|
||||||
this is MutableMeta<*> -> {
|
|
||||||
get(childName).node ?: createProvider()
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
createProvider()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun MutableItemProvider.getChild(childName: String): MutableItemProvider = getChild(childName.toName())
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update existing mutable node with another node. The rules are following:
|
|
||||||
* * value replaces anything
|
|
||||||
* * node updates node and replaces anything but node
|
|
||||||
* * node list updates node list if number of nodes in the list is the same and replaces anything otherwise
|
|
||||||
*/
|
|
||||||
public fun MutableItemProvider.update(meta: Meta) {
|
|
||||||
meta.valueSequence().forEach { (name, value) -> set(name, value) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Edit a provider child at given name location
|
|
||||||
*/
|
|
||||||
public fun MutableItemProvider.editChild(name: Name, builder: MutableItemProvider.() -> Unit): MutableItemProvider =
|
|
||||||
getChild(name).apply(builder)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a mutable item provider that uses given provider for default values if those are not found in this provider.
|
|
||||||
* Changes are propagated only to this provider.
|
|
||||||
*/
|
|
||||||
public fun MutableItemProvider.withDefault(default: ItemProvider?): MutableItemProvider =
|
|
||||||
if (default == null || (default is Meta && default.isEmpty())) {
|
|
||||||
//Optimize for use with empty default
|
|
||||||
this
|
|
||||||
} else object : MutableItemProvider {
|
|
||||||
override fun setItem(name: Name, item: MetaItem?) {
|
|
||||||
this@withDefault.setItem(name, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItem(name: Name): MetaItem? = this@withDefault.getItem(name) ?: default.getItem(name)
|
|
||||||
}
|
|
@ -1,74 +1,352 @@
|
|||||||
package space.kscience.dataforge.meta
|
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>?) {
|
/**
|
||||||
|
* Get existing node or create a new one
|
||||||
|
*/
|
||||||
|
public fun getOrCreate(name: Name): MutableMeta
|
||||||
|
|
||||||
|
//TODO to be moved to extensions with multi-receivers
|
||||||
|
|
||||||
|
public infix fun Name.put(value: Value?) {
|
||||||
|
set(this, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
public infix fun Name.put(string: String) {
|
||||||
|
set(this, string.asValue())
|
||||||
|
}
|
||||||
|
|
||||||
|
public infix fun Name.put(number: Number) {
|
||||||
|
set(this, number.asValue())
|
||||||
|
}
|
||||||
|
|
||||||
|
public infix fun Name.put(boolean: Boolean) {
|
||||||
|
set(this, boolean.asValue())
|
||||||
|
}
|
||||||
|
|
||||||
|
public infix fun Name.put(enum: Enum<*>) {
|
||||||
|
set(this, EnumValue(enum))
|
||||||
|
}
|
||||||
|
|
||||||
|
public infix fun Name.put(iterable: Iterable<Meta>) {
|
||||||
|
setIndexed(this, iterable)
|
||||||
|
}
|
||||||
|
|
||||||
|
public infix fun Name.put(meta: Meta) {
|
||||||
|
set(this, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
public infix fun Name.put(repr: MetaRepr) {
|
||||||
|
put(repr.toMeta())
|
||||||
|
}
|
||||||
|
|
||||||
|
public infix fun Name.put(mutableMeta: MutableMeta.() -> Unit) {
|
||||||
|
set(this, Meta(mutableMeta))
|
||||||
|
}
|
||||||
|
|
||||||
|
public infix fun String.put(meta: Meta) {
|
||||||
|
toName() put meta
|
||||||
|
}
|
||||||
|
|
||||||
|
public infix fun String.put(value: Value?) {
|
||||||
|
set(toName(), value)
|
||||||
|
}
|
||||||
|
|
||||||
|
public infix fun String.put(string: String) {
|
||||||
|
set(toName(), string.asValue())
|
||||||
|
}
|
||||||
|
|
||||||
|
public infix fun String.put(number: Number) {
|
||||||
|
set(toName(), number.asValue())
|
||||||
|
}
|
||||||
|
|
||||||
|
public infix fun String.put(boolean: Boolean) {
|
||||||
|
set(toName(), boolean.asValue())
|
||||||
|
}
|
||||||
|
|
||||||
|
public infix fun String.put(enum: Enum<*>) {
|
||||||
|
set(toName(), EnumValue(enum))
|
||||||
|
}
|
||||||
|
|
||||||
|
public infix fun String.put(array: DoubleArray) {
|
||||||
|
set(toName(), array.asValue())
|
||||||
|
}
|
||||||
|
|
||||||
|
public infix fun String.put(repr: MetaRepr) {
|
||||||
|
toName() put repr.toMeta()
|
||||||
|
}
|
||||||
|
|
||||||
|
public infix fun String.put(builder: MutableMeta.() -> Unit) {
|
||||||
|
set(toName(), MutableMeta(builder))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable(MutableMetaSerializer::class)
|
||||||
|
public interface MutableTypedMeta<M : MutableTypedMeta<M>> : TypedMeta<M>, MutableMeta {
|
||||||
|
/**
|
||||||
|
* Zero-copy attach or replace existing node. Node is used with any additional state, listeners, etc.
|
||||||
|
* In some cases it is possible to have the same node as a child to several others
|
||||||
|
*/
|
||||||
|
public fun attach(name: Name, node: M)
|
||||||
|
|
||||||
|
override fun getOrCreate(name: Name): M
|
||||||
|
}
|
||||||
|
|
||||||
|
public operator fun MutableMeta.set(key: String, item: Meta?): Unit =
|
||||||
|
set(key.toName(), item)
|
||||||
|
|
||||||
|
|
||||||
|
public fun MutableMeta.remove(name: Name): Unit = set(name, null)
|
||||||
|
|
||||||
|
public fun MutableMeta.remove(name: String): Unit = remove(name.toName())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Universal unsafe set method
|
||||||
|
*/
|
||||||
|
public operator fun MutableMeta.set(name: Name, value: Any?) {
|
||||||
|
when (value) {
|
||||||
|
null -> remove(name)
|
||||||
|
else -> set(name, Value.of(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public operator fun MutableMeta.set(name: NameToken, value: Any?): Unit =
|
||||||
|
set(name.asName(), value)
|
||||||
|
|
||||||
|
public operator fun MutableMeta.set(key: String, value: Any?): Unit =
|
||||||
|
set(key.toName(), value)
|
||||||
|
|
||||||
|
public operator fun MutableMeta.set(key: String, index: String, value: Any?): Unit =
|
||||||
|
set(key.toName().withIndex(index), value)
|
||||||
|
|
||||||
|
|
||||||
|
/* Same name siblings generation */
|
||||||
|
|
||||||
|
public fun MutableMeta.setIndexedItems(
|
||||||
|
name: Name,
|
||||||
|
items: Iterable<Meta>,
|
||||||
|
indexFactory: (Meta, index: Int) -> String = { _, index -> index.toString() },
|
||||||
|
) {
|
||||||
|
val tokens = name.tokens.toMutableList()
|
||||||
|
val last = tokens.last()
|
||||||
|
items.forEachIndexed { index, meta ->
|
||||||
|
val indexedToken = NameToken(last.body, (last.index ?: "") + indexFactory(meta, index))
|
||||||
|
tokens[tokens.lastIndex] = indexedToken
|
||||||
|
set(Name(tokens), meta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun MutableMeta.setIndexed(
|
||||||
|
name: Name,
|
||||||
|
metas: Iterable<Meta>,
|
||||||
|
indexFactory: (Meta, index: Int) -> String = { _, index -> index.toString() },
|
||||||
|
) {
|
||||||
|
setIndexedItems(name, metas) { item, index -> indexFactory(item, index) }
|
||||||
|
}
|
||||||
|
|
||||||
|
public operator fun <M : MutableTypedMeta<M>> MutableTypedMeta<M>.set(name: Name, metas: Iterable<Meta>): Unit =
|
||||||
|
setIndexed(name, metas)
|
||||||
|
|
||||||
|
public operator fun <M : MutableTypedMeta<M>> MutableTypedMeta<M>.set(name: String, metas: Iterable<Meta>): Unit =
|
||||||
|
setIndexed(name.toName(), metas)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update existing mutable node with another node. The rules are following:
|
||||||
|
* * value replaces anything
|
||||||
|
* * node updates node and replaces anything but node
|
||||||
|
* * node list updates node list if number of nodes in the list is the same and replaces anything otherwise
|
||||||
|
*/
|
||||||
|
public fun <M : MutableTypedMeta<M>> MutableTypedMeta<M>.update(meta: Meta) {
|
||||||
|
meta.valueSequence().forEach { (name, value) ->
|
||||||
|
set(name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///**
|
||||||
|
// * Get child with given name or create a new one
|
||||||
|
// */
|
||||||
|
//public fun <M : MutableTypedMeta<M>> MutableTypedMeta<M>.getOrCreate(name: Name): M =
|
||||||
|
// get(name) ?: empty().also { attach(name, it) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edit node at [name]
|
||||||
|
*/
|
||||||
|
public fun <M : MutableTypedMeta<M>> MutableTypedMeta<M>.edit(name: Name, builder: M.() -> Unit): M =
|
||||||
|
getOrCreate(name).apply(builder)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a value at a given [name]. If node does not exist, create it.
|
||||||
|
*/
|
||||||
|
public operator fun <M : MutableTypedMeta<M>> MutableTypedMeta<M>.set(name: Name, value: Value?) {
|
||||||
|
edit(name) {
|
||||||
|
this.value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///**
|
||||||
|
// * Create a mutable item provider that uses given provider for default values if those are not found in this provider.
|
||||||
|
// * Changes are propagated only to this provider.
|
||||||
|
// */
|
||||||
|
//public fun <M : MutableTypedMeta<M>> M.withDefault(default: Meta?): MutableTypedMeta<*> =
|
||||||
|
// if (default == null || default.isEmpty()) {
|
||||||
|
// //Optimize for use with empty default
|
||||||
|
// this
|
||||||
|
// } else object : MutableTypedMeta<M> {
|
||||||
|
// override fun set(name: Name, item: MetaItem?) {
|
||||||
|
// this@withDefault.set(name, item)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override fun getItem(name: Name): MetaItem? = this@withDefault.getItem(name) ?: default.getItem(name)
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A general implementation of mutable [Meta] which implements both [MutableTypedMeta] and [ObservableMeta].
|
||||||
|
* The implementation uses blocking synchronization on mutation on JVM
|
||||||
|
*/
|
||||||
|
@DFBuilder
|
||||||
|
private class MutableMetaImpl(
|
||||||
|
override var value: Value?,
|
||||||
|
children: Map<NameToken, Meta> = emptyMap()
|
||||||
|
) : ObservableMutableMeta {
|
||||||
|
|
||||||
|
private val children: LinkedHashMap<NameToken, ObservableMutableMeta> =
|
||||||
|
LinkedHashMap(children.mapValues { (key, meta) ->
|
||||||
|
MutableMetaImpl(meta.value, meta.items).apply { adoptBy(this, key) }
|
||||||
|
})
|
||||||
|
|
||||||
|
override val items: Map<NameToken, ObservableMutableMeta> get() = children
|
||||||
|
|
||||||
|
private val listeners = HashSet<MetaListener>()
|
||||||
|
|
||||||
|
private fun changed(name: Name) {
|
||||||
|
listeners.forEach { it.callback(this, name) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit) {
|
||||||
|
listeners.add(MetaListener(owner, callback))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun removeListener(owner: Any?) {
|
||||||
|
listeners.removeAll { it.owner === owner }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun adoptBy(parent: MutableMetaImpl, key: NameToken) {
|
||||||
|
onChange(parent) { name ->
|
||||||
|
parent.changed(key + name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun attach(name: Name, node: ObservableMutableMeta) {
|
||||||
|
when (name.length) {
|
||||||
|
0 -> error("Can't set a meta with empty name")
|
||||||
|
1 -> {
|
||||||
|
val key = name.firstOrNull()!!
|
||||||
|
children[key] = node
|
||||||
|
adoptBy(this, key)
|
||||||
|
changed(name)
|
||||||
|
}
|
||||||
|
else -> get(name.cutLast())?.attach(name.lastOrNull()!!.asName(), node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getOrCreate(name: Name): ObservableMutableMeta =
|
||||||
|
get(name) ?: MutableMetaImpl(null).also { attach(name, it) }
|
||||||
|
|
||||||
|
override fun removeNode(name: Name) {
|
||||||
|
when (name.length) {
|
||||||
|
0 -> error("Can't remove self")
|
||||||
|
1 -> if (children.remove(name.firstOrNull()!!) != null) changed(name)
|
||||||
|
else -> get(name.cutLast())?.removeNode(name.lastOrNull()!!.asName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun replaceItem(
|
||||||
|
key: NameToken,
|
||||||
|
oldItem: ObservableMutableMeta?,
|
||||||
|
newItem: MutableMetaImpl?
|
||||||
|
) {
|
||||||
|
if (oldItem != newItem) {
|
||||||
if (newItem == null) {
|
if (newItem == null) {
|
||||||
children.remove(key)
|
children.remove(key)
|
||||||
} else {
|
} else {
|
||||||
|
newItem.adoptBy(this, key)
|
||||||
children[key] = newItem
|
children[key] = newItem
|
||||||
}
|
}
|
||||||
//itemChanged(key.asName(), oldItem, newItem)
|
changed(key.asName())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun wrapItem(item: MetaItem?): TypedMetaItem<M>? = when (item) {
|
private fun wrapItem(meta: Meta): MutableMetaImpl =
|
||||||
null -> null
|
MutableMetaImpl(meta.value, meta.items.mapValuesTo(LinkedHashMap()) { wrapItem(it.value) })
|
||||||
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
|
|
||||||
|
|
||||||
/**
|
override fun set(name: Name, meta: Meta) {
|
||||||
* Create empty node
|
val oldItem: ObservableMutableMeta? = get(name)
|
||||||
*/
|
if (oldItem != meta) {
|
||||||
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 token = name.firstOrNull()!!
|
||||||
val oldItem: TypedMetaItem<M>? = getItem(name)
|
replaceItem(token, oldItem, wrapItem(meta))
|
||||||
replaceItem(token, oldItem, wrapItem(item))
|
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val token = name.firstOrNull()!!
|
val token = name.firstOrNull()!!
|
||||||
//get existing or create new node. Query is ignored for new node
|
//get existing or create new node. Index is ignored for new node
|
||||||
if (items[token] == null) {
|
if (items[token] == null) {
|
||||||
replaceItem(token, null, MetaItemNode(empty()))
|
replaceItem(token, null, MutableMetaImpl(null))
|
||||||
}
|
}
|
||||||
items[token]?.node!!.set(name.cutFirst(), item)
|
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)
|
@ -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,
|
@ -1,159 +0,0 @@
|
|||||||
package space.kscience.dataforge.meta
|
|
||||||
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
|
||||||
import kotlinx.serialization.encoding.Decoder
|
|
||||||
import kotlinx.serialization.encoding.Encoder
|
|
||||||
import space.kscience.dataforge.names.*
|
|
||||||
import kotlin.js.JsName
|
|
||||||
import kotlin.jvm.Synchronized
|
|
||||||
import kotlin.reflect.KProperty1
|
|
||||||
|
|
||||||
|
|
||||||
internal data class ItemListener(
|
|
||||||
val owner: Any? = null,
|
|
||||||
val action: (name: Name, oldItem: MetaItem?, newItem: MetaItem?) -> Unit,
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An item provider that could be observed and mutated
|
|
||||||
*/
|
|
||||||
public interface ObservableItemProvider : ItemProvider, MutableItemProvider {
|
|
||||||
/**
|
|
||||||
* Add change listener to this meta. Owner is declared to be able to remove listeners later. Listener without owner could not be removed
|
|
||||||
*/
|
|
||||||
public fun onChange(owner: Any?, action: (name: Name, oldItem: MetaItem?, newItem: MetaItem?) -> Unit)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove all listeners belonging to given owner
|
|
||||||
*/
|
|
||||||
public fun removeListener(owner: Any?)
|
|
||||||
}
|
|
||||||
|
|
||||||
private open class ObservableItemProviderWrapper(
|
|
||||||
open val itemProvider: MutableItemProvider,
|
|
||||||
open val parent: Pair<ObservableItemProviderWrapper, Name>? = null
|
|
||||||
) : ObservableItemProvider {
|
|
||||||
|
|
||||||
override fun getItem(name: Name): MetaItem? = itemProvider.getItem(name)
|
|
||||||
|
|
||||||
private val listeners = HashSet<ItemListener>()
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
private fun itemChanged(name: Name, oldItem: MetaItem?, newItem: MetaItem?) {
|
|
||||||
listeners.forEach { it.action(name, oldItem, newItem) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setItem(name: Name, item: MetaItem?) {
|
|
||||||
val oldItem = getItem(name)
|
|
||||||
itemProvider.setItem(name, item)
|
|
||||||
itemChanged(name, oldItem, item)
|
|
||||||
|
|
||||||
//Recursively send update to parent listeners
|
|
||||||
parent?.let { (parentNode, token) ->
|
|
||||||
parentNode.itemChanged(token + name, oldItem, item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
override fun onChange(owner: Any?, action: (Name, MetaItem?, MetaItem?) -> Unit) {
|
|
||||||
listeners.add(ItemListener(owner, action))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
override fun removeListener(owner: Any?) {
|
|
||||||
listeners.removeAll { it.owner === owner }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun MutableItemProvider.asObservable(): ObservableItemProvider =
|
|
||||||
(this as? ObservableItemProvider) ?: ObservableItemProviderWrapper(this)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Meta instance that could be both mutated and observed.
|
|
||||||
*/
|
|
||||||
@Serializable(ObservableMetaSerializer::class)
|
|
||||||
public interface ObservableMeta : ObservableItemProvider, MutableMeta<ObservableMeta>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A wrapper class that creates observable meta node from regular meta node
|
|
||||||
*/
|
|
||||||
private class ObservableMetaWrapper<M : MutableMeta<M>>(
|
|
||||||
override val itemProvider: M,
|
|
||||||
override val parent: Pair<ObservableMetaWrapper<M>, Name>? = null
|
|
||||||
) : ObservableItemProviderWrapper(itemProvider, parent), ObservableMeta {
|
|
||||||
override fun equals(other: Any?): Boolean = (itemProvider == other)
|
|
||||||
|
|
||||||
override fun hashCode(): Int = itemProvider.hashCode()
|
|
||||||
|
|
||||||
override fun toString(): String = itemProvider.toString()
|
|
||||||
|
|
||||||
private fun wrapItem(name: Name, item: TypedMetaItem<M>): TypedMetaItem<ObservableMeta> {
|
|
||||||
return when (item) {
|
|
||||||
is MetaItemValue -> item
|
|
||||||
is MetaItemNode<M> -> ObservableMetaWrapper(item.node, this to name).asMetaItem()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItem(name: Name): TypedMetaItem<ObservableMeta>? = itemProvider[name]?.let {
|
|
||||||
wrapItem(name, it)
|
|
||||||
}
|
|
||||||
|
|
||||||
override val items: Map<NameToken, TypedMetaItem<ObservableMeta>>
|
|
||||||
get() = itemProvider.items.mapValues { (token, childItem: TypedMetaItem<M>) ->
|
|
||||||
wrapItem(token.asName(), childItem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If this meta is observable return itself. Otherwise return an observable wrapper. The changes of initial meta are
|
|
||||||
* reflected on wrapper but are **NOT** observed.
|
|
||||||
*/
|
|
||||||
public fun <M : MutableMeta<M>> M.asObservable(): ObservableMeta =
|
|
||||||
(this as? ObservableMeta) ?: ObservableMetaWrapper(this)
|
|
||||||
|
|
||||||
@JsName("buildObservableMeta")
|
|
||||||
public fun ObservableMeta(): ObservableMeta = MetaBuilder().asObservable()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use the value of the property in a [callBack].
|
|
||||||
* The callback is called once immediately after subscription to pass the initial value.
|
|
||||||
*
|
|
||||||
* Optional [owner] property is used for
|
|
||||||
*/
|
|
||||||
public fun <O : ObservableItemProvider, T> O.useProperty(
|
|
||||||
property: KProperty1<O, T>,
|
|
||||||
owner: Any? = null,
|
|
||||||
callBack: O.(T) -> Unit,
|
|
||||||
) {
|
|
||||||
//Pass initial value.
|
|
||||||
callBack(property.get(this))
|
|
||||||
onChange(owner) { name, oldItem, newItem ->
|
|
||||||
if (name.startsWith(property.name.asName()) && oldItem != newItem) {
|
|
||||||
callBack(property.get(this))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public object ObservableMetaSerializer : KSerializer<ObservableMeta> {
|
|
||||||
public fun empty(): ObservableMeta = ObservableMeta()
|
|
||||||
override val descriptor: SerialDescriptor get() = MetaSerializer.descriptor
|
|
||||||
|
|
||||||
override fun deserialize(decoder: Decoder): ObservableMeta {
|
|
||||||
return MetaSerializer.deserialize(decoder).toMutableMeta().asObservable()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, value: ObservableMeta) {
|
|
||||||
MetaSerializer.serialize(encoder, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public operator fun ObservableMeta.get(token: NameToken): TypedMetaItem<ObservableMeta>? = items[token]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a copy of this config, optionally applying the given [block].
|
|
||||||
* The listeners of the original Config are not retained.
|
|
||||||
*/
|
|
||||||
public inline fun ObservableMeta.copy(block: ObservableMeta.() -> Unit = {}): ObservableMeta =
|
|
||||||
asObservable().apply(block)
|
|
@ -0,0 +1,116 @@
|
|||||||
|
package space.kscience.dataforge.meta
|
||||||
|
|
||||||
|
import space.kscience.dataforge.names.Name
|
||||||
|
import space.kscience.dataforge.names.NameToken
|
||||||
|
import space.kscience.dataforge.names.asName
|
||||||
|
import space.kscience.dataforge.names.startsWith
|
||||||
|
import space.kscience.dataforge.values.Value
|
||||||
|
import kotlin.jvm.Synchronized
|
||||||
|
import kotlin.reflect.KProperty1
|
||||||
|
|
||||||
|
|
||||||
|
internal data class MetaListener(
|
||||||
|
val owner: Any? = null,
|
||||||
|
val callback: Meta.(name: Name) -> Unit,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An item provider that could be observed and mutated
|
||||||
|
*/
|
||||||
|
public interface ObservableMeta : Meta {
|
||||||
|
/**
|
||||||
|
* Add change listener to this meta. Owner is declared to be able to remove listeners later. Listener without owner could not be removed
|
||||||
|
*/
|
||||||
|
public fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all listeners belonging to given owner
|
||||||
|
*/
|
||||||
|
public fun removeListener(owner: Any?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [Meta] which is both observable and mutable
|
||||||
|
*/
|
||||||
|
public interface ObservableMutableMeta : ObservableMeta, MutableTypedMeta<ObservableMutableMeta>
|
||||||
|
|
||||||
|
private class ObservableMetaWrapper<M : MutableTypedMeta<M>>(
|
||||||
|
val origin: M,
|
||||||
|
) : ObservableMutableMeta, Meta by origin {
|
||||||
|
|
||||||
|
private val listeners = HashSet<MetaListener>()
|
||||||
|
|
||||||
|
private fun changed(name: Name) {
|
||||||
|
listeners.forEach { it.callback(this, name) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit) {
|
||||||
|
listeners.add(MetaListener(owner, callback))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun removeListener(owner: Any?) {
|
||||||
|
listeners.removeAll { it.owner === owner }
|
||||||
|
}
|
||||||
|
|
||||||
|
override val items: Map<NameToken, ObservableMetaWrapper<M>>
|
||||||
|
get() = origin.items.mapValues { ObservableMetaWrapper(it.value) }
|
||||||
|
|
||||||
|
override var value: Value?
|
||||||
|
get() = origin.value
|
||||||
|
set(value) {
|
||||||
|
origin.value = value
|
||||||
|
changed(Name.EMPTY)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun attach(name: Name, node: ObservableMutableMeta) {
|
||||||
|
origin.attach(name, node.origin)
|
||||||
|
changed(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getOrCreate(name: Name): ObservableMutableMeta =
|
||||||
|
get(name) ?: ObservableMetaWrapper(origin.getOrCreate(name))
|
||||||
|
|
||||||
|
|
||||||
|
override fun removeNode(name: Name) {
|
||||||
|
origin.removeNode(name)
|
||||||
|
changed(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun set(name: Name, meta: Meta) {
|
||||||
|
val oldMeta = get(name)
|
||||||
|
origin[name] = meta
|
||||||
|
if (oldMeta != meta) {
|
||||||
|
changed(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toMeta(): Meta {
|
||||||
|
return origin.toMeta()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun <M : MutableTypedMeta<M>> MutableTypedMeta<M>.asObservable(): ObservableMeta =
|
||||||
|
(this as? ObservableMeta) ?: ObservableMetaWrapper(self)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use the value of the property in a [callBack].
|
||||||
|
* The callback is called once immediately after subscription to pass the initial value.
|
||||||
|
*
|
||||||
|
* Optional [owner] property is used for
|
||||||
|
*/
|
||||||
|
public fun <O : ObservableMeta, T> O.useProperty(
|
||||||
|
property: KProperty1<O, T>,
|
||||||
|
owner: Any? = null,
|
||||||
|
callBack: O.(T) -> Unit,
|
||||||
|
) {
|
||||||
|
//Pass initial value.
|
||||||
|
callBack(property.get(this))
|
||||||
|
onChange(owner) { name ->
|
||||||
|
if (name.startsWith(property.name.asName())) {
|
||||||
|
callBack(property.get(this@useProperty))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,54 +1,38 @@
|
|||||||
package space.kscience.dataforge.meta
|
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
|
||||||
|
@ -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())
|
|
||||||
}
|
|
@ -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>) {
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
package space.kscience.dataforge.meta
|
|
||||||
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import space.kscience.dataforge.names.Name
|
|
||||||
import space.kscience.dataforge.names.NameToken
|
|
||||||
import space.kscience.dataforge.names.toName
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A meta node that ensures that all of its descendants has at least the same type
|
|
||||||
*/
|
|
||||||
public interface TypedMeta<out M : TypedMeta<M>> : Meta {
|
|
||||||
override val items: Map<NameToken, TypedMetaItem<M>>
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
override fun getItem(name: Name): TypedMetaItem<M>? = super.getItem(name)?.let { it as TypedMetaItem<M> }
|
|
||||||
//Typed meta guarantees that all children have M type
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The same as [Meta.get], but with specific node type
|
|
||||||
*/
|
|
||||||
public operator fun <M : TypedMeta<M>> M.get(name: Name): TypedMetaItem<M>? = getItem(name)
|
|
||||||
|
|
||||||
|
|
||||||
public operator fun <M : TypedMeta<M>> M.get(key: String): TypedMetaItem<M>? = this[key.toName()]
|
|
||||||
public operator fun <M : TypedMeta<M>> M.get(key: NameToken): TypedMetaItem<M>? = items[key]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Equals, hashcode and to string for any meta
|
|
||||||
*/
|
|
||||||
public abstract class MetaBase : Meta {
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean = if (other is Meta) {
|
|
||||||
Meta.equals(this, other)
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int = items.hashCode()
|
|
||||||
|
|
||||||
override fun toString(): String = json.encodeToString(MetaSerializer, this)
|
|
||||||
|
|
||||||
public companion object{
|
|
||||||
private val json = Json {
|
|
||||||
prettyPrint = true
|
|
||||||
useArrayPolymorphism = true
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Equals and hash code implementation for meta node
|
|
||||||
*/
|
|
||||||
public abstract class AbstractTypedMeta<M : TypedMeta<M>> : TypedMeta<M>, MetaBase()
|
|
@ -4,7 +4,7 @@ package space.kscience.dataforge.meta.descriptors
|
|||||||
* An object which provides its descriptor
|
* 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"
|
||||||
|
@ -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())
|
||||||
|
//
|
||||||
|
@ -0,0 +1,60 @@
|
|||||||
|
package space.kscience.dataforge.meta.descriptors
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import space.kscience.dataforge.meta.Meta
|
||||||
|
import space.kscience.dataforge.names.*
|
||||||
|
import space.kscience.dataforge.values.Value
|
||||||
|
import space.kscience.dataforge.values.ValueType
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The descriptor for a meta
|
||||||
|
* @param info description text
|
||||||
|
* @param children child descriptors for this node
|
||||||
|
* @param multiple True if same name siblings with this name are allowed
|
||||||
|
* @param required True if the item is required
|
||||||
|
* @param type list of allowed types for [Meta.value], null if all values are allowed
|
||||||
|
* @param indexKey An index field by which this node is identified in case of same name siblings construct
|
||||||
|
* @param defaultValue the default [Meta.value] for the node
|
||||||
|
* @param attributes additional attributes of this descriptor. For example validation and widget parameters
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
public data class MetaDescriptor(
|
||||||
|
public val info: String? = null,
|
||||||
|
public val children: Map<String, MetaDescriptor> = emptyMap(),
|
||||||
|
public val multiple: Boolean = false,
|
||||||
|
public val required: Boolean = false,
|
||||||
|
public val type: List<ValueType>? = null,
|
||||||
|
public val indexKey: String = Meta.INDEX_KEY,
|
||||||
|
public val defaultValue: Value? = null,
|
||||||
|
public val attributes: Meta = Meta.EMPTY,
|
||||||
|
)
|
||||||
|
|
||||||
|
public operator fun MetaDescriptor.get(name: Name): MetaDescriptor? = when (name.length) {
|
||||||
|
0 -> this
|
||||||
|
1 -> children[name.firstOrNull()!!.toString()]
|
||||||
|
else -> get(name.firstOrNull()!!.asName())?.get(name.cutFirst())
|
||||||
|
}
|
||||||
|
|
||||||
|
public operator fun MetaDescriptor.get(name: String): MetaDescriptor? = get(name.toName())
|
||||||
|
|
||||||
|
public class MetaDescriptorBuilder {
|
||||||
|
public var info: String? = null
|
||||||
|
public var children: MutableMap<String, MetaDescriptor> = hashMapOf()
|
||||||
|
public var multiple: Boolean = false
|
||||||
|
public var required: Boolean = false
|
||||||
|
public var type: List<ValueType>? = null
|
||||||
|
public var indexKey: String = Meta.INDEX_KEY
|
||||||
|
public var default: Value? = null
|
||||||
|
public var attributes: Meta = Meta.EMPTY
|
||||||
|
|
||||||
|
internal fun build(): MetaDescriptor = MetaDescriptor(
|
||||||
|
info = info,
|
||||||
|
children = children,
|
||||||
|
multiple = multiple,
|
||||||
|
required = required,
|
||||||
|
type = type,
|
||||||
|
indexKey = indexKey,
|
||||||
|
defaultValue = default,
|
||||||
|
attributes = attributes
|
||||||
|
)
|
||||||
|
}
|
@ -1,222 +1,222 @@
|
|||||||
package space.kscience.dataforge.meta.descriptors
|
//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())
|
||||||
}
|
// }
|
||||||
}
|
//}
|
@ -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())
|
||||||
}
|
//}
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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)) {
|
||||||
|
@ -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 }
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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 =
|
||||||
|
@ -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))
|
||||||
|
@ -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 =
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user