WIP full refactor of Meta

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,14 +1,13 @@
package space.kscience.dataforge.properties
import space.kscience.dataforge.meta.ObservableItemProvider
import space.kscience.dataforge.meta.ObservableMeta
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.startsWith
import space.kscience.dataforge.names.toName
import kotlin.reflect.KMutableProperty1
@DFExperimental
public fun <P : ObservableItemProvider, T : Any> P.property(property: KMutableProperty1<P, T?>): Property<T?> =
public fun <P : ObservableMeta, T : Any> P.property(property: KMutableProperty1<P, T?>): Property<T?> =
object : Property<T?> {
override var value: T?
get() = property.get(this@property)

View File

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

View File

@ -0,0 +1,14 @@
package space.kscience.dataforge.provider
import kotlin.test.Test
import kotlin.test.assertEquals
class PathTest {
@Test
fun testParse(){
val nameString = "a.b.c.d"
val pathString = "a.b/c.d"
assertEquals(1, Path.parse(nameString).length)
assertEquals(2, Path.parse(pathString).length)
}
}

View File

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

View File

@ -6,7 +6,7 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.fold
import space.kscience.dataforge.data.*
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaBuilder
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.misc.DFBuilder
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.misc.DFInternal
@ -18,7 +18,7 @@ import kotlin.reflect.typeOf
public class JoinGroup<T : Any, R : Any>(public var name: String, internal val set: DataSet<T>) {
public var meta: MetaBuilder = MetaBuilder()
public var meta: MutableMeta = MutableMeta()
public lateinit var result: suspend ActionEnv.(Map<Name, T>) -> R

View File

@ -7,7 +7,7 @@ import kotlinx.coroutines.launch
import space.kscience.dataforge.data.*
import space.kscience.dataforge.meta.Laminate
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaBuilder
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.toMutableMeta
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.misc.DFInternal
@ -20,7 +20,7 @@ import kotlin.reflect.typeOf
public class SplitBuilder<T : Any, R : Any>(public val name: Name, public val meta: Meta) {
public class FragmentRule<T : Any, R : Any>(public val name: Name, public var meta: MetaBuilder) {
public class FragmentRule<T : Any, R : Any>(public val name: Name, public var meta: MutableMeta) {
public lateinit var result: suspend (T) -> R
public fun result(f: suspend (T) -> R) {

View File

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

View File

@ -1,7 +1,7 @@
package space.kscience.dataforge.data
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaBuilder
import space.kscience.dataforge.meta.MutableMeta
/**
@ -17,4 +17,4 @@ public suspend fun DataSetBuilder<*>.meta(meta: Meta): Unit = emit(DataSet.META_
/**
* Add meta-data node to a [DataSet]
*/
public suspend fun DataSetBuilder<*>.meta(metaBuilder: MetaBuilder.() -> Unit): Unit = meta(Meta(metaBuilder))
public suspend fun DataSetBuilder<*>.meta(mutableMeta: MutableMeta.() -> Unit): Unit = meta(Meta(mutableMeta))

View File

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

View File

@ -34,7 +34,7 @@ public fun Meta.toYaml(): YamlMap {
return YamlMap(map)
}
private class YamlMeta(private val yamlMap: YamlMap, private val descriptor: NodeDescriptor? = null) : MetaBase() {
private class YamlMeta(private val yamlMap: YamlMap, private val descriptor: NodeDescriptor? = null) : AbstractTypedMeta() {
private fun buildItems(): Map<NameToken, MetaItem> {
val map = LinkedHashMap<NameToken, MetaItem>()

View File

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

View File

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

View File

@ -11,9 +11,8 @@ import space.kscience.dataforge.context.Context
import space.kscience.dataforge.io.IOFormat.Companion.NAME_KEY
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.meta.node
import space.kscience.dataforge.meta.toJson
import space.kscience.dataforge.meta.toMetaItem
import space.kscience.dataforge.meta.toMeta
import kotlin.reflect.KType
import kotlin.reflect.typeOf
@ -36,7 +35,7 @@ public class JsonMetaFormat(private val json: Json = DEFAULT_JSON) : MetaFormat
override fun readMeta(input: Input, descriptor: NodeDescriptor?): Meta {
val str = input.readUtf8String()//readByteArray().decodeToString()
val jsonElement = json.parseToJsonElement(str)
val item = jsonElement.toMetaItem(descriptor)
val item = jsonElement.toMeta(descriptor)
return item.node ?: Meta.EMPTY
}

View File

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

View File

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

View File

@ -1,111 +0,0 @@
package space.kscience.dataforge.meta
import space.kscience.dataforge.meta.transformations.MetaConverter
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.values.Value
import kotlin.properties.ReadOnlyProperty
/* Meta delegates */
public typealias ItemDelegate = ReadOnlyProperty<Any?, MetaItem?>
public fun ItemProvider.item(key: Name? = null): ItemDelegate = ReadOnlyProperty { _, property ->
get(key ?: property.name.asName())
}
//TODO add caching for sealed nodes
/**
* Apply a converter to this delegate creating a delegate with a custom type
*/
public fun <R : Any> ItemDelegate.convert(
converter: MetaConverter<R>,
): ReadOnlyProperty<Any?, R?> = ReadOnlyProperty { thisRef, property ->
this@convert.getValue(thisRef, property)?.let(converter::itemToObject)
}
/*
*
*/
public fun <R : Any> ItemDelegate.convert(
converter: MetaConverter<R>,
default: () -> R,
): ReadOnlyProperty<Any?, R> = ReadOnlyProperty<Any?, R> { thisRef, property ->
this@convert.getValue(thisRef, property)?.let(converter::itemToObject) ?: default()
}
/**
* A converter with a custom reader transformation
*/
public fun <R> ItemDelegate.convert(
reader: (MetaItem?) -> R,
): ReadOnlyProperty<Any?, R> = ReadOnlyProperty<Any?, R> { thisRef, property ->
this@convert.getValue(thisRef, property).let(reader)
}
/* Read-only delegates for [ItemProvider] */
/**
* A property delegate that uses custom key
*/
public fun ItemProvider.value(key: Name? = null): ReadOnlyProperty<Any?, Value?> =
item(key).convert(MetaConverter.value)
public fun ItemProvider.string(key: Name? = null): ReadOnlyProperty<Any?, String?> =
item(key).convert(MetaConverter.string)
public fun ItemProvider.boolean(key: Name? = null): ReadOnlyProperty<Any?, Boolean?> =
item(key).convert(MetaConverter.boolean)
public fun ItemProvider.number(key: Name? = null): ReadOnlyProperty<Any?, Number?> =
item(key).convert(MetaConverter.number)
public fun ItemProvider.double(key: Name? = null): ReadOnlyProperty<Any?, Double?> =
item(key).convert(MetaConverter.double)
public fun ItemProvider.float(key: Name? = null): ReadOnlyProperty<Any?, Float?> =
item(key).convert(MetaConverter.float)
public fun ItemProvider.int(key: Name? = null): ReadOnlyProperty<Any?, Int?> =
item(key).convert(MetaConverter.int)
public fun ItemProvider.long(key: Name? = null): ReadOnlyProperty<Any?, Long?> =
item(key).convert(MetaConverter.long)
public fun ItemProvider.node(key: Name? = null): ReadOnlyProperty<Any?, Meta?> =
item(key).convert(MetaConverter.meta)
public fun ItemProvider.string(default: String, key: Name? = null): ReadOnlyProperty<Any?, String> =
item(key).convert(MetaConverter.string) { default }
public fun ItemProvider.boolean(default: Boolean, key: Name? = null): ReadOnlyProperty<Any?, Boolean> =
item(key).convert(MetaConverter.boolean) { default }
public fun ItemProvider.number(default: Number, key: Name? = null): ReadOnlyProperty<Any?, Number> =
item(key).convert(MetaConverter.number) { default }
public fun ItemProvider.double(default: Double, key: Name? = null): ReadOnlyProperty<Any?, Double> =
item(key).convert(MetaConverter.double) { default }
public fun ItemProvider.float(default: Float, key: Name? = null): ReadOnlyProperty<Any?, Float> =
item(key).convert(MetaConverter.float) { default }
public fun ItemProvider.int(default: Int, key: Name? = null): ReadOnlyProperty<Any?, Int> =
item(key).convert(MetaConverter.int) { default }
public fun ItemProvider.long(default: Long, key: Name? = null): ReadOnlyProperty<Any?, Long> =
item(key).convert(MetaConverter.long) { default }
public inline fun <reified E : Enum<E>> ItemProvider.enum(default: E, key: Name? = null): ReadOnlyProperty<Any?, E> =
item(key).convert(MetaConverter.enum()) { default }
public fun ItemProvider.string(key: Name? = null, default: () -> String): ReadOnlyProperty<Any?, String> =
item(key).convert(MetaConverter.string, default)
public fun ItemProvider.boolean(key: Name? = null, default: () -> Boolean): ReadOnlyProperty<Any?, Boolean> =
item(key).convert(MetaConverter.boolean, default)
public fun ItemProvider.number(key: Name? = null, default: () -> Number): ReadOnlyProperty<Any?, Number> =
item(key).convert(MetaConverter.number, default)

View File

@ -1,88 +0,0 @@
package space.kscience.dataforge.meta
import space.kscience.dataforge.names.*
public fun interface ItemProvider {
//getItem used instead of get in order to provide extension freedom
public fun getItem(name: Name): MetaItem?
public companion object {
public val EMPTY: ItemProvider = ItemProvider { null }
}
}
/* Get operations*/
/**
* Perform recursive item search using given [name]. Each [NameToken] is treated as a name in [Meta.items] of a parent node.
*
* If [name] is empty return current [Meta] as a [MetaItemNode]
*/
public operator fun ItemProvider?.get(name: Name): MetaItem? = this?.getItem(name)
/**
* Root item of this provider
*/
public val ItemProvider.rootItem: MetaItem? get() = get(Name.EMPTY)
/**
* The root node of this item provider if it is present
*/
public val ItemProvider.rootNode: Meta? get() = rootItem.node
/**
* Parse [Name] from [key] using full name notation and pass it to [Meta.get]
*/
public operator fun ItemProvider?.get(key: String): MetaItem? = this?.get(key.toName())
/**
* Create a provider that uses given provider for default values if those are not found in this provider
*/
public fun ItemProvider.withDefault(default: ItemProvider?): ItemProvider = if (default == null) {
this
} else {
ItemProvider {
this[it] ?: default[it]
}
}
/**
* Get all items matching given name. The index of the last element, if present is used as a [Regex],
* against which indexes of elements are matched.
*/
public fun ItemProvider.getIndexed(name: Name): Map<String?, MetaItem> {
val root: Meta = when (name.length) {
0 -> error("Can't use empty name for 'getIndexed'")
1 -> this.rootNode ?: return emptyMap()
else -> this[name.cutLast()].node ?: return emptyMap()
}
val (body, index) = name.lastOrNull()!!
return if (index == null) {
root.items.filter { it.key.body == body }.mapKeys { it.key.index }
} else {
val regex = index.toRegex()
root.items.filter { it.key.body == body && (regex.matches(it.key.index ?: "")) }
.mapKeys { it.key.index }
}
}
public fun ItemProvider.getIndexed(name: String): Map<String?, MetaItem> = getIndexed(name.toName())
/**
* Return a provider referencing a child node
*/
public fun ItemProvider.getChild(childName: Name): ItemProvider = get(childName).node ?: ItemProvider.EMPTY
public fun ItemProvider.getChild(childName: String): ItemProvider = getChild(childName.toName())
///**
// * Get all items matching given name.
// */
//@Suppress("UNCHECKED_CAST")
//public fun <M : TypedMeta<M>> M.getIndexed(name: Name): Map<String?, MetaItem<M>> =
// (this as Meta).getIndexed(name) as Map<String?, MetaItem<M>>
//
//public fun <M : TypedMeta<M>> M.getIndexed(name: String): Map<String?, MetaItem<M>> =
// getIndexed(name.toName())

View File

@ -3,20 +3,15 @@
package space.kscience.dataforge.meta
import kotlinx.serialization.json.*
import space.kscience.dataforge.meta.JsonMeta.Companion.JSON_ARRAY_KEY
import space.kscience.dataforge.meta.descriptors.ItemDescriptor
import space.kscience.dataforge.meta.descriptors.ItemDescriptor.Companion.DEFAULT_INDEX_KEY
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.meta.descriptors.ValueDescriptor
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.withIndex
import space.kscience.dataforge.values.*
/**
* @param descriptor reserved for custom serialization in future
*/
public fun Value.toJson(descriptor: ValueDescriptor? = null): JsonElement = when (type) {
public fun Value.toJson(descriptor: MetaDescriptor? = null): JsonElement = when (type) {
ValueType.NUMBER -> JsonPrimitive(numberOrNull)
ValueType.STRING -> JsonPrimitive(string)
ValueType.BOOLEAN -> JsonPrimitive(boolean)
@ -26,73 +21,47 @@ public fun Value.toJson(descriptor: ValueDescriptor? = null): JsonElement = when
//Use these methods to customize JSON key mapping
@Suppress("NULLABLE_EXTENSION_OPERATOR_WITH_SAFE_CALL_RECEIVER")
private fun String.toJsonKey(descriptor: ItemDescriptor?) = descriptor?.attributes?.get("jsonName").string ?: toString()
private fun String.toJsonKey(descriptor: MetaDescriptor?) = descriptor?.attributes?.get("jsonName").string ?: toString()
//private fun NodeDescriptor?.getDescriptor(key: String) = this?.items?.get(key)
/**
* Convert given [Meta] to [JsonObject]. Primitives and nodes are copied as is, same name siblings are treated as json arrays
*/
private fun Meta.toJsonWithIndex(descriptor: NodeDescriptor?, indexValue: String?): JsonObject {
val elementMap = HashMap<String, JsonElement>()
fun MetaItem.toJsonElement(itemDescriptor: ItemDescriptor?, index: String?): JsonElement = when (this) {
is MetaItemValue -> {
value.toJson(itemDescriptor as? ValueDescriptor)
}
is MetaItemNode -> {
node.toJsonWithIndex(itemDescriptor as? NodeDescriptor, index)
}
}
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
private fun Meta.toJsonWithIndex(descriptor: MetaDescriptor?, index: String?): JsonElement = if (items.isEmpty()) {
value?.toJson(descriptor) ?: JsonNull
} else {
val pairs: MutableList<Pair<String, JsonElement>> = items.entries.groupBy {
it.key.body
}.mapTo(ArrayList()) { (body, list) ->
val childDescriptor = descriptor?.children?.get(body)
if (list.size == 1) {
val (token, element) = list.first()
val child: JsonElement = element.toJsonWithIndex(childDescriptor, token.index)
body to child
} else {
//treat arrays with single element
elementMap[jsonKey] = buildJsonArray {
add(element)
}
}
}
else -> {
val array = buildJsonArray {
items.forEach { (index, item) ->
add(item.toJsonElement(itemDescriptor, index))
}
}
elementMap[jsonKey] = array
val elements: List<JsonElement> = list.sortedBy { it.key.index }.mapIndexed { index, entry ->
//Use index if it is not equal to the item order
val actualIndex = if (index.toString() != entry.key.index) entry.key.index else null
entry.value.toJsonWithIndex(childDescriptor, actualIndex)
}
body to JsonArray(elements)
}
}
((descriptor?.items?.keys ?: emptySet()) + items.keys.map { it.body }).forEach(::addElement)
if (indexValue != null) {
val indexKey = descriptor?.indexKey ?: DEFAULT_INDEX_KEY
elementMap[indexKey] = JsonPrimitive(indexValue)
//Add index if needed
if (index != null) {
pairs += Meta.INDEX_KEY to JsonPrimitive(index)
}
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) {
JsonNull -> Null
else -> {
@ -105,79 +74,47 @@ public fun JsonPrimitive.toValue(descriptor: ValueDescriptor?): Value {
}
}
public fun JsonElement.toMetaItem(descriptor: ItemDescriptor? = null): TypedMetaItem<JsonMeta> = when (this) {
is JsonPrimitive -> {
val value = this.toValue(descriptor as? ValueDescriptor)
MetaItemValue(value)
}
is JsonObject -> {
val meta = JsonMeta(this, descriptor as? NodeDescriptor)
MetaItemNode(meta)
}
is JsonArray -> {
if (this.all { it is JsonPrimitive }) {
val value = if (isEmpty()) {
Null
} else {
map<JsonElement, Value> {
//We already checked that all values are primitives
(it as JsonPrimitive).toValue(descriptor as? ValueDescriptor)
}.asValue()
}
MetaItemValue(value)
} else {
//We can't return multiple items therefore we create top level node
buildJsonObject { put(JSON_ARRAY_KEY, this@toMetaItem) }.toMetaItem(descriptor)
}
}
}
public fun JsonElement.toMeta(descriptor: MetaDescriptor? = null): TypedMeta<JsonMeta> = JsonMeta(this, descriptor)
/**
* A meta wrapping json object
*/
public class JsonMeta(private val json: JsonObject, private val descriptor: NodeDescriptor? = null) : MetaBase() {
public class JsonMeta(
private val json: JsonElement,
private val descriptor: MetaDescriptor? = null
) : TypedMeta<JsonMeta> {
private fun buildItems(): Map<NameToken, TypedMetaItem<JsonMeta>> {
val map = LinkedHashMap<NameToken, TypedMetaItem<JsonMeta>>()
private val indexName by lazy { descriptor?.indexKey ?: Meta.INDEX_KEY }
json.forEach { (jsonKey, value) ->
val key = NameToken(jsonKey)
val itemDescriptor = descriptor?.items?.get(jsonKey)
when (value) {
is JsonPrimitive -> {
map[key] = MetaItemValue(value.toValue(itemDescriptor as? ValueDescriptor))
}
is JsonObject -> {
map[key] = MetaItemNode(
JsonMeta(
value,
itemDescriptor as? NodeDescriptor
)
)
}
is JsonArray -> if (value.all { it is JsonPrimitive }) {
val listValue = ListValue(
value.map {
//We already checked that all values are primitives
(it as JsonPrimitive).toValue(itemDescriptor as? ValueDescriptor)
}
)
map[key] = MetaItemValue(listValue)
} else value.forEachIndexed { index, jsonElement ->
val indexKey = (itemDescriptor as? NodeDescriptor)?.indexKey ?: DEFAULT_INDEX_KEY
val indexValue: String = (jsonElement as? JsonObject)
?.get(indexKey)?.jsonPrimitive?.contentOrNull
?: index.toString() //In case index is non-string, the backward transformation will be broken.
val token = key.withIndex(indexValue)
map[token] = jsonElement.toMetaItem(itemDescriptor)
override val value: Value? by lazy {
when (json) {
is JsonPrimitive -> json.toValue(descriptor)
is JsonObject -> json[Meta.VALUE_KEY]?.let { JsonMeta(it).value }
is JsonArray -> if (json.all { it is JsonPrimitive }) {
//convert array of primitives to ListValue
json.map { (it as JsonPrimitive).toValue(descriptor) }.asValue()
} else {
null
}
}
}
return map
}
override val items: Map<NameToken, TypedMetaItem<JsonMeta>> by lazy(::buildItems)
override val items: Map<NameToken, JsonMeta> by lazy {
when (json) {
is JsonPrimitive -> emptyMap()
is JsonObject -> json.entries.associate { (name, child) ->
val index = (child as? JsonObject)?.get(indexName)?.jsonPrimitive?.content
val token = NameToken(name, index)
token to JsonMeta(child, descriptor?.children?.get(name))
}
is JsonArray -> json.mapIndexed { index, child ->
//Use explicit index or or order for index
val tokenIndex = (child as? JsonObject)?.get(indexName)?.jsonPrimitive?.content ?: index.toString()
val token = NameToken(JSON_ARRAY_KEY, tokenIndex)
token to JsonMeta(child)
}.toMap()
}
}
public companion object {
/**

View File

@ -2,12 +2,15 @@ package space.kscience.dataforge.meta
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.values.Value
/**
* A meta laminate consisting of multiple immutable meta layers. For mutable front layer, use [Scheme].
* If [layers] list contains a [Laminate] it is flat-mapped.
*/
public class Laminate(layers: List<Meta>) : MetaBase() {
public class Laminate(layers: List<Meta>) : TypedMeta<SealedMeta> {
override val value: Value? = layers.firstNotNullOfOrNull { it.value }
public val layers: List<Meta> = layers.flatMap {
if (it is Laminate) {
@ -17,7 +20,7 @@ public class Laminate(layers: List<Meta>) : MetaBase() {
}
}
override val items: Map<NameToken, TypedMetaItem<Meta>> by lazy {
override val items: Map<NameToken, SealedMeta> by lazy {
layers.map { it.items.keys }.flatten().associateWith { key ->
layers.asSequence().map { it.items[key] }.filterNotNull().let(replaceRule)
}
@ -30,7 +33,7 @@ public class Laminate(layers: List<Meta>) : MetaBase() {
val items = layers.map { it.items.keys }.flatten().associateWith { key ->
layers.asSequence().map { it.items[key] }.filterNotNull().merge()
}
return SealedMeta(items)
return SealedMeta(value, items)
}
public companion object {
@ -40,15 +43,12 @@ public class Laminate(layers: List<Meta>) : MetaBase() {
*
* TODO add picture
*/
public val replaceRule: (Sequence<MetaItem>) -> TypedMetaItem<SealedMeta> = { it.first().seal() }
public val replaceRule: (Sequence<Meta>) -> SealedMeta = { it.first().seal() }
private fun Sequence<MetaItem>.merge(): TypedMetaItem<SealedMeta> {
return when {
all { it is MetaItemValue } -> //If all items are values, take first
first().seal()
all { it is MetaItemNode } -> {
private fun Sequence<Meta>.merge(): SealedMeta {
val value = firstNotNullOfOrNull { it.value }
//list nodes in item
val nodes = map { (it as MetaItemNode).node }
val nodes = toList()
//represent as key->value entries
val entries = nodes.flatMap { it.items.entries.asSequence() }
//group by keys
@ -57,16 +57,7 @@ public class Laminate(layers: List<Meta>) : MetaBase() {
val items = groups.mapValues { entry ->
entry.value.asSequence().map { it.value }.merge()
}
MetaItemNode(SealedMeta(items))
}
else -> map {
when (it) {
is MetaItemValue -> MetaItemNode(Meta { Meta.VALUE_KEY put it.value })
is MetaItemNode -> it
}
}.merge()
}
return SealedMeta(value,items)
}
@ -74,7 +65,7 @@ public class Laminate(layers: List<Meta>) : MetaBase() {
* The values a replaced but meta children are joined
* TODO add picture
*/
public val mergeRule: (Sequence<MetaItem>) -> TypedMetaItem<SealedMeta> = { it.merge() }
public val mergeRule: (Sequence<Meta>) -> TypedMeta<SealedMeta> = { it.merge() }
}
}
@ -84,7 +75,7 @@ public fun Laminate(vararg layers: Meta?): Laminate = Laminate(layers.filterNotN
/**
* Performance optimized version of get method
*/
public fun Laminate.getFirst(name: Name): MetaItem? {
public fun Laminate.getFirst(name: Name): Meta? {
layers.forEach { layer ->
layer[name]?.let { return it }
}

View File

@ -1,51 +1,37 @@
package space.kscience.dataforge.meta
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import space.kscience.dataforge.misc.Type
import space.kscience.dataforge.names.*
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.*
/**
* The object that could be represented as [Meta]. Meta provided by [toMeta] method should fully represent object state.
* Meaning that two states with the same meta are equal.
*/
@Serializable(MetaSerializer::class)
public interface MetaRepr {
public fun toMeta(): Meta
}
/**
* Generic meta tree representation. Elements are [TypedMetaItem] objects that could be represented by three different entities:
* * [MetaItemValue] (leaf)
* * [MetaItemNode] single node
*
* * Same name siblings are supported via elements with the same [Name] but different queries
* A meta node
* TODO add documentation
* Same name siblings are supported via elements with the same [Name] but different indices.
*/
@Type(Meta.TYPE)
@Serializable(MetaSerializer::class)
public interface Meta : MetaRepr, ItemProvider {
/**
* Top level items of meta tree
*/
public val items: Map<NameToken, MetaItem>
override fun getItem(name: Name): MetaItem? {
if (name.isEmpty()) return MetaItemNode(this)
return name.firstOrNull()?.let { token ->
val tail = name.cutFirst()
when (tail.length) {
0 -> items[token]
else -> items[token]?.node?.get(tail)
}
}
}
public interface Meta : MetaRepr {
public val value: Value?
public val items: Map<NameToken, Meta>
override fun toMeta(): Meta = this
override fun equals(other: Any?): Boolean
override fun hashCode(): Int
override fun toString(): String
override fun equals(other: Any?): Boolean
override fun hashCode(): Int
public companion object {
public const val TYPE: String = "meta"
@ -54,43 +40,179 @@ public interface Meta : MetaRepr, ItemProvider {
* A key for single value node
*/
public const val VALUE_KEY: String = "@value"
public const val INDEX_KEY: String = "@index"
public fun equals(meta1: Meta?, meta2: Meta?): Boolean = meta1?.items == meta2?.items
public val EMPTY: Meta = object : MetaBase() {
override val items: Map<NameToken, MetaItem> = emptyMap()
public fun hashCode(meta: Meta): Int {
var result = meta.value?.hashCode() ?: 0
result = 31 * result + meta.items.hashCode()
return result
}
public fun equals(meta1: Meta?, meta2: Meta?): Boolean {
return meta1?.value == meta2?.value && meta1?.items == meta2?.items
}
private val json = Json {
prettyPrint = true
useArrayPolymorphism = true
}
public fun toString(meta: Meta): String = json.encodeToString(MetaSerializer, meta)
public val EMPTY: Meta = SealedMeta(null, emptyMap())
}
}
public operator fun Meta.get(token: NameToken): MetaItem? = items.get(token)
public operator fun Meta.get(token: NameToken): Meta? = items[token]
/**
* Get a sequence of [Name]-[Value] pairs
* Perform recursive item search using given [name]. Each [NameToken] is treated as a name in [Meta.items] of a parent node.
*
* If [name] is empty return current [Meta]
*/
public fun Meta.valueSequence(): Sequence<Pair<Name, Value>> {
return items.asSequence().flatMap { (key, item) ->
when (item) {
is MetaItemValue -> sequenceOf(key.asName() to item.value)
is MetaItemNode -> item.node.valueSequence().map { pair -> (key.asName() + pair.first) to pair.second }
public tailrec operator fun Meta.get(name: Name): Meta? = if (name.isEmpty()) {
this
} else {
get(name.firstOrNull()!!)?.get(name.cutFirst())
}
/**
* Parse [Name] from [key] using full name notation and pass it to [Meta.get]
*/
public operator fun Meta.get(key: String): Meta? = this[key.toName()]
/**
* Get all items matching given name. The index of the last element, if present is used as a [Regex],
* against which indexes of elements are matched.
*/
public fun Meta.getIndexed(name: Name): Map<String?, Meta> {
val root: Meta = when (name.length) {
0 -> error("Can't use empty name for 'getIndexed'")
1 -> this
else -> this[name.cutLast()] ?: return emptyMap()
}
val (body, index) = name.lastOrNull()!!
return if (index == null) {
root.items
.filter { it.key.body == body }
.mapKeys { it.key.index }
} else {
val regex = index.toRegex()
root.items
.filter { it.key.body == body && (regex.matches(it.key.index ?: "")) }
.mapKeys { it.key.index }
}
}
/**
* A meta node that ensures that all of its descendants has at least the same type.
*
*/
public interface TypedMeta<out M : TypedMeta<M>> : Meta {
/**
* Access self as a recursive type instance
*/
@Suppress("UNCHECKED_CAST")
public val self: M
get() = this as M
override val value: Value?
override val items: Map<NameToken, M>
override fun toMeta(): Meta = this
}
//public typealias Meta = TypedMeta<*>
public operator fun <M : TypedMeta<M>> TypedMeta<M>.get(token: NameToken): M? = items[token]
/**
* Perform recursive item search using given [name]. Each [NameToken] is treated as a name in [TypedMeta.items] of a parent node.
*
* If [name] is empty return current [Meta]
*/
public tailrec operator fun <M : TypedMeta<M>> TypedMeta<M>.get(name: Name): M? = if (name.isEmpty()) {
self
} else {
get(name.firstOrNull()!!)?.get(name.cutFirst())
}
/**
* Parse [Name] from [key] using full name notation and pass it to [TypedMeta.get]
*/
public operator fun <M : TypedMeta<M>> TypedMeta<M>.get(key: String): M? = this[key.toName()]
/**
* Get a sequence of [Name]-[Value] pairs using top-down traversal of the tree
*/
public fun Meta.valueSequence(): Sequence<Pair<Name, Value>> = sequence {
items.forEach { (key, item) ->
item.value?.let { itemValue ->
yield(key.asName() to itemValue)
}
yieldAll(item.valueSequence().map { pair -> (key.asName() + pair.first) to pair.second })
}
}
/**
* Get a sequence of all [Name]-[TypedMetaItem] pairs for all items including nodes
* Get a sequence of all [Name]-[TypedMeta] pairs in a top-down traversal
*/
public fun Meta.itemSequence(): Sequence<Pair<Name, MetaItem>> = sequence {
public fun Meta.nodeSequence(): Sequence<Pair<Name, Meta>> = sequence {
items.forEach { (key, item) ->
yield(key.asName() to item)
if (item is MetaItemNode) {
yieldAll(item.node.itemSequence().map { (innerKey, innerItem) ->
yieldAll(item.nodeSequence().map { (innerKey, 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)
}

View File

@ -1,146 +0,0 @@
package space.kscience.dataforge.meta
import kotlinx.serialization.Serializable
import space.kscience.dataforge.misc.DFBuilder
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.values.EnumValue
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.asValue
import kotlin.jvm.JvmName
/**
* DSL builder for meta. Is not intended to store mutable state
*/
@DFBuilder
@Serializable
public class MetaBuilder : AbstractMutableMeta<MetaBuilder>() {
override val children: MutableMap<NameToken, TypedMetaItem<MetaBuilder>> = LinkedHashMap()
override fun wrapNode(meta: Meta): MetaBuilder = if (meta is MetaBuilder) meta else meta.toMutableMeta()
override fun empty(): MetaBuilder = MetaBuilder()
public infix fun String.put(item: MetaItem?) {
set(this, item)
}
public infix fun String.put(value: Value?) {
set(this, value)
}
public infix fun String.put(string: String?) {
set(this, string?.asValue())
}
public infix fun String.put(number: Number?) {
set(this, number?.asValue())
}
public infix fun String.put(boolean: Boolean?) {
set(this, boolean?.asValue())
}
public infix fun String.put(enum: Enum<*>) {
set(this, EnumValue(enum))
}
@JvmName("putValues")
public infix fun String.put(iterable: Iterable<Value>) {
set(this, iterable.asValue())
}
@JvmName("putNumbers")
public infix fun String.put(iterable: Iterable<Number>) {
set(this, iterable.map { it.asValue() }.asValue())
}
@JvmName("putStrings")
public infix fun String.put(iterable: Iterable<String>) {
set(this, iterable.map { it.asValue() }.asValue())
}
public infix fun String.put(array: DoubleArray) {
set(this, array.asValue())
}
public infix fun String.put(meta: Meta?) {
this@MetaBuilder[this] = meta
}
public infix fun String.put(repr: MetaRepr?) {
set(this, repr?.toMeta())
}
@JvmName("putMetas")
public infix fun String.put(value: Iterable<Meta>) {
set(this,value.toList())
}
public inline infix fun String.put(metaBuilder: MetaBuilder.() -> Unit) {
this@MetaBuilder[this] = MetaBuilder().apply(metaBuilder)
}
public infix fun Name.put(value: Value?) {
set(this, value)
}
public infix fun Name.put(string: String?) {
set(this, string?.asValue())
}
public infix fun Name.put(number: Number?) {
set(this, number?.asValue())
}
public infix fun Name.put(boolean: Boolean?) {
set(this, boolean?.asValue())
}
public infix fun Name.put(enum: Enum<*>) {
set(this, EnumValue(enum))
}
@JvmName("putValues")
public infix fun Name.put(iterable: Iterable<Value>) {
set(this, iterable.asValue())
}
public infix fun Name.put(meta: Meta?) {
this@MetaBuilder[this] = meta
}
public infix fun Name.put(repr: MetaRepr?) {
set(this, repr?.toMeta())
}
@JvmName("putMetas")
public infix fun Name.put(value: Iterable<Meta>) {
set(this, value.toList())
}
public infix fun Name.put(metaBuilder: MetaBuilder.() -> Unit) {
this@MetaBuilder[this] = MetaBuilder().apply(metaBuilder)
}
}
/**
* For safety, builder always copies the initial meta even if it is builder itself
*/
public fun Meta.toMutableMeta(): MetaBuilder {
return MetaBuilder().also { builder ->
items.mapValues { entry ->
val item = entry.value
builder[entry.key.asName()] = when (item) {
is MetaItemValue -> item.value
is MetaItemNode -> MetaItemNode(item.node.toMutableMeta())
}
}
}
}
/**
* Build a [MetaBuilder] using given transformation
*/
@Suppress("FunctionName")
public inline fun Meta(builder: MetaBuilder.() -> Unit): MetaBuilder = MetaBuilder().apply(builder)

View File

@ -0,0 +1,111 @@
package space.kscience.dataforge.meta
import space.kscience.dataforge.meta.transformations.MetaConverter
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.values.Value
import kotlin.properties.ReadOnlyProperty
/* Meta delegates */
public typealias MetaDelegate = ReadOnlyProperty<Any?, Meta?>
public fun Meta.item(key: Name? = null): MetaDelegate = ReadOnlyProperty { _, property ->
get(key ?: property.name.asName())
}
//TODO add caching for sealed nodes
/**
* Apply a converter to this delegate creating a delegate with a custom type
*/
public fun <R : Any> MetaDelegate.convert(
converter: MetaConverter<R>,
): ReadOnlyProperty<Any?, R?> = ReadOnlyProperty { thisRef, property ->
this@convert.getValue(thisRef, property)?.let(converter::itemToObject)
}
/*
*
*/
public fun <R : Any> MetaDelegate.convert(
converter: MetaConverter<R>,
default: () -> R,
): ReadOnlyProperty<Any?, R> = ReadOnlyProperty<Any?, R> { thisRef, property ->
this@convert.getValue(thisRef, property)?.let(converter::itemToObject) ?: default()
}
/**
* A converter with a custom reader transformation
*/
public fun <R> MetaDelegate.convert(
reader: (Meta?) -> R,
): ReadOnlyProperty<Any?, R> = ReadOnlyProperty<Any?, R> { thisRef, property ->
this@convert.getValue(thisRef, property).let(reader)
}
/* Read-only delegates for [Meta] */
/**
* A property delegate that uses custom key
*/
public fun Meta.value(key: Name? = null): ReadOnlyProperty<Any?, Value?> =
item(key).convert(MetaConverter.value)
public fun Meta.string(key: Name? = null): ReadOnlyProperty<Any?, String?> =
item(key).convert(MetaConverter.string)
public fun Meta.boolean(key: Name? = null): ReadOnlyProperty<Any?, Boolean?> =
item(key).convert(MetaConverter.boolean)
public fun Meta.number(key: Name? = null): ReadOnlyProperty<Any?, Number?> =
item(key).convert(MetaConverter.number)
public fun Meta.double(key: Name? = null): ReadOnlyProperty<Any?, Double?> =
item(key).convert(MetaConverter.double)
public fun Meta.float(key: Name? = null): ReadOnlyProperty<Any?, Float?> =
item(key).convert(MetaConverter.float)
public fun Meta.int(key: Name? = null): ReadOnlyProperty<Any?, Int?> =
item(key).convert(MetaConverter.int)
public fun Meta.long(key: Name? = null): ReadOnlyProperty<Any?, Long?> =
item(key).convert(MetaConverter.long)
public fun Meta.node(key: Name? = null): ReadOnlyProperty<Any?, Meta?> =
item(key).convert(MetaConverter.meta)
public fun Meta.string(default: String, key: Name? = null): ReadOnlyProperty<Any?, String> =
item(key).convert(MetaConverter.string) { default }
public fun Meta.boolean(default: Boolean, key: Name? = null): ReadOnlyProperty<Any?, Boolean> =
item(key).convert(MetaConverter.boolean) { default }
public fun Meta.number(default: Number, key: Name? = null): ReadOnlyProperty<Any?, Number> =
item(key).convert(MetaConverter.number) { default }
public fun Meta.double(default: Double, key: Name? = null): ReadOnlyProperty<Any?, Double> =
item(key).convert(MetaConverter.double) { default }
public fun Meta.float(default: Float, key: Name? = null): ReadOnlyProperty<Any?, Float> =
item(key).convert(MetaConverter.float) { default }
public fun Meta.int(default: Int, key: Name? = null): ReadOnlyProperty<Any?, Int> =
item(key).convert(MetaConverter.int) { default }
public fun Meta.long(default: Long, key: Name? = null): ReadOnlyProperty<Any?, Long> =
item(key).convert(MetaConverter.long) { default }
public inline fun <reified E : Enum<E>> Meta.enum(default: E, key: Name? = null): ReadOnlyProperty<Any?, E> =
item(key).convert(MetaConverter.enum()) { default }
public fun Meta.string(key: Name? = null, default: () -> String): ReadOnlyProperty<Any?, String> =
item(key).convert(MetaConverter.string, default)
public fun Meta.boolean(key: Name? = null, default: () -> Boolean): ReadOnlyProperty<Any?, Boolean> =
item(key).convert(MetaConverter.boolean, default)
public fun Meta.number(key: Name? = null, default: () -> Number): ReadOnlyProperty<Any?, Number> =
item(key).convert(MetaConverter.number, default)

View File

@ -1,96 +0,0 @@
package space.kscience.dataforge.meta
import kotlinx.serialization.Serializable
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.isEmpty
import space.kscience.dataforge.values.*
/**
* A member of the meta tree. Could be represented as one of following:
* * a [MetaItemValue] (leaf)
* * a [MetaItemNode] (node)
*/
@Serializable(MetaItemSerializer::class)
public sealed class TypedMetaItem<out M : Meta> : ItemProvider {
abstract override fun equals(other: Any?): Boolean
abstract override fun hashCode(): Int
public companion object {
public fun of(arg: Any?): MetaItem {
return when (arg) {
null -> Null.asMetaItem()
is MetaItem -> arg
is Meta -> arg.asMetaItem()
is ItemProvider -> arg.rootItem ?: Null.asMetaItem()
else -> Value.of(arg).asMetaItem()
}
}
}
}
public typealias MetaItem = TypedMetaItem<*>
@Serializable(MetaItemSerializer::class)
public class MetaItemValue(public val value: Value) : TypedMetaItem<Nothing>() {
override fun getItem(name: Name): MetaItem? = if (name.isEmpty()) this else null
override fun toString(): String = value.toString()
override fun equals(other: Any?): Boolean {
return this.value == (other as? MetaItemValue)?.value
}
override fun hashCode(): Int {
return value.hashCode()
}
}
@Serializable(MetaItemSerializer::class)
public class MetaItemNode<M : Meta>(public val node: M) : TypedMetaItem<M>() {
override fun getItem(name: Name): MetaItem? = if (name.isEmpty()) this else node.getItem(name)
//Fixing serializer for node could cause class cast problems, but it should not since Meta descendants are not serializable
override fun toString(): String = node.toString()
override fun equals(other: Any?): Boolean = Meta.equals(node, (other as? MetaItemNode<*>)?.node)
override fun hashCode(): Int = node.hashCode()
}
public fun Value.asMetaItem(): MetaItemValue = MetaItemValue(this)
public fun <M : Meta> M.asMetaItem(): MetaItemNode<M> = MetaItemNode(this)
/**
* Unsafe methods to access values and nodes directly from [TypedMetaItem]
*/
public val MetaItem?.value: Value?
get() = (this as? MetaItemValue)?.value
?: (this?.node?.get(Meta.VALUE_KEY) as? MetaItemValue)?.value
public val MetaItem?.string: String? get() = value?.string
public val MetaItem?.boolean: Boolean? get() = value?.boolean
public val MetaItem?.number: Number? get() = value?.numberOrNull
public val MetaItem?.double: Double? get() = number?.toDouble()
public val MetaItem?.float: Float? get() = number?.toFloat()
public val MetaItem?.int: Int? get() = number?.toInt()
public val MetaItem?.long: Long? get() = number?.toLong()
public val MetaItem?.short: Short? get() = number?.toShort()
public inline fun <reified E : Enum<E>> MetaItem?.enum(): E? =
if (this is MetaItemValue && this.value is EnumValue<*>) {
this.value.value as E
} else {
string?.let { enumValueOf<E>(it) }
}
public val MetaItem.stringList: List<String>? get() = value?.list?.map { it.string }
public val <M : Meta> TypedMetaItem<M>?.node: M?
get() = when (this) {
null -> null
is MetaItemValue -> null//error("Trying to interpret value meta item as node item")
is MetaItemNode -> node
}

View File

@ -1,80 +1,46 @@
package space.kscience.dataforge.meta
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.InternalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.MapSerializer
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.json.JsonDecoder
import kotlinx.serialization.json.JsonEncoder
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.JsonElement
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.NameTokenSerializer
import space.kscience.dataforge.values.ValueSerializer
public object MetaItemSerializer : KSerializer<MetaItem> {
@OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class)
override val descriptor: SerialDescriptor = buildSerialDescriptor("MetaItem", PolymorphicKind.SEALED) {
element<Boolean>("isNode")
element("value", buildSerialDescriptor("MetaItem.value", SerialKind.CONTEXTUAL))
}
override fun deserialize(decoder: Decoder): MetaItem {
decoder.decodeStructure(descriptor) {
//Force strict serialization order
require(decodeElementIndex(descriptor) == 0) { "Node flag must be first item serialized" }
val isNode = decodeBooleanElement(descriptor, 0)
require(decodeElementIndex(descriptor) == 1) { "Missing MetaItem content" }
val item = if (isNode) {
decodeSerializableElement(descriptor,1, MetaSerializer).asMetaItem()
} else {
decodeSerializableElement(descriptor,1, ValueSerializer).asMetaItem()
}
require(decodeElementIndex(descriptor) == CompositeDecoder.DECODE_DONE){"Serialized MetaItem contains additional fields"}
return item
}
}
override fun serialize(encoder: Encoder, value: MetaItem) {
encoder.encodeStructure(descriptor) {
encodeBooleanElement(descriptor, 0, value is MetaItemNode)
when (value) {
is MetaItemValue -> encodeSerializableElement(descriptor, 1, ValueSerializer, value.value)
is MetaItemNode -> encodeSerializableElement(descriptor, 1, MetaSerializer, value.node)
}
}
}
}
/**
* Serialized for meta
*/
public object MetaSerializer : KSerializer<Meta> {
private val mapSerializer: KSerializer<Map<NameToken, TypedMetaItem<Meta>>> = MapSerializer(
private val itemsSerializer: KSerializer<Map<NameToken, Meta>> = MapSerializer(
NameTokenSerializer,
MetaItemSerializer//MetaItem.serializer(MetaSerializer)
MetaSerializer
)
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Meta")
override val descriptor: SerialDescriptor = JsonElement.serializer().descriptor
override fun deserialize(decoder: Decoder): Meta {
return if (decoder is JsonDecoder) {
JsonObject.serializer().deserialize(decoder).toMeta()
} else {
object : MetaBase() {
override val items: Map<NameToken, MetaItem> = mapSerializer.deserialize(decoder)
}
}
}
override fun deserialize(decoder: Decoder): Meta = JsonElement.serializer().deserialize(decoder).toMeta()
override fun serialize(encoder: Encoder, value: Meta) {
if (encoder is JsonEncoder) {
JsonObject.serializer().serialize(encoder, value.toJson())
} else {
mapSerializer.serialize(encoder, value.items)
}
JsonElement.serializer().serialize(encoder, value.toJson())
}
}
/**
* A serializer for [MutableMeta]
*/
public object MutableMetaSerializer : KSerializer<MutableMeta> {
override val descriptor: SerialDescriptor = MetaSerializer.descriptor
override fun deserialize(decoder: Decoder): MutableMeta {
val meta = decoder.decodeSerializableValue(MetaSerializer)
return (meta as? MutableMeta) ?: meta.toMutableMeta()
}
override fun serialize(encoder: Encoder, value: MutableMeta) {
encoder.encodeSerializableValue(MetaSerializer, value)
}
}

View File

@ -1,153 +0,0 @@
package space.kscience.dataforge.meta
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import space.kscience.dataforge.names.*
import space.kscience.dataforge.values.Value
@Serializable(MutableItemProviderSerializer::class)
public interface MutableItemProvider : ItemProvider {
public fun setItem(name: Name, item: MetaItem?)
}
/**
* A serializer form [MutableItemProvider]
*/
public class MutableItemProviderSerializer : KSerializer<MutableItemProvider> {
override val descriptor: SerialDescriptor = MetaSerializer.descriptor
override fun deserialize(decoder: Decoder): MutableItemProvider {
val meta = decoder.decodeSerializableValue(MetaSerializer)
return (meta as? MetaBuilder) ?: meta.toMutableMeta()
}
override fun serialize(encoder: Encoder, value: MutableItemProvider) {
encoder.encodeSerializableValue(MetaSerializer, value.rootItem?.node ?: Meta.EMPTY)
}
}
public operator fun MutableItemProvider.set(name: Name, item: MetaItem?): Unit = setItem(name, item)
public operator fun MutableItemProvider.set(name: Name, value: Value?): Unit = set(name, value?.asMetaItem())
public operator fun MutableItemProvider.set(name: Name, meta: Meta?): Unit = set(name, meta?.asMetaItem())
public operator fun MutableItemProvider.set(key: String, item: MetaItem?): Unit = set(key.toName(), item)
public operator fun MutableItemProvider.set(key: String, meta: Meta?): Unit = set(key, meta?.asMetaItem())
@Suppress("NOTHING_TO_INLINE")
public inline fun MutableItemProvider.remove(name: Name): Unit = setItem(name, null)
@Suppress("NOTHING_TO_INLINE")
public inline fun MutableItemProvider.remove(name: String): Unit = remove(name.toName())
/**
* Universal unsafe set method
*/
public operator fun MutableItemProvider.set(name: Name, value: Any?) {
when (value) {
null -> remove(name)
else -> set(name, MetaItem.of(value))
}
}
public operator fun MutableItemProvider.set(name: NameToken, value: Any?): Unit =
set(name.asName(), value)
public operator fun MutableItemProvider.set(key: String, value: Any?): Unit =
set(key.toName(), value)
public operator fun MutableItemProvider.set(key: String, index: String, value: Any?): Unit =
set(key.toName().withIndex(index), value)
/* Same name siblings generation */
public fun MutableItemProvider.setIndexedItems(
name: Name,
items: Iterable<MetaItem>,
indexFactory: (MetaItem, index: Int) -> String = { _, index -> index.toString() },
) {
val tokens = name.tokens.toMutableList()
val last = tokens.last()
items.forEachIndexed { index, meta ->
val indexedToken = NameToken(last.body, (last.index ?: "") + indexFactory(meta, index))
tokens[tokens.lastIndex] = indexedToken
set(Name(tokens), meta)
}
}
public fun MutableItemProvider.setIndexed(
name: Name,
metas: Iterable<Meta>,
indexFactory: (Meta, index: Int) -> String = { _, index -> index.toString() },
) {
setIndexedItems(name, metas.map { MetaItemNode(it) }) { item, index -> indexFactory(item.node!!, index) }
}
public operator fun MutableItemProvider.set(name: Name, metas: Iterable<Meta>): Unit = setIndexed(name, metas)
public operator fun MutableItemProvider.set(name: String, metas: Iterable<Meta>): Unit =
setIndexed(name.toName(), metas)
/**
* Get a [MutableItemProvider] referencing a child node
*/
public fun MutableItemProvider.getChild(childName: Name): MutableItemProvider {
fun createProvider() = object : MutableItemProvider {
override fun setItem(name: Name, item: MetaItem?) {
this@getChild.setItem(childName + name, item)
}
override fun getItem(name: Name): MetaItem? = this@getChild.getItem(childName + name)
}
return when {
childName.isEmpty() -> this
this is MutableMeta<*> -> {
get(childName).node ?: createProvider()
}
else -> {
createProvider()
}
}
}
public fun MutableItemProvider.getChild(childName: String): MutableItemProvider = getChild(childName.toName())
/**
* Update existing mutable node with another node. The rules are following:
* * value replaces anything
* * node updates node and replaces anything but node
* * node list updates node list if number of nodes in the list is the same and replaces anything otherwise
*/
public fun MutableItemProvider.update(meta: Meta) {
meta.valueSequence().forEach { (name, value) -> set(name, value) }
}
/**
* Edit a provider child at given name location
*/
public fun MutableItemProvider.editChild(name: Name, builder: MutableItemProvider.() -> Unit): MutableItemProvider =
getChild(name).apply(builder)
/**
* Create a mutable item provider that uses given provider for default values if those are not found in this provider.
* Changes are propagated only to this provider.
*/
public fun MutableItemProvider.withDefault(default: ItemProvider?): MutableItemProvider =
if (default == null || (default is Meta && default.isEmpty())) {
//Optimize for use with empty default
this
} else object : MutableItemProvider {
override fun setItem(name: Name, item: MetaItem?) {
this@withDefault.setItem(name, item)
}
override fun getItem(name: Name): MetaItem? = this@withDefault.getItem(name) ?: default.getItem(name)
}

View File

@ -1,74 +1,352 @@
package space.kscience.dataforge.meta
import kotlinx.serialization.Serializable
import space.kscience.dataforge.misc.DFBuilder
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.*
import space.kscience.dataforge.values.EnumValue
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.asValue
import kotlin.jvm.Synchronized
public interface MutableMeta<out M : MutableMeta<M>> : TypedMeta<M>, MutableItemProvider {
override val items: Map<NameToken, TypedMetaItem<M>>
}
/**
* A mutable meta node with attachable change listener.
*
* Changes in Meta are not thread safe.
* Mutable variant of [Meta]
* TODO documentation
*/
public abstract class AbstractMutableMeta<M : MutableMeta<M>> : AbstractTypedMeta<M>(), MutableMeta<M> {
@Serializable(MutableMetaSerializer::class)
public interface MutableMeta : Meta {
protected abstract val children: MutableMap<NameToken, TypedMetaItem<M>>
/**
* Get or set value of this node
*/
override var value: Value?
override val items: Map<NameToken, TypedMetaItem<M>> get() = children
/**
* Set or replace node at given [name]
*/
public operator fun set(name: Name, meta: Meta)
//protected abstract fun itemChanged(name: Name, oldItem: MetaItem<*>?, newItem: MetaItem<*>?)
/**
* Remove a note at a given [name] if it is present
*/
public fun removeNode(name: Name)
protected open fun replaceItem(key: NameToken, oldItem: TypedMetaItem<M>?, newItem: TypedMetaItem<M>?) {
/**
* 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) {
children.remove(key)
} else {
newItem.adoptBy(this, key)
children[key] = newItem
}
//itemChanged(key.asName(), oldItem, newItem)
changed(key.asName())
}
}
private fun wrapItem(item: MetaItem?): TypedMetaItem<M>? = when (item) {
null -> null
is MetaItemValue -> item
is MetaItemNode -> MetaItemNode(wrapNode(item.node))
}
private fun wrapItem(meta: Meta): MutableMetaImpl =
MutableMetaImpl(meta.value, meta.items.mapValuesTo(LinkedHashMap()) { wrapItem(it.value) })
/**
* Transform given meta to node type of this meta tree
*/
protected abstract fun wrapNode(meta: Meta): M
/**
* Create empty node
*/
internal abstract fun empty(): M
override fun setItem(name: Name, item: MetaItem?) {
override fun set(name: Name, meta: Meta) {
val oldItem: ObservableMutableMeta? = get(name)
if (oldItem != meta) {
when (name.length) {
0 -> error("Can't set a meta item for empty name")
0 -> error("Can't set a meta with empty name")
1 -> {
val token = name.firstOrNull()!!
val oldItem: TypedMetaItem<M>? = getItem(name)
replaceItem(token, oldItem, wrapItem(item))
replaceItem(token, oldItem, wrapItem(meta))
}
else -> {
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) {
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
*/
public fun MutableItemProvider.append(name: Name, value: Any?) {
@DFExperimental
public fun MutableMeta.append(name: Name, value: Any?) {
require(!name.isEmpty()) { "Name could not be empty for append operation" }
val newIndex = name.lastOrNull()!!.index
if (newIndex != null) {
@ -79,17 +357,40 @@ public fun MutableItemProvider.append(name: Name, value: Any?) {
}
}
public fun MutableItemProvider.append(name: String, value: Any?): Unit = append(name.toName(), value)
@DFExperimental
public fun MutableMeta.append(name: String, value: Any?): Unit = append(name.toName(), value)
///**
// * Apply existing node with given [builder] or create a new element with it.
// */
//@DFExperimental
//public fun MutableMeta.edit(name: Name, builder: MutableMeta.() -> Unit) {
// val item = when (val existingItem = get(name)) {
// null -> MutableMeta().also { set(name, it) }
// is MetaItemNode<MutableMeta> -> existingItem.node
// else -> error("Can't edit value meta item")
// }
// item.apply(builder)
//}
/**
* Apply existing node with given [builder] or create a new element with it.
* Create a mutable copy of this meta. The copy is created even if the Meta is already mutable
*/
@DFExperimental
public fun <M : AbstractMutableMeta<M>> M.edit(name: Name, builder: M.() -> Unit) {
val item = when (val existingItem = get(name)) {
null -> empty().also { set(name, it) }
is MetaItemNode<M> -> existingItem.node
else -> error("Can't edit value meta item")
}
item.apply(builder)
}
public fun Meta.toMutableMeta(): ObservableMutableMeta = MutableMetaImpl(value, items)
public fun Meta.asMutableMeta(): MutableMeta = (this as? MutableMeta) ?: toMutableMeta()
/**
* Build a [MutableMeta] using given transformation
*/
@Suppress("FunctionName")
public fun MutableMeta(builder: MutableMeta.() -> Unit = {}): ObservableMutableMeta =
MutableMetaImpl(null).apply(builder)
/**
* Create a copy of this [Meta], optionally applying the given [block].
* The listeners of the original Config are not retained.
*/
public inline fun Meta.copy(block: MutableMeta.() -> Unit = {}): Meta =
toMutableMeta().apply(block)

View File

@ -9,14 +9,14 @@ import kotlin.reflect.KProperty
/* Read-write delegates */
public typealias MutableItemDelegate = ReadWriteProperty<Any?, MetaItem?>
public typealias MutableMetaDelegate = ReadWriteProperty<Any?, Meta?>
public fun MutableItemProvider.item(key: Name? = null): MutableItemDelegate = object : MutableItemDelegate {
override fun getValue(thisRef: Any?, property: KProperty<*>): MetaItem? {
public fun MutableMeta.item(key: Name? = null): MutableMetaDelegate = object : MutableMetaDelegate {
override fun getValue(thisRef: Any?, property: KProperty<*>): Meta? {
return get(key ?: property.name.asName())
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: MetaItem?) {
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Meta?) {
val name = key ?: property.name.asName()
set(name, value)
}
@ -27,7 +27,7 @@ public fun MutableItemProvider.item(key: Name? = null): MutableItemDelegate = ob
/**
* A type converter for a mutable [TypedMetaItem] delegate
*/
public fun <R : Any> MutableItemDelegate.convert(
public fun <R : Any> MutableMetaDelegate.convert(
converter: MetaConverter<R>,
): ReadWriteProperty<Any?, R?> = object : ReadWriteProperty<Any?, R?> {
@ -40,7 +40,7 @@ public fun <R : Any> MutableItemDelegate.convert(
}
}
public fun <R : Any> MutableItemDelegate.convert(
public fun <R : Any> MutableMetaDelegate.convert(
converter: MetaConverter<R>,
default: () -> R,
): ReadWriteProperty<Any?, R> = object : ReadWriteProperty<Any?, R> {
@ -54,9 +54,9 @@ public fun <R : Any> MutableItemDelegate.convert(
}
}
public fun <R> MutableItemDelegate.convert(
reader: (MetaItem?) -> R,
writer: (R) -> MetaItem?,
public fun <R> MutableMetaDelegate.convert(
reader: (Meta?) -> R,
writer: (R) -> Meta?,
): ReadWriteProperty<Any?, R> = object : ReadWriteProperty<Any?, R> {
override fun getValue(thisRef: Any?, property: KProperty<*>): R =
@ -74,120 +74,112 @@ public fun <R> MutableItemDelegate.convert(
/**
* A property delegate that uses custom key
*/
public fun MutableItemProvider.value(key: Name? = null): ReadWriteProperty<Any?, Value?> =
public fun MutableMeta.value(key: Name? = null): ReadWriteProperty<Any?, Value?> =
item(key).convert(MetaConverter.value)
public fun MutableItemProvider.string(key: Name? = null): ReadWriteProperty<Any?, String?> =
public fun MutableMeta.string(key: Name? = null): ReadWriteProperty<Any?, String?> =
item(key).convert(MetaConverter.string)
public fun MutableItemProvider.boolean(key: Name? = null): ReadWriteProperty<Any?, Boolean?> =
public fun MutableMeta.boolean(key: Name? = null): ReadWriteProperty<Any?, Boolean?> =
item(key).convert(MetaConverter.boolean)
public fun MutableItemProvider.number(key: Name? = null): ReadWriteProperty<Any?, Number?> =
public fun MutableMeta.number(key: Name? = null): ReadWriteProperty<Any?, Number?> =
item(key).convert(MetaConverter.number)
public fun MutableItemProvider.string(default: String, key: Name? = null): ReadWriteProperty<Any?, String> =
public fun MutableMeta.string(default: String, key: Name? = null): ReadWriteProperty<Any?, String> =
item(key).convert(MetaConverter.string) { default }
public fun MutableItemProvider.boolean(default: Boolean, key: Name? = null): ReadWriteProperty<Any?, Boolean> =
public fun MutableMeta.boolean(default: Boolean, key: Name? = null): ReadWriteProperty<Any?, Boolean> =
item(key).convert(MetaConverter.boolean) { default }
public fun MutableItemProvider.number(default: Number, key: Name? = null): ReadWriteProperty<Any?, Number> =
public fun MutableMeta.number(default: Number, key: Name? = null): ReadWriteProperty<Any?, Number> =
item(key).convert(MetaConverter.number) { default }
public fun MutableItemProvider.value(key: Name? = null, default: () -> Value): ReadWriteProperty<Any?, Value> =
public fun MutableMeta.value(key: Name? = null, default: () -> Value): ReadWriteProperty<Any?, Value> =
item(key).convert(MetaConverter.value, default)
public fun MutableItemProvider.string(key: Name? = null, default: () -> String): ReadWriteProperty<Any?, String> =
public fun MutableMeta.string(key: Name? = null, default: () -> String): ReadWriteProperty<Any?, String> =
item(key).convert(MetaConverter.string, default)
public fun MutableItemProvider.boolean(key: Name? = null, default: () -> Boolean): ReadWriteProperty<Any?, Boolean> =
public fun MutableMeta.boolean(key: Name? = null, default: () -> Boolean): ReadWriteProperty<Any?, Boolean> =
item(key).convert(MetaConverter.boolean, default)
public fun MutableItemProvider.number(key: Name? = null, default: () -> Number): ReadWriteProperty<Any?, Number> =
public fun MutableMeta.number(key: Name? = null, default: () -> Number): ReadWriteProperty<Any?, Number> =
item(key).convert(MetaConverter.number, default)
public inline fun <reified E : Enum<E>> MutableItemProvider.enum(
public inline fun <reified E : Enum<E>> MutableMeta.enum(
default: E,
key: Name? = null,
): ReadWriteProperty<Any?, E> =
item(key).convert(MetaConverter.enum()) { default }
public fun MutableItemProvider.node(key: Name? = null): ReadWriteProperty<Any?, Meta?> = item(key).convert(
reader = { it.node },
writer = { it?.asMetaItem() }
)
public inline fun <reified M : MutableMeta<M>> M.node(key: Name? = null): ReadWriteProperty<Any?, M?> =
item(key).convert(reader = { it?.let { it.node as M } }, writer = { it?.let { MetaItemNode(it) } })
/* Number delegates */
public fun MutableItemProvider.int(key: Name? = null): ReadWriteProperty<Any?, Int?> =
public fun MutableMeta.int(key: Name? = null): ReadWriteProperty<Any?, Int?> =
item(key).convert(MetaConverter.int)
public fun MutableItemProvider.double(key: Name? = null): ReadWriteProperty<Any?, Double?> =
public fun MutableMeta.double(key: Name? = null): ReadWriteProperty<Any?, Double?> =
item(key).convert(MetaConverter.double)
public fun MutableItemProvider.long(key: Name? = null): ReadWriteProperty<Any?, Long?> =
public fun MutableMeta.long(key: Name? = null): ReadWriteProperty<Any?, Long?> =
item(key).convert(MetaConverter.long)
public fun MutableItemProvider.float(key: Name? = null): ReadWriteProperty<Any?, Float?> =
public fun MutableMeta.float(key: Name? = null): ReadWriteProperty<Any?, Float?> =
item(key).convert(MetaConverter.float)
/* Safe number delegates*/
public fun MutableItemProvider.int(default: Int, key: Name? = null): ReadWriteProperty<Any?, Int> =
public fun MutableMeta.int(default: Int, key: Name? = null): ReadWriteProperty<Any?, Int> =
item(key).convert(MetaConverter.int) { default }
public fun MutableItemProvider.double(default: Double, key: Name? = null): ReadWriteProperty<Any?, Double> =
public fun MutableMeta.double(default: Double, key: Name? = null): ReadWriteProperty<Any?, Double> =
item(key).convert(MetaConverter.double) { default }
public fun MutableItemProvider.long(default: Long, key: Name? = null): ReadWriteProperty<Any?, Long> =
public fun MutableMeta.long(default: Long, key: Name? = null): ReadWriteProperty<Any?, Long> =
item(key).convert(MetaConverter.long) { default }
public fun MutableItemProvider.float(default: Float, key: Name? = null): ReadWriteProperty<Any?, Float> =
public fun MutableMeta.float(default: Float, key: Name? = null): ReadWriteProperty<Any?, Float> =
item(key).convert(MetaConverter.float) { default }
/* Extra delegates for special cases */
public fun MutableItemProvider.stringList(
public fun MutableMeta.stringList(
vararg default: String,
key: Name? = null,
): ReadWriteProperty<Any?, List<String>> = item(key).convert(
reader = { it?.stringList ?: listOf(*default) },
writer = { it.map { str -> str.asValue() }.asValue().asMetaItem() }
writer = { Meta(it.map { str -> str.asValue() }.asValue()) }
)
public fun MutableItemProvider.stringList(
public fun MutableMeta.stringList(
key: Name? = null,
): ReadWriteProperty<Any?, List<String>?> = item(key).convert(
reader = { it?.stringList },
writer = { it?.map { str -> str.asValue() }?.asValue()?.asMetaItem() }
writer = { it?.map { str -> str.asValue() }?.asValue()?.let { Meta(it) } }
)
public fun MutableItemProvider.numberList(
public fun MutableMeta.numberList(
vararg default: Number,
key: Name? = null,
): ReadWriteProperty<Any?, List<Number>> = item(key).convert(
reader = { it?.value?.list?.map { value -> value.numberOrNull ?: Double.NaN } ?: listOf(*default) },
writer = { it.map { num -> num.asValue() }.asValue().asMetaItem() }
writer = { Meta(it.map { num -> num.asValue() }.asValue()) }
)
/* A special delegate for double arrays */
public fun MutableItemProvider.doubleArray(
public fun MutableMeta.doubleArray(
vararg default: Double,
key: Name? = null,
): ReadWriteProperty<Any?, DoubleArray> = item(key).convert(
reader = { it?.value?.doubleArray ?: doubleArrayOf(*default) },
writer = { DoubleArrayValue(it).asMetaItem() }
writer = { Meta(DoubleArrayValue(it)) }
)
public fun <T> MutableItemProvider.listValue(
public fun <T> MutableMeta.listValue(
key: Name? = null,
writer: (T) -> Value = { Value.of(it) },
reader: (Value) -> T,

View File

@ -1,159 +0,0 @@
package space.kscience.dataforge.meta
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import space.kscience.dataforge.names.*
import kotlin.js.JsName
import kotlin.jvm.Synchronized
import kotlin.reflect.KProperty1
internal data class ItemListener(
val owner: Any? = null,
val action: (name: Name, oldItem: MetaItem?, newItem: MetaItem?) -> Unit,
)
/**
* An item provider that could be observed and mutated
*/
public interface ObservableItemProvider : ItemProvider, MutableItemProvider {
/**
* Add change listener to this meta. Owner is declared to be able to remove listeners later. Listener without owner could not be removed
*/
public fun onChange(owner: Any?, action: (name: Name, oldItem: MetaItem?, newItem: MetaItem?) -> Unit)
/**
* Remove all listeners belonging to given owner
*/
public fun removeListener(owner: Any?)
}
private open class ObservableItemProviderWrapper(
open val itemProvider: MutableItemProvider,
open val parent: Pair<ObservableItemProviderWrapper, Name>? = null
) : ObservableItemProvider {
override fun getItem(name: Name): MetaItem? = itemProvider.getItem(name)
private val listeners = HashSet<ItemListener>()
@Synchronized
private fun itemChanged(name: Name, oldItem: MetaItem?, newItem: MetaItem?) {
listeners.forEach { it.action(name, oldItem, newItem) }
}
override fun setItem(name: Name, item: MetaItem?) {
val oldItem = getItem(name)
itemProvider.setItem(name, item)
itemChanged(name, oldItem, item)
//Recursively send update to parent listeners
parent?.let { (parentNode, token) ->
parentNode.itemChanged(token + name, oldItem, item)
}
}
@Synchronized
override fun onChange(owner: Any?, action: (Name, MetaItem?, MetaItem?) -> Unit) {
listeners.add(ItemListener(owner, action))
}
@Synchronized
override fun removeListener(owner: Any?) {
listeners.removeAll { it.owner === owner }
}
}
public fun MutableItemProvider.asObservable(): ObservableItemProvider =
(this as? ObservableItemProvider) ?: ObservableItemProviderWrapper(this)
/**
* A Meta instance that could be both mutated and observed.
*/
@Serializable(ObservableMetaSerializer::class)
public interface ObservableMeta : ObservableItemProvider, MutableMeta<ObservableMeta>
/**
* A wrapper class that creates observable meta node from regular meta node
*/
private class ObservableMetaWrapper<M : MutableMeta<M>>(
override val itemProvider: M,
override val parent: Pair<ObservableMetaWrapper<M>, Name>? = null
) : ObservableItemProviderWrapper(itemProvider, parent), ObservableMeta {
override fun equals(other: Any?): Boolean = (itemProvider == other)
override fun hashCode(): Int = itemProvider.hashCode()
override fun toString(): String = itemProvider.toString()
private fun wrapItem(name: Name, item: TypedMetaItem<M>): TypedMetaItem<ObservableMeta> {
return when (item) {
is MetaItemValue -> item
is MetaItemNode<M> -> ObservableMetaWrapper(item.node, this to name).asMetaItem()
}
}
override fun getItem(name: Name): TypedMetaItem<ObservableMeta>? = itemProvider[name]?.let {
wrapItem(name, it)
}
override val items: Map<NameToken, TypedMetaItem<ObservableMeta>>
get() = itemProvider.items.mapValues { (token, childItem: TypedMetaItem<M>) ->
wrapItem(token.asName(), childItem)
}
}
/**
* If this meta is observable return itself. Otherwise return an observable wrapper. The changes of initial meta are
* reflected on wrapper but are **NOT** observed.
*/
public fun <M : MutableMeta<M>> M.asObservable(): ObservableMeta =
(this as? ObservableMeta) ?: ObservableMetaWrapper(this)
@JsName("buildObservableMeta")
public fun ObservableMeta(): ObservableMeta = MetaBuilder().asObservable()
/**
* Use the value of the property in a [callBack].
* The callback is called once immediately after subscription to pass the initial value.
*
* Optional [owner] property is used for
*/
public fun <O : ObservableItemProvider, T> O.useProperty(
property: KProperty1<O, T>,
owner: Any? = null,
callBack: O.(T) -> Unit,
) {
//Pass initial value.
callBack(property.get(this))
onChange(owner) { name, oldItem, newItem ->
if (name.startsWith(property.name.asName()) && oldItem != newItem) {
callBack(property.get(this))
}
}
}
public object ObservableMetaSerializer : KSerializer<ObservableMeta> {
public fun empty(): ObservableMeta = ObservableMeta()
override val descriptor: SerialDescriptor get() = MetaSerializer.descriptor
override fun deserialize(decoder: Decoder): ObservableMeta {
return MetaSerializer.deserialize(decoder).toMutableMeta().asObservable()
}
override fun serialize(encoder: Encoder, value: ObservableMeta) {
MetaSerializer.serialize(encoder, value)
}
}
public operator fun ObservableMeta.get(token: NameToken): TypedMetaItem<ObservableMeta>? = items[token]
/**
* Create a copy of this config, optionally applying the given [block].
* The listeners of the original Config are not retained.
*/
public inline fun ObservableMeta.copy(block: ObservableMeta.() -> Unit = {}): ObservableMeta =
asObservable().apply(block)

View File

@ -0,0 +1,116 @@
package space.kscience.dataforge.meta
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.startsWith
import space.kscience.dataforge.values.Value
import kotlin.jvm.Synchronized
import kotlin.reflect.KProperty1
internal data class MetaListener(
val owner: Any? = null,
val callback: Meta.(name: Name) -> Unit,
)
/**
* An item provider that could be observed and mutated
*/
public interface ObservableMeta : Meta {
/**
* Add change listener to this meta. Owner is declared to be able to remove listeners later. Listener without owner could not be removed
*/
public fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit)
/**
* Remove all listeners belonging to given owner
*/
public fun removeListener(owner: Any?)
}
/**
* A [Meta] which is both observable and mutable
*/
public interface ObservableMutableMeta : ObservableMeta, MutableTypedMeta<ObservableMutableMeta>
private class ObservableMetaWrapper<M : MutableTypedMeta<M>>(
val origin: M,
) : ObservableMutableMeta, Meta by origin {
private val listeners = HashSet<MetaListener>()
private fun changed(name: Name) {
listeners.forEach { it.callback(this, name) }
}
@Synchronized
override fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit) {
listeners.add(MetaListener(owner, callback))
}
@Synchronized
override fun removeListener(owner: Any?) {
listeners.removeAll { it.owner === owner }
}
override val items: Map<NameToken, ObservableMetaWrapper<M>>
get() = origin.items.mapValues { ObservableMetaWrapper(it.value) }
override var value: Value?
get() = origin.value
set(value) {
origin.value = value
changed(Name.EMPTY)
}
override fun attach(name: Name, node: ObservableMutableMeta) {
origin.attach(name, node.origin)
changed(name)
}
override fun getOrCreate(name: Name): ObservableMutableMeta =
get(name) ?: ObservableMetaWrapper(origin.getOrCreate(name))
override fun removeNode(name: Name) {
origin.removeNode(name)
changed(name)
}
override fun set(name: Name, meta: Meta) {
val oldMeta = get(name)
origin[name] = meta
if (oldMeta != meta) {
changed(name)
}
}
override fun toMeta(): Meta {
return origin.toMeta()
}
}
public fun <M : MutableTypedMeta<M>> MutableTypedMeta<M>.asObservable(): ObservableMeta =
(this as? ObservableMeta) ?: ObservableMetaWrapper(self)
/**
* Use the value of the property in a [callBack].
* The callback is called once immediately after subscription to pass the initial value.
*
* Optional [owner] property is used for
*/
public fun <O : ObservableMeta, T> O.useProperty(
property: KProperty1<O, T>,
owner: Any? = null,
callBack: O.(T) -> Unit,
) {
//Pass initial value.
callBack(property.get(this))
onChange(owner) { name ->
if (name.startsWith(property.name.asName())) {
callBack(property.get(this@useProperty))
}
}
}

View File

@ -1,54 +1,38 @@
package space.kscience.dataforge.meta
import space.kscience.dataforge.meta.descriptors.Described
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.meta.descriptors.get
import space.kscience.dataforge.meta.descriptors.validateItem
import space.kscience.dataforge.meta.descriptors.*
import space.kscience.dataforge.names.Name
import kotlin.jvm.Synchronized
/**
* A base for delegate-based or descriptor-based scheme. [Scheme] has an empty constructor to simplify usage from [Specification].
* Default item provider and [NodeDescriptor] are optional
*/
public open class Scheme(
private var items: ObservableItemProvider = ObservableMeta(),
final override var descriptor: NodeDescriptor? = null
) : Described, MetaRepr, ObservableItemProvider {
public open class Scheme internal constructor(
source: MutableMeta = MutableMeta()
) : Described, ObservableMutableMeta, Meta by source {
/**
* Add a listener to this scheme changes. If the inner provider is observable, then listening will be delegated to it.
* Otherwise, local listeners will be created.
*/
@Synchronized
override fun onChange(owner: Any?, action: (Name, MetaItem?, MetaItem?) -> Unit) {
items.onChange(owner, action)
}
private var source = source.asObservable()
final override var descriptor: MetaDescriptor? = null
internal set
/**
* Remove all listeners belonging to given owner
*/
@Synchronized
override fun removeListener(owner: Any?) {
items.removeListener(owner)
}
internal fun wrap(
items: MutableItemProvider,
items: MutableMeta,
preserveDefault: Boolean = false
) {
this.items = if (preserveDefault) items.withDefault(this.items).asObservable() else items.asObservable()
this.source = if (preserveDefault) items.withDefault(this.source) else items
}
/**
* Get a property with default
*/
override fun getItem(name: Name): MetaItem? = items[name] ?: descriptor?.get(name)?.defaultValue
//
// /**
// * Get a property with default
// */
// override fun getItem(name: Name): MetaItem? = source[name] ?: descriptor?.get(name)?.defaultValue
/**
* Check if property with given [name] could be assigned to [item]
*/
public open fun validateItem(name: Name, item: MetaItem?): Boolean {
public open fun validate(name: Name, item: Meta?): Boolean {
val descriptor = descriptor?.get(name)
return descriptor?.validateItem(item) ?: true
}
@ -56,30 +40,25 @@ public open class Scheme(
/**
* Set a configurable property
*/
override fun setItem(name: Name, item: MetaItem?) {
val oldItem = items[name]
if (oldItem != item) {
if (validateItem(name, item)) {
items[name] = item
override fun set(name: Name, meta: Meta) {
val oldItem = source[name]
if (oldItem != meta) {
if (validate(name, meta)) {
source[name] = meta
} else {
error("Validation failed for property $name with value $item")
error("Validation failed for property $name with value $meta")
}
}
}
override fun toMeta(): Laminate = Laminate(items.rootNode, descriptor?.defaultMeta)
override fun toMeta(): Laminate = Laminate(source, descriptor?.defaultMeta)
}
/**
* The scheme is considered empty only if its root item does not exist.
*/
public fun Scheme.isEmpty(): Boolean = rootItem == null
/**
* Relocate scheme target onto given [MutableItemProvider]. Old provider does not get updates anymore.
* Relocate scheme target onto given [MutableTypedMeta]. Old provider does not get updates anymore.
* Current state of the scheme used as a default.
*/
public fun <T : Scheme> T.retarget(provider: MutableItemProvider): T = apply {
public fun <T : Scheme> T.retarget(provider: MutableMeta): T = apply {
wrap(provider, true)
}
@ -95,16 +74,16 @@ public open class SchemeSpec<out T : Scheme>(
private val builder: () -> T,
) : Specification<T>, Described {
override fun read(items: ItemProvider): T = empty().also {
it.wrap(ObservableMeta().withDefault(items))
override fun read(items: Meta): T = empty().also {
it.wrap(MutableMeta().withDefault(items))
}
override fun write(target: MutableItemProvider): T = empty().also {
override fun write(target: MutableMeta): T = empty().also {
it.wrap(target)
}
//TODO Generate descriptor from Scheme class
override val descriptor: NodeDescriptor? get() = null
override val descriptor: MetaDescriptor? get() = null
override fun empty(): T = builder().also {
it.descriptor = descriptor

View File

@ -1,6 +1,7 @@
package space.kscience.dataforge.meta
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.values.Value
/**
* The meta implementation which is guaranteed to be immutable.
@ -8,16 +9,25 @@ import space.kscience.dataforge.names.NameToken
* If the argument is possibly mutable node, it is copied on creation
*/
public class SealedMeta internal constructor(
override val items: Map<NameToken, TypedMetaItem<SealedMeta>>,
) : AbstractTypedMeta<SealedMeta>()
override val value: Value?,
override val items: Map<NameToken, SealedMeta>
) : TypedMeta<SealedMeta> {
override fun toString(): String = Meta.toString(this)
override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta)
override fun hashCode(): Int = Meta.hashCode(this)
}
/**
* Generate sealed node from [this]. If it is already sealed return it as is
*/
public fun Meta.seal(): SealedMeta = this as? SealedMeta ?: SealedMeta(items.mapValues { entry -> entry.value.seal() })
public fun Meta.seal(): SealedMeta = this as? SealedMeta ?: SealedMeta(value, items.mapValues { entry ->
entry.value.seal()
})
@Suppress("FunctionName")
public fun Meta(value: Value): SealedMeta = SealedMeta(value, emptyMap())
@Suppress("FunctionName")
public fun Meta(builder: MutableMeta.() -> Unit): SealedMeta =
MutableMeta(builder).seal()
@Suppress("UNCHECKED_CAST")
public fun MetaItem.seal(): TypedMetaItem<SealedMeta> = when (this) {
is MetaItemValue -> this
is MetaItemNode -> MetaItemNode(node.seal())
}

View File

@ -6,13 +6,13 @@ import space.kscience.dataforge.names.asName
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
public interface ReadOnlySpecification<out T : ItemProvider> {
public interface ReadOnlySpecification<out T : Any> {
/**
* Read generic read-only meta with this [Specification] producing instance of desired type.
* The source is not mutated even if it is in theory mutable
*/
public fun read(items: ItemProvider): T
public fun read(source: Meta): T
/**
* Generate an empty object
@ -31,48 +31,49 @@ public interface ReadOnlySpecification<out T : ItemProvider> {
* By convention [Scheme] companion should inherit this class
*
*/
public interface Specification<out T : MutableItemProvider> : ReadOnlySpecification<T> {
public interface Specification<out T : Any> : ReadOnlySpecification<T> {
/**
* Wrap [MutableItemProvider], using it as inner storage (changes to [Specification] are reflected on [MutableItemProvider]
* Wrap [MutableTypedMeta], using it as inner storage (changes to [Specification] are reflected on [MutableTypedMeta]
*/
public fun write(target: MutableItemProvider): T
public fun write(target: MutableMeta): T
}
/**
* Update a [MutableItemProvider] using given specification
* Update a [MutableTypedMeta] using given specification
*/
public fun <T : MutableItemProvider> MutableItemProvider.update(spec: Specification<T>, action: T.() -> Unit) {
spec.write(this).apply(action)
}
public fun <M : MutableTypedMeta<M>, T : Any> M.update(
spec: Specification<T>,
action: T.() -> Unit
): T = spec.write(this).apply(action)
/**
* Update configuration using given specification
*/
public fun <C : MutableItemProvider, S : Specification<C>> Configurable.update(
spec: S,
action: C.() -> Unit,
) {
config.update(spec, action)
}
public fun <T : Any> Configurable.update(
spec: Specification<T>,
action: T.() -> Unit,
): T = spec.write(config).apply(action)
public fun <T : MutableItemProvider> TypedMetaItem<MutableMeta<*>>.withSpec(spec: Specification<T>): T? =
node?.let { spec.write(it) }
//
//public fun <M : MutableTypedMeta<M>> MutableMeta.withSpec(spec: Specification<M>): M? =
// spec.write(it)
/**
* A delegate that uses a [Specification] to wrap a child of this provider
*/
public fun <T : Scheme> MutableItemProvider.spec(
public fun <T : Scheme> MutableMeta.spec(
spec: Specification<T>,
key: Name? = null,
): ReadWriteProperty<Any?, T> = object : ReadWriteProperty<Any?, T> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
val name = key ?: property.name.asName()
return getChild(name).let { spec.write(it) }
return spec.write(getOrCreate(name))
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
val name = key ?: property.name.asName()
set(name, value.toMeta().asMetaItem())
set(name, value.toMeta())
}
}
@ -82,21 +83,13 @@ public fun <T : Scheme> MutableItemProvider.spec(
* The list is a snapshot of children state, so change in structure is not reflected on its composition.
*/
@DFExperimental
public fun <T : Scheme> MutableItemProvider.listOfSpec(
public fun <T : Scheme> MutableMeta.listOfSpec(
spec: Specification<T>,
key: Name? = null,
): ReadWriteProperty<Any?, List<T>> = object : ReadWriteProperty<Any?, List<T>> {
override fun getValue(thisRef: Any?, property: KProperty<*>): List<T> {
val name = key ?: property.name.asName()
return getIndexed(name).map {
when (val value = it.value) {
is MetaItemNode<*> -> when (value.node) {
is MutableItemProvider -> spec.write(value.node)
else -> spec.read(value.node)
}
is MetaItemValue -> spec.read(value)
}
}
return getIndexed(name).values.map { spec.write(it as MutableMeta) }
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: List<T>) {

View File

@ -1,56 +0,0 @@
package space.kscience.dataforge.meta
import kotlinx.serialization.json.Json
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.toName
/**
* A meta node that ensures that all of its descendants has at least the same type
*/
public interface TypedMeta<out M : TypedMeta<M>> : Meta {
override val items: Map<NameToken, TypedMetaItem<M>>
@Suppress("UNCHECKED_CAST")
override fun getItem(name: Name): TypedMetaItem<M>? = super.getItem(name)?.let { it as TypedMetaItem<M> }
//Typed meta guarantees that all children have M type
}
/**
* The same as [Meta.get], but with specific node type
*/
public operator fun <M : TypedMeta<M>> M.get(name: Name): TypedMetaItem<M>? = getItem(name)
public operator fun <M : TypedMeta<M>> M.get(key: String): TypedMetaItem<M>? = this[key.toName()]
public operator fun <M : TypedMeta<M>> M.get(key: NameToken): TypedMetaItem<M>? = items[key]
/**
* Equals, hashcode and to string for any meta
*/
public abstract class MetaBase : Meta {
override fun equals(other: Any?): Boolean = if (other is Meta) {
Meta.equals(this, other)
} else {
false
}
override fun hashCode(): Int = items.hashCode()
override fun toString(): String = json.encodeToString(MetaSerializer, this)
public companion object{
private val json = Json {
prettyPrint = true
useArrayPolymorphism = true
}
}
}
/**
* Equals and hash code implementation for meta node
*/
public abstract class AbstractTypedMeta<M : TypedMeta<M>> : TypedMeta<M>, MetaBase()

View File

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

View File

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

View File

@ -0,0 +1,60 @@
package space.kscience.dataforge.meta.descriptors
import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.names.*
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.ValueType
/**
* The descriptor for a meta
* @param info description text
* @param children child descriptors for this node
* @param multiple True if same name siblings with this name are allowed
* @param required True if the item is required
* @param type list of allowed types for [Meta.value], null if all values are allowed
* @param indexKey An index field by which this node is identified in case of same name siblings construct
* @param defaultValue the default [Meta.value] for the node
* @param attributes additional attributes of this descriptor. For example validation and widget parameters
*/
@Serializable
public data class MetaDescriptor(
public val info: String? = null,
public val children: Map<String, MetaDescriptor> = emptyMap(),
public val multiple: Boolean = false,
public val required: Boolean = false,
public val type: List<ValueType>? = null,
public val indexKey: String = Meta.INDEX_KEY,
public val defaultValue: Value? = null,
public val attributes: Meta = Meta.EMPTY,
)
public operator fun MetaDescriptor.get(name: Name): MetaDescriptor? = when (name.length) {
0 -> this
1 -> children[name.firstOrNull()!!.toString()]
else -> get(name.firstOrNull()!!.asName())?.get(name.cutFirst())
}
public operator fun MetaDescriptor.get(name: String): MetaDescriptor? = get(name.toName())
public class MetaDescriptorBuilder {
public var info: String? = null
public var children: MutableMap<String, MetaDescriptor> = hashMapOf()
public var multiple: Boolean = false
public var required: Boolean = false
public var type: List<ValueType>? = null
public var indexKey: String = Meta.INDEX_KEY
public var default: Value? = null
public var attributes: Meta = Meta.EMPTY
internal fun build(): MetaDescriptor = MetaDescriptor(
info = info,
children = children,
multiple = multiple,
required = required,
type = type,
indexKey = indexKey,
defaultValue = default,
attributes = attributes
)
}

View File

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

View File

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

View File

@ -4,10 +4,10 @@ import space.kscience.dataforge.names.Name
import space.kscience.dataforge.values.ValueType
import space.kscience.dataforge.values.asValue
public inline fun <reified E : Enum<E>> NodeDescriptorBuilder.enum(
public inline fun <reified E : Enum<E>> MetaDescriptorBuilder.enum(
key: Name,
default: E?,
crossinline modifier: ValueDescriptor.() -> Unit = {},
crossinline modifier: MetaDescriptor.() -> Unit = {},
): Unit = value(key) {
type(ValueType.STRING)
default?.let {

View File

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

View File

@ -21,12 +21,12 @@ public interface TransformationRule {
* @return a sequence of item paths to be transformed
*/
public fun selectItems(meta: Meta): Sequence<Name> =
meta.itemSequence().filter { matches(it.first, it.second) }.map { it.first }
meta.nodeSequence().filter { matches(it.first, it.second) }.map { it.first }
/**
* Apply transformation for a single item (Node or Value) to the target
*/
public fun <M : MutableMeta<M>> transformItem(name: Name, item: MetaItem?, target: M): Unit
public fun transformItem(name: Name, item: MetaItem?, target: MutableTypedMeta): Unit
}
/**
@ -39,9 +39,9 @@ public data class KeepTransformationRule(val selector: (Name) -> Boolean) :
}
override fun selectItems(meta: Meta): Sequence<Name> =
meta.itemSequence().map { it.first }.filter(selector)
meta.nodeSequence().map { it.first }.filter(selector)
override fun <M : MutableMeta<M>> transformItem(name: Name, item: MetaItem?, target: M) {
override fun transformItem(name: Name, item: MetaItem?, target: MutableTypedMeta) {
if (selector(name)) target.set(name, item)
}
}
@ -51,7 +51,7 @@ public data class KeepTransformationRule(val selector: (Name) -> Boolean) :
*/
public data class SingleItemTransformationRule(
val from: Name,
val transform: MutableMeta<*>.(Name, MetaItem?) -> Unit,
val transform: MutableTypedMeta.(Name, MetaItem?) -> Unit,
) : TransformationRule {
override fun matches(name: Name, item: MetaItem?): Boolean {
return name == from
@ -59,7 +59,7 @@ public data class SingleItemTransformationRule(
override fun selectItems(meta: Meta): Sequence<Name> = sequenceOf(from)
override fun <M : MutableMeta<M>> transformItem(name: Name, item: MetaItem?, target: M) {
override fun transformItem(name: Name, item: MetaItem?, target: MutableTypedMeta) {
if (name == this.from) {
target.transform(name, item)
}
@ -68,13 +68,13 @@ public data class SingleItemTransformationRule(
public data class RegexItemTransformationRule(
val from: Regex,
val transform: MutableMeta<*>.(name: Name, MatchResult, MetaItem?) -> Unit,
val transform: MutableTypedMeta.(name: Name, MatchResult, MetaItem?) -> Unit,
) : TransformationRule {
override fun matches(name: Name, item: MetaItem?): Boolean {
return from.matches(name.toString())
}
override fun <M : MutableMeta<M>> transformItem(name: Name, item: MetaItem?, target: M) {
override fun transformItem(name: Name, item: MetaItem?, target: MutableTypedMeta) {
val match = from.matchEntire(name.toString())
if (match != null) {
target.transform(name, match, item)
@ -105,7 +105,7 @@ public value class MetaTransformation(private val transformations: Collection<Tr
* Generate an observable configuration that contains only elements defined by transformation rules and changes with the source
*/
@DFExperimental
public fun generate(source: ObservableMeta): ObservableItemProvider = ObservableMeta().apply {
public fun generate(source: MutableMeta): ObservableMeta = MutableMeta().apply {
transformations.forEach { rule ->
rule.selectItems(source).forEach { name ->
rule.transformItem(name, source[name], this)
@ -131,7 +131,7 @@ public value class MetaTransformation(private val transformations: Collection<Tr
/**
* Listens for changes in the source node and translates them into second node if transformation set contains a corresponding rule.
*/
public fun <M : MutableMeta<M>> bind(source: ObservableMeta, target: M) {
public fun bind(source: ObservableMeta, target: MutableTypedMeta) {
source.onChange(target) { name, _, newItem ->
transformations.forEach { t ->
if (t.matches(name, newItem)) {

View File

@ -1,7 +1,7 @@
package space.kscience.dataforge.values
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaBuilder
import space.kscience.dataforge.meta.MutableMeta
/**
* Check if value is null
@ -35,4 +35,4 @@ public val Value.doubleArray: DoubleArray
}
public fun Value.toMeta(): MetaBuilder = Meta { Meta.VALUE_KEY put this }
public fun Value.toMeta(): MutableMeta = Meta { Meta.VALUE_KEY put this }

View File

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

View File

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

View File

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

View File

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

View File

@ -38,7 +38,7 @@ public fun Meta.toDynamic(): dynamic {
return res
}
public class DynamicMeta(internal val obj: dynamic) : MetaBase() {
public class DynamicMeta(internal val obj: dynamic) : AbstractTypedMeta() {
private fun keys(): Array<String> = js("Object").keys(obj)
private fun isArray(@Suppress("UNUSED_PARAMETER") obj: dynamic): Boolean =

View File

@ -2,7 +2,7 @@ package space.kscience.dataforge.workspace
import space.kscience.dataforge.context.ContextAware
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaBuilder
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.misc.Type
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.toName
@ -55,5 +55,5 @@ public suspend fun Workspace.produce(task: String, target: String): TaskResult<*
public suspend fun Workspace.produce(task: String, meta: Meta): TaskResult<*> =
produce(task.toName(), meta)
public suspend fun Workspace.produce(task: String, block: MetaBuilder.() -> Unit = {}): TaskResult<*> =
public suspend fun Workspace.produce(task: String, block: MutableMeta.() -> Unit = {}): TaskResult<*> =
produce(task, Meta(block))

View File

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

View File

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

View File

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