Cover corner cases in JsonMeta

This commit is contained in:
Alexander Nozik 2021-07-30 09:17:46 +03:00
parent a3479e74f7
commit c8bd3390cb
43 changed files with 767 additions and 638 deletions

View File

@ -10,6 +10,7 @@
- Kotlin 1.5.10 - Kotlin 1.5.10
- Build tools 0.10.0 - Build tools 0.10.0
- Relaxed type restriction on `MetaConverter`. Now nullables are available. - Relaxed type restriction on `MetaConverter`. Now nullables are available.
- **Huge API-breaking refactoring of Meta**. Meta now can hava both value and children.
### Deprecated ### Deprecated
- Direct use of `Config` - Direct use of `Config`
@ -17,6 +18,7 @@
### Removed ### Removed
- Public PluginManager mutability - Public PluginManager mutability
- Tables and tables-exposed moved to the separate project `tables.kt` - Tables and tables-exposed moved to the separate project `tables.kt`
- BinaryMetaFormat. Use CBOR encoding instead
### Fixed ### Fixed
- Proper json array index treatment. - Proper json array index treatment.

View File

@ -95,8 +95,8 @@ public open class Context internal constructor(
override fun toMeta(): Meta = Meta { override fun toMeta(): Meta = Meta {
"parent" to parent?.name "parent" to parent?.name
"properties" put properties.layers.firstOrNull() properties.layers.firstOrNull()?.let { set("properties", it) }
"plugins" put plugins.map { it.toMeta().asMetaItem() } "plugins" put plugins.map { it.toMeta() }
} }
public companion object { public companion object {

View File

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

View File

@ -3,6 +3,7 @@ package space.kscience.dataforge.properties
import space.kscience.dataforge.meta.ObservableMeta import space.kscience.dataforge.meta.ObservableMeta
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.startsWith
import space.kscience.dataforge.names.toName import space.kscience.dataforge.names.toName
import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KMutableProperty1
@ -16,8 +17,8 @@ public fun <P : ObservableMeta, T : Any> P.property(property: KMutableProperty1<
} }
override fun onChange(owner: Any?, callback: (T?) -> Unit) { override fun onChange(owner: Any?, callback: (T?) -> Unit) {
this@property.onChange(this) { name, oldItem, newItem -> this@property.onChange(this) { name ->
if (name.startsWith(property.name.toName()) && oldItem != newItem) { if (name.startsWith(property.name.toName())) {
callback(property.get(this@property)) callback(property.get(this@property))
} }
} }

View File

@ -4,7 +4,6 @@ import kotlinx.coroutines.*
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import space.kscience.dataforge.data.Data.Companion.TYPE_OF_NOTHING import space.kscience.dataforge.data.Data.Companion.TYPE_OF_NOTHING
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.set
import space.kscience.dataforge.names.* import space.kscience.dataforge.names.*
import kotlin.reflect.KType import kotlin.reflect.KType

View File

@ -54,7 +54,7 @@ public interface GroupRule {
val data = set.getData(name) val data = set.getData(name)
@Suppress("NULLABLE_EXTENSION_OPERATOR_WITH_SAFE_CALL_RECEIVER") @Suppress("NULLABLE_EXTENSION_OPERATOR_WITH_SAFE_CALL_RECEIVER")
val tagValue = data?.meta[key]?.string ?: defaultTagValue val tagValue = data?.meta?.get(key)?.string ?: defaultTagValue
map.getOrPut(tagValue) { ActiveDataTree(set.dataType) }.emit(name, data) map.getOrPut(tagValue) { ActiveDataTree(set.dataType) }.emit(name, data)
} }
} }

View File

@ -10,43 +10,49 @@ import space.kscience.dataforge.io.MetaFormat
import space.kscience.dataforge.io.MetaFormatFactory import space.kscience.dataforge.io.MetaFormatFactory
import space.kscience.dataforge.io.readUtf8String import space.kscience.dataforge.io.readUtf8String
import space.kscience.dataforge.io.writeUtf8String import space.kscience.dataforge.io.writeUtf8String
import space.kscience.dataforge.meta.* import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.descriptors.ItemDescriptor import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.NodeDescriptor import space.kscience.dataforge.meta.descriptors.get
import space.kscience.dataforge.meta.isLeaf
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.NameToken import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.withIndex import space.kscience.dataforge.names.withIndex
import space.kscience.dataforge.values.ListValue import space.kscience.dataforge.values.ListValue
import space.kscience.dataforge.values.Null import space.kscience.dataforge.values.Null
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.parseValue import space.kscience.dataforge.values.parseValue
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.set
public fun Meta.toYaml(): YamlMap { public fun Meta.toYaml(): YamlMap {
val map: Map<String, Any?> = items.entries.associate { (key, item) -> val map: Map<String, Any?> = items.entries.associate { (key, item) ->
key.toString() to when (item) { key.toString() to if (item.isLeaf) {
is MetaItemValue -> { item.value?.value
item.value.value } else {
} item.toYaml()
is MetaItemNode -> {
item.node.toYaml()
}
} }
} }
return YamlMap(map) return YamlMap(map)
} }
private class YamlMeta(private val yamlMap: YamlMap, private val descriptor: NodeDescriptor? = null) : AbstractTypedMeta() { private class YamlMeta(private val yamlMap: YamlMap, private val descriptor: MetaDescriptor? = null) : Meta {
private fun buildItems(): Map<NameToken, MetaItem> { override val value: Value?
val map = LinkedHashMap<NameToken, MetaItem>() get() = yamlMap.getStringOrNull(null)?.parseValue()
private fun buildItems(): Map<NameToken, Meta> {
val map = LinkedHashMap<NameToken, Meta>()
yamlMap.content.entries.forEach { (key, value) -> yamlMap.content.entries.forEach { (key, value) ->
val stringKey = key.toString() val stringKey = key.toString()
val itemDescriptor = descriptor?.items?.get(stringKey) val itemDescriptor = descriptor?.get(stringKey)
val token = NameToken(stringKey) val token = NameToken(stringKey)
when (value) { when (value) {
YamlNull -> Null.asMetaItem() YamlNull -> Meta(Null)
is YamlLiteral -> map[token] = value.content.parseValue().asMetaItem() is YamlLiteral -> map[token] = Meta(value.content.parseValue())
is YamlMap -> map[token] = value.toMeta().asMetaItem() is YamlMap -> map[token] = value.toMeta()
is YamlList -> if (value.all { it is YamlLiteral }) { is YamlList -> if (value.all { it is YamlLiteral }) {
val listValue = ListValue( val listValue = ListValue(
value.map { value.map {
@ -54,29 +60,33 @@ private class YamlMeta(private val yamlMap: YamlMap, private val descriptor: Nod
(it as YamlLiteral).content.parseValue() (it as YamlLiteral).content.parseValue()
} }
) )
map[token] = MetaItemValue(listValue) map[token] = Meta(listValue)
} else value.forEachIndexed { index, yamlElement -> } else value.forEachIndexed { index, yamlElement ->
val indexKey = (itemDescriptor as? NodeDescriptor)?.indexKey ?: ItemDescriptor.DEFAULT_INDEX_KEY val indexKey = itemDescriptor?.indexKey
val indexValue: String = (yamlElement as? YamlMap)?.getStringOrNull(indexKey) val indexValue: String = (yamlElement as? YamlMap)?.getStringOrNull(indexKey)
?: index.toString() //In case index is non-string, the backward transformation will be broken. ?: index.toString() //In case index is non-string, the backward transformation will be broken.
val tokenWithIndex = token.withIndex(indexValue) val tokenWithIndex = token.withIndex(indexValue)
map[tokenWithIndex] = yamlElement.toMetaItem(itemDescriptor) map[tokenWithIndex] = yamlElement.toMeta(itemDescriptor)
} }
} }
} }
return map return map
} }
override val items: Map<NameToken, MetaItem> get() = buildItems() override val items: Map<NameToken, Meta> get() = buildItems()
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)
} }
public fun YamlElement.toMetaItem(descriptor: ItemDescriptor? = null): MetaItem = when (this) { public fun YamlElement.toMeta(descriptor: MetaDescriptor? = null): Meta = when (this) {
YamlNull -> Null.asMetaItem() YamlNull -> Meta(Null)
is YamlLiteral -> content.parseValue().asMetaItem() is YamlLiteral -> Meta(content.parseValue())
is YamlMap -> toMeta().asMetaItem() is YamlMap -> toMeta()
//We can't return multiple items therefore we create top level node //We can't return multiple items therefore we create top level node
is YamlList -> YamlMap(mapOf("@yamlArray" to this)).toMetaItem(descriptor) is YamlList -> YamlMap(mapOf("@yamlArray" to this)).toMeta(descriptor)
} }
public fun YamlMap.toMeta(): Meta = YamlMeta(this) public fun YamlMap.toMeta(): Meta = YamlMeta(this)
@ -88,13 +98,13 @@ public fun YamlMap.toMeta(): Meta = YamlMeta(this)
@DFExperimental @DFExperimental
public class YamlMetaFormat(private val meta: Meta) : MetaFormat { public class YamlMetaFormat(private val meta: Meta) : MetaFormat {
override fun writeMeta(output: Output, meta: Meta, descriptor: NodeDescriptor?) { override fun writeMeta(output: Output, meta: Meta, descriptor: MetaDescriptor?) {
val yaml = meta.toYaml() val yaml = meta.toYaml()
val string = Yaml.encodeToString(yaml) val string = Yaml.encodeToString(yaml)
output.writeUtf8String(string) output.writeUtf8String(string)
} }
override fun readMeta(input: Input, descriptor: NodeDescriptor?): Meta { override fun readMeta(input: Input, descriptor: MetaDescriptor?): Meta {
val yaml = Yaml.decodeYamlMapFromString(input.readUtf8String()) val yaml = Yaml.decodeYamlMapFromString(input.readUtf8String())
return yaml.toMeta() return yaml.toMeta()
} }
@ -113,10 +123,10 @@ public class YamlMetaFormat(private val meta: Meta) : MetaFormat {
private val default = YamlMetaFormat() private val default = YamlMetaFormat()
override fun writeMeta(output: Output, meta: Meta, descriptor: NodeDescriptor?): Unit = override fun writeMeta(output: Output, meta: Meta, descriptor: MetaDescriptor?): Unit =
default.writeMeta(output, meta, descriptor) default.writeMeta(output, meta, descriptor)
override fun readMeta(input: Input, descriptor: NodeDescriptor?): Meta = override fun readMeta(input: Input, descriptor: MetaDescriptor?): Meta =
default.readMeta(input, descriptor) default.readMeta(input, descriptor)
} }
} }

View File

@ -1,133 +0,0 @@
package space.kscience.dataforge.io
import io.ktor.utils.io.core.*
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.values.*
/**
* A DataForge-specific simplified binary format for meta
* TODO add description
*/
public object BinaryMetaFormat : MetaFormat, MetaFormatFactory {
override val shortName: String = "bin"
override val key: Short = 0x4249//BI
override fun invoke(meta: Meta, context: Context): MetaFormat = this
override fun readMeta(input: Input, descriptor: NodeDescriptor?): Meta {
return (input.readMetaItem() as MetaItemNode).node
}
private fun Output.writeChar(char: Char) = writeByte(char.code.toByte())
private fun Output.writeString(str: String) {
writeInt(str.length)
writeFully(str.encodeToByteArray())
}
public fun Output.writeValue(value: Value): Unit = when (value.type) {
ValueType.NUMBER -> when (value.value) {
is Short -> {
writeChar('s')
writeShort(value.short)
}
is Int -> {
writeChar('i')
writeInt(value.int)
}
is Long -> {
writeChar('l')
writeLong(value.long)
}
is Float -> {
writeChar('f')
writeFloat(value.float)
}
else -> {
writeChar('d')
writeDouble(value.double)
}
}
ValueType.STRING -> {
writeChar('S')
writeString(value.string)
}
ValueType.BOOLEAN -> {
if (value.boolean) {
writeChar('+')
} else {
writeChar('-')
}
}
ValueType.NULL -> {
writeChar('N')
}
ValueType.LIST -> {
writeChar('L')
writeInt(value.list.size)
value.list.forEach {
writeValue(it)
}
}
}
override fun writeMeta(
output: Output,
meta: Meta,
descriptor: space.kscience.dataforge.meta.descriptors.NodeDescriptor?,
) {
output.writeChar('M')
output.writeInt(meta.items.size)
meta.items.forEach { (key, item) ->
output.writeString(key.toString())
when (item) {
is MetaItemValue -> {
output.writeValue(item.value)
}
is MetaItemNode -> {
writeObject(output, item.node)
}
}
}
}
private fun Input.readString(): String {
val length = readInt()
val array = readBytes(length)
return array.decodeToString()
}
@Suppress("UNCHECKED_CAST")
public fun Input.readMetaItem(): TypedMetaItem<MutableMeta> {
return when (val keyChar = readByte().toInt().toChar()) {
'S' -> MetaItemValue(StringValue(readString()))
'N' -> MetaItemValue(Null)
'+' -> MetaItemValue(True)
'-' -> MetaItemValue(True)
's' -> MetaItemValue(NumberValue(readShort()))
'i' -> MetaItemValue(NumberValue(readInt()))
'l' -> MetaItemValue(NumberValue(readInt()))
'f' -> MetaItemValue(NumberValue(readFloat()))
'd' -> MetaItemValue(NumberValue(readDouble()))
'L' -> {
val length = readInt()
val list = (1..length).map { (readMetaItem() as MetaItemValue).value }
MetaItemValue(Value.of(list))
}
'M' -> {
val length = readInt()
val meta = Meta {
(1..length).forEach { _ ->
val name = readString()
val item = readMetaItem()
set(name, item)
}
}
MetaItemNode(meta)
}
else -> error("Unknown serialization key character: $keyChar")
}
}
}

View File

@ -15,7 +15,7 @@ import space.kscience.dataforge.names.toName
private class PartDescriptor : Scheme() { private class PartDescriptor : Scheme() {
var offset by int(0) var offset by int(0)
var size by int(0) var size by int(0)
var partMeta by node("meta".toName()) var partMeta by item("meta".toName())
companion object : SchemeSpec<PartDescriptor>(::PartDescriptor) { companion object : SchemeSpec<PartDescriptor>(::PartDescriptor) {
val MULTIPART_KEY = ENVELOPE_NODE_KEY + "multipart" val MULTIPART_KEY = ENVELOPE_NODE_KEY + "multipart"
@ -86,15 +86,15 @@ public fun EnvelopeBuilder.envelopes(
public fun Envelope.parts(): EnvelopeParts { public fun Envelope.parts(): EnvelopeParts {
if (data == null) return emptyList() if (data == null) return emptyList()
//TODO add zip folder reader //TODO add zip folder reader
val parts = meta.getIndexed(PARTS_KEY).values.mapNotNull { it.node }.map { val parts = meta.getIndexed(PARTS_KEY).values.map {
PartDescriptor.read(it) PartDescriptor.read(it)
} }
return if (parts.isEmpty()) { return if (parts.isEmpty()) {
listOf(EnvelopePart(data!!, meta[MULTIPART_KEY].node)) listOf(EnvelopePart(data!!, meta[MULTIPART_KEY]))
} else { } else {
parts.map { parts.map {
val binary = data!!.view(it.offset, it.size) val binary = data!!.view(it.offset, it.size)
val meta = Laminate(it.partMeta, meta[MULTIPART_KEY].node) val meta = Laminate(it.partMeta, meta[MULTIPART_KEY])
EnvelopePart(binary, meta) EnvelopePart(binary, meta)
} }
} }

View File

@ -6,13 +6,11 @@ import space.kscience.dataforge.context.Factory
import space.kscience.dataforge.io.IOFormat.Companion.NAME_KEY import space.kscience.dataforge.io.IOFormat.Companion.NAME_KEY
import space.kscience.dataforge.io.IOFormatFactory.Companion.IO_FORMAT_TYPE import space.kscience.dataforge.io.IOFormatFactory.Companion.IO_FORMAT_TYPE
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaItemValue
import space.kscience.dataforge.meta.MetaRepr import space.kscience.dataforge.meta.MetaRepr
import space.kscience.dataforge.misc.Named import space.kscience.dataforge.misc.Named
import space.kscience.dataforge.misc.Type import space.kscience.dataforge.misc.Type
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.asName
import space.kscience.dataforge.values.Value
import kotlin.reflect.KType import kotlin.reflect.KType
import kotlin.reflect.typeOf import kotlin.reflect.typeOf
@ -117,19 +115,19 @@ public object DoubleIOFormat : IOFormat<Double>, IOFormatFactory<Double> {
override fun readObject(input: Input): Double = input.readDouble() override fun readObject(input: Input): Double = input.readDouble()
} }
public object ValueIOFormat : IOFormat<Value>, IOFormatFactory<Value> { //public object ValueIOFormat : IOFormat<Value>, IOFormatFactory<Value> {
override fun invoke(meta: Meta, context: Context): IOFormat<Value> = this // override fun invoke(meta: Meta, context: Context): IOFormat<Value> = this
//
override val name: Name = "value".asName() // override val name: Name = "value".asName()
//
override val type: KType get() = typeOf<Value>() // override val type: KType get() = typeOf<Value>()
//
override fun writeObject(output: Output, obj: Value) { // override fun writeObject(output: Output, obj: Value) {
BinaryMetaFormat.run { output.writeValue(obj) } // BinaryMetaFormat.run { output.writeValue(obj) }
} // }
//
override fun readObject(input: Input): Value { // override fun readObject(input: Input): Value {
return (BinaryMetaFormat.run { input.readMetaItem() } as? MetaItemValue)?.value // return (BinaryMetaFormat.run { input.readMetaItem() } as? MetaItemValue)?.value
?: error("The item is not a value") // ?: error("The item is not a value")
} // }
} //}

View File

@ -6,7 +6,9 @@ import space.kscience.dataforge.io.IOFormat.Companion.META_KEY
import space.kscience.dataforge.io.IOFormat.Companion.NAME_KEY import space.kscience.dataforge.io.IOFormat.Companion.NAME_KEY
import space.kscience.dataforge.io.IOFormatFactory.Companion.IO_FORMAT_TYPE import space.kscience.dataforge.io.IOFormatFactory.Companion.IO_FORMAT_TYPE
import space.kscience.dataforge.io.MetaFormatFactory.Companion.META_FORMAT_TYPE import space.kscience.dataforge.io.MetaFormatFactory.Companion.META_FORMAT_TYPE
import space.kscience.dataforge.meta.* import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.string
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.toName import space.kscience.dataforge.names.toName
import kotlin.native.concurrent.ThreadLocal import kotlin.native.concurrent.ThreadLocal
@ -19,13 +21,13 @@ public class IOPlugin(meta: Meta) : AbstractPlugin(meta) {
context.gather<IOFormatFactory<*>>(IO_FORMAT_TYPE).values context.gather<IOFormatFactory<*>>(IO_FORMAT_TYPE).values
} }
public fun <T : Any> resolveIOFormat(item: MetaItem, type: KClass<out T>): IOFormat<T>? { public fun <T : Any> resolveIOFormat(item: Meta, type: KClass<out T>): IOFormat<T>? {
val key = item.string ?: item.node[NAME_KEY]?.string ?: error("Format name not defined") val key = item.string ?: item[NAME_KEY]?.string ?: error("Format name not defined")
val name = key.toName() val name = key.toName()
return ioFormatFactories.find { it.name == name }?.let { return ioFormatFactories.find { it.name == name }?.let {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
if (it.type != type) error("Format type ${it.type} is not the same as requested type $type") if (it.type != type) error("Format type ${it.type} is not the same as requested type $type")
else it.invoke(item.node[META_KEY].node ?: Meta.EMPTY, context) as IOFormat<T> else it.invoke(item[META_KEY] ?: Meta.EMPTY, context) as IOFormat<T>
} }
} }
@ -47,9 +49,9 @@ public class IOPlugin(meta: Meta) : AbstractPlugin(meta) {
private fun resolveEnvelopeFormat(name: Name, meta: Meta = Meta.EMPTY): EnvelopeFormat? = private fun resolveEnvelopeFormat(name: Name, meta: Meta = Meta.EMPTY): EnvelopeFormat? =
envelopeFormatFactories.find { it.name == name }?.invoke(meta, context) envelopeFormatFactories.find { it.name == name }?.invoke(meta, context)
public fun resolveEnvelopeFormat(item: MetaItem): EnvelopeFormat? { public fun resolveEnvelopeFormat(item: Meta): EnvelopeFormat? {
val name = item.string ?: item.node[NAME_KEY]?.string ?: error("Envelope format name not defined") val name = item.string ?: item[NAME_KEY]?.string ?: error("Envelope format name not defined")
val meta = item.node[META_KEY].node ?: Meta.EMPTY val meta = item[META_KEY] ?: Meta.EMPTY
return resolveEnvelopeFormat(name.toName(), meta) return resolveEnvelopeFormat(name.toName(), meta)
} }
@ -62,7 +64,7 @@ public class IOPlugin(meta: Meta) : AbstractPlugin(meta) {
} }
public companion object : PluginFactory<IOPlugin> { public companion object : PluginFactory<IOPlugin> {
public val defaultMetaFormats: List<MetaFormatFactory> = listOf(JsonMetaFormat, BinaryMetaFormat) public val defaultMetaFormats: List<MetaFormatFactory> = listOf(JsonMetaFormat)
public val defaultEnvelopeFormats: List<EnvelopeFormatFactory> = public val defaultEnvelopeFormats: List<EnvelopeFormatFactory> =
listOf(TaggedEnvelopeFormat, TaglessEnvelopeFormat) listOf(TaggedEnvelopeFormat, TaglessEnvelopeFormat)

View File

@ -7,7 +7,7 @@ import io.ktor.utils.io.core.use
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
import space.kscience.dataforge.io.MetaFormatFactory.Companion.META_FORMAT_TYPE import space.kscience.dataforge.io.MetaFormatFactory.Companion.META_FORMAT_TYPE
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.descriptors.NodeDescriptor import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.misc.Type import space.kscience.dataforge.misc.Type
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.asName
@ -30,10 +30,10 @@ public interface MetaFormat : IOFormat<Meta> {
public fun writeMeta( public fun writeMeta(
output: Output, output: Output,
meta: Meta, meta: Meta,
descriptor: NodeDescriptor? = null, descriptor: MetaDescriptor? = null,
) )
public fun readMeta(input: Input, descriptor: NodeDescriptor? = null): Meta public fun readMeta(input: Input, descriptor: MetaDescriptor? = null): Meta
} }
@Type(META_FORMAT_TYPE) @Type(META_FORMAT_TYPE)

View File

@ -2,11 +2,12 @@ package space.kscience.dataforge.io
import kotlinx.serialization.json.* import kotlinx.serialization.json.*
import space.kscience.dataforge.meta.* import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.JsonMeta.Companion.JSON_ARRAY_KEY
import space.kscience.dataforge.values.ListValue import space.kscience.dataforge.values.ListValue
import space.kscience.dataforge.values.double
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
fun Meta.toByteArray(format: MetaFormat = JsonMetaFormat) = buildByteArray { fun Meta.toByteArray(format: MetaFormat = JsonMetaFormat) = buildByteArray {
format.writeObject(this@buildByteArray, this@toByteArray) format.writeObject(this@buildByteArray, this@toByteArray)
} }
@ -16,20 +17,6 @@ fun MetaFormat.fromByteArray(packet: ByteArray): Meta {
} }
class MetaFormatTest { class MetaFormatTest {
@Test
fun testBinaryMetaFormat() {
val meta = Meta {
"a" put 22
"node" put {
"b" put "DDD"
"c" put 11.1
"array" put doubleArrayOf(1.0, 2.0, 3.0)
}
}
val bytes = meta.toByteArray(BinaryMetaFormat)
val result = BinaryMetaFormat.fromByteArray(bytes)
assertEquals(meta, result)
}
@Test @Test
fun testJsonMetaFormat() { fun testJsonMetaFormat() {
@ -50,36 +37,36 @@ class MetaFormatTest {
if (meta[it] != result[it]) error("${meta[it]} != ${result[it]}") if (meta[it] != result[it]) error("${meta[it]} != ${result[it]}")
} }
assertEquals<Meta>(meta, result) assertEquals(meta, result)
} }
@Test @Test
fun testJsonToMeta() { fun testJsonToMeta() {
val json = buildJsonArray { val json = buildJsonArray {
//top level array //top level array
add(buildJsonArray { addJsonArray {
add(JsonPrimitive(88)) add(88)
add(buildJsonObject { addJsonObject {
put("c", "aasdad") put("c", "aasdad")
put("d", true) put("d", true)
})
})
add("value")
add(buildJsonArray {
add(JsonPrimitive(1.0))
add(JsonPrimitive(2.0))
add(JsonPrimitive(3.0))
})
} }
val meta = json.toMeta().node!! }
add("value")
addJsonArray {
add(1.0)
add(2.0)
add(3.0)
}
}
val meta = json.toMeta()
assertEquals(true, meta["$JSON_ARRAY_KEY[0].$JSON_ARRAY_KEY[1].d"].boolean) assertEquals(true, meta["${Meta.JSON_ARRAY_KEY}[0].${Meta.JSON_ARRAY_KEY}[1].d"].boolean)
assertEquals("value", meta["$JSON_ARRAY_KEY[1]"].string) assertEquals("value", meta["${Meta.JSON_ARRAY_KEY}[1]"].string)
assertEquals(listOf(1.0, 2.0, 3.0), meta["$JSON_ARRAY_KEY[2"].value?.list?.map { it.number.toDouble() }) assertEquals(listOf(1.0, 2.0, 3.0), meta["${Meta.JSON_ARRAY_KEY}[2]"]?.value?.list?.map { it.double })
} }
@Test @Test
fun testJsonStringToMeta(){ fun testJsonStringToMeta() {
val jsonString = """ val jsonString = """
{ {
"comments": [ "comments": [
@ -97,8 +84,8 @@ class MetaFormatTest {
} }
""".trimIndent() """.trimIndent()
val json = Json.parseToJsonElement(jsonString) val json = Json.parseToJsonElement(jsonString)
val meta = json.toMeta().node!! val meta = json.toMeta()
assertEquals(ListValue.EMPTY, meta["comments"].value) assertEquals(ListValue.EMPTY, meta["comments"]?.value)
} }
} }

View File

@ -5,7 +5,7 @@ import kotlinx.serialization.cbor.Cbor
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaSerializer import space.kscience.dataforge.meta.MetaSerializer
import space.kscience.dataforge.meta.TypedMetaItem import space.kscience.dataforge.meta.seal
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.toName import space.kscience.dataforge.names.toName
import kotlin.test.Test import kotlin.test.Test
@ -28,7 +28,7 @@ class MetaSerializerTest {
fun testMetaSerialization() { fun testMetaSerialization() {
val string = JSON_PRETTY.encodeToString(MetaSerializer, meta) val string = JSON_PRETTY.encodeToString(MetaSerializer, meta)
println(string) println(string)
val restored = JSON_PLAIN.decodeFromString(MetaSerializer, string) val restored = JSON_PLAIN.decodeFromString(MetaSerializer, string).seal()
assertEquals(meta, restored) assertEquals(meta, restored)
} }
@ -52,7 +52,7 @@ class MetaSerializerTest {
@OptIn(ExperimentalSerializationApi::class) @OptIn(ExperimentalSerializationApi::class)
@Test @Test
fun testMetaItemDescriptor() { fun testMetaItemDescriptor() {
val descriptor = TypedMetaItem.serializer(MetaSerializer).descriptor.getElementDescriptor(0) val descriptor = MetaSerializer.descriptor.getElementDescriptor(0)
println(descriptor) println(descriptor)
} }
} }

View File

@ -3,7 +3,7 @@ package space.kscience.dataforge.io
import io.ktor.utils.io.core.* import io.ktor.utils.io.core.*
import io.ktor.utils.io.streams.asOutput import io.ktor.utils.io.streams.asOutput
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.descriptors.NodeDescriptor import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.isEmpty import space.kscience.dataforge.meta.isEmpty
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DFExperimental
import java.nio.file.Files import java.nio.file.Files
@ -97,7 +97,7 @@ public inline fun <reified T : Any> IOPlugin.resolveIOFormat(): IOFormat<T>? {
public fun IOPlugin.readMetaFile( public fun IOPlugin.readMetaFile(
path: Path, path: Path,
formatOverride: MetaFormat? = null, formatOverride: MetaFormat? = null,
descriptor: NodeDescriptor? = null, descriptor: MetaDescriptor? = null,
): Meta { ): Meta {
if (!Files.exists(path)) error("Meta file $path does not exist") if (!Files.exists(path)) error("Meta file $path does not exist")
@ -125,7 +125,7 @@ public fun IOPlugin.writeMetaFile(
path: Path, path: Path,
meta: Meta, meta: Meta,
metaFormat: MetaFormatFactory = JsonMetaFormat, metaFormat: MetaFormatFactory = JsonMetaFormat,
descriptor: NodeDescriptor? = null, descriptor: MetaDescriptor? = null,
) { ) {
val actualPath = if (Files.isDirectory(path)) { val actualPath = if (Files.isDirectory(path)) {
path.resolve("@" + metaFormat.name.toString()) path.resolve("@" + metaFormat.name.toString())

View File

@ -4,9 +4,13 @@ package space.kscience.dataforge.meta
import kotlinx.serialization.json.* import kotlinx.serialization.json.*
import space.kscience.dataforge.meta.descriptors.MetaDescriptor import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.get
import space.kscience.dataforge.names.NameToken import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.values.* import space.kscience.dataforge.values.*
private const val jsonArrayKey: String = "@jsonArray"
public val Meta.Companion.JSON_ARRAY_KEY: String get() = jsonArrayKey
/** /**
* @param descriptor reserved for custom serialization in future * @param descriptor reserved for custom serialization in future
@ -15,7 +19,7 @@ public fun Value.toJson(descriptor: MetaDescriptor? = null): JsonElement = when
ValueType.NUMBER -> JsonPrimitive(numberOrNull) ValueType.NUMBER -> JsonPrimitive(numberOrNull)
ValueType.STRING -> JsonPrimitive(string) ValueType.STRING -> JsonPrimitive(string)
ValueType.BOOLEAN -> JsonPrimitive(boolean) ValueType.BOOLEAN -> JsonPrimitive(boolean)
ValueType.LIST -> JsonArray(list.map { it.toJson() }) ValueType.LIST -> JsonArray(list.map { it.toJson(descriptor) })
ValueType.NULL -> JsonNull ValueType.NULL -> JsonNull
} }
@ -46,7 +50,7 @@ private fun Meta.toJsonWithIndex(descriptor: MetaDescriptor?, index: String?): J
//Add index if needed //Add index if needed
if (index != null) { if (index != null) {
pairs += Meta.INDEX_KEY to JsonPrimitive(index) pairs += (descriptor?.indexKey ?: Meta.INDEX_KEY) to JsonPrimitive(index)
} }
//Add value if needed //Add value if needed
@ -59,8 +63,9 @@ private fun Meta.toJsonWithIndex(descriptor: MetaDescriptor?, index: String?): J
public fun Meta.toJson(descriptor: MetaDescriptor? = null): JsonElement = toJsonWithIndex(descriptor, null) public fun Meta.toJson(descriptor: MetaDescriptor? = null): JsonElement = toJsonWithIndex(descriptor, null)
public fun JsonObject.toMeta(descriptor: MetaDescriptor? = null): JsonMeta = JsonMeta(this, descriptor) /**
* Convert a Json primitive to a [Value]
*/
public fun JsonPrimitive.toValue(descriptor: MetaDescriptor?): Value { public fun JsonPrimitive.toValue(descriptor: MetaDescriptor?): Value {
return when (this) { return when (this) {
JsonNull -> Null JsonNull -> Null
@ -68,62 +73,160 @@ public fun JsonPrimitive.toValue(descriptor: MetaDescriptor?): Value {
if (isString) { if (isString) {
StringValue(content) StringValue(content)
} else { } else {
//consider using LazyParse
content.parseValue() content.parseValue()
} }
} }
} }
} }
public fun JsonElement.toMeta(descriptor: MetaDescriptor? = null): TypedMeta<JsonMeta> = JsonMeta(this, descriptor)
/** /**
* A meta wrapping json object * Turn this [JsonArray] into a [ListValue] with recursion or return null if it contains objects
*/ */
public class JsonMeta( public fun JsonElement.toValueOrNull(descriptor: MetaDescriptor?): Value? = when (this) {
private val json: JsonElement, is JsonPrimitive -> toValue(descriptor)
private val descriptor: MetaDescriptor? = null is JsonObject -> get(Meta.VALUE_KEY)?.toValueOrNull(descriptor)
) : TypedMeta<JsonMeta> { is JsonArray -> {
if(isEmpty()) ListValue.EMPTY else {
private val indexName by lazy { descriptor?.indexKey ?: Meta.INDEX_KEY } val values = map { it.toValueOrNull(descriptor) }
values.map { it ?: return null }.asValue()
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
} }
} }
}
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()
}
}
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)
public companion object {
/**
* A key representing top-level json array of nodes, which could not be directly represented by a meta node
*/
public const val JSON_ARRAY_KEY: String = "@jsonArray"
}
} }
/**
* Fill a mutable map with children produced from [element] with given top level [key]
*/
private fun MutableMap<NameToken, SealedMeta>.addJsonElement(
key: String,
element: JsonElement,
descriptor: MetaDescriptor?
) {
when (element) {
is JsonPrimitive -> put(NameToken(key), Meta(element.toValue(descriptor)))
is JsonArray -> {
val value = element.toValueOrNull(descriptor)
if (value != null) {
put(NameToken(key), Meta(value))
} else {
val indexKey = descriptor?.indexKey ?: Meta.INDEX_KEY
element.forEachIndexed { serial, childElement ->
val index = (childElement as? JsonObject)?.get(indexKey)?.jsonPrimitive?.content
?: serial.toString()
val child: SealedMeta = when (childElement) {
is JsonObject -> childElement.toMeta(descriptor)
is JsonArray -> {
val childValue = childElement.toValueOrNull(null)
if (childValue == null) {
SealedMeta(null,
hashMapOf<NameToken, SealedMeta>().apply {
addJsonElement(Meta.JSON_ARRAY_KEY, childElement, null)
}
)
} else {
Meta(childValue)
}
}
is JsonPrimitive -> Meta(childElement.toValue(null))
}
put(NameToken(key, index), child)
}
}
}
is JsonObject -> {
val indexKey = descriptor?.indexKey ?: Meta.INDEX_KEY
val index = element[indexKey]?.jsonPrimitive?.content
put(NameToken(key, index), element.toMeta(descriptor))
}
}
}
public fun JsonObject.toMeta(descriptor: MetaDescriptor? = null): SealedMeta {
val map = LinkedHashMap<NameToken, SealedMeta>()
forEach { (key, element) ->
if (key != Meta.VALUE_KEY) {
map.addJsonElement(key, element, descriptor?.get(key))
}
}
return SealedMeta(get(Meta.VALUE_KEY)?.toValueOrNull(descriptor), map)
}
public fun JsonElement.toMeta(descriptor: MetaDescriptor? = null): SealedMeta = when (this) {
is JsonPrimitive -> Meta(toValue(descriptor))
is JsonObject -> toMeta(descriptor)
is JsonArray -> SealedMeta(null,
linkedMapOf<NameToken, SealedMeta>().apply {
addJsonElement(Meta.JSON_ARRAY_KEY, this@toMeta, null)
}
)
}
//
///**
// * A meta wrapping json object
// */
//public class JsonMeta(
// private val json: JsonElement,
// private val descriptor: MetaDescriptor? = null
//) : TypedMeta<JsonMeta> {
//
// private val indexName by lazy { descriptor?.indexKey ?: Meta.INDEX_KEY }
//
// override val value: Value? by lazy {
// json.toValueOrNull(descriptor)
// }
//
// private fun MutableMap<NameToken, JsonMeta>.appendArray(json: JsonArray, key: String) {
// json.forEachIndexed { index, child ->
// if (child is JsonArray) {
// appendArray(child, key)
// } else {
// //Use explicit index or order for index
// val tokenIndex = (child as? JsonObject)
// ?.get(indexName)
// ?.jsonPrimitive?.content
// ?: index.toString()
// val token = NameToken(key, tokenIndex)
// this[token] = JsonMeta(child)
// }
// }
// }
//
// override val items: Map<NameToken, JsonMeta> by lazy {
// val map = HashMap<NameToken, JsonMeta>()
// when (json) {
// is JsonObject -> json.forEach { (name, child) ->
// //skip value key
// if (name != Meta.VALUE_KEY) {
// if (child is JsonArray && child.any { it is JsonObject }) {
// map.appendArray(child, name)
// } else {
//
// val index = (child as? JsonObject)?.get(indexName)?.jsonPrimitive?.content
// val token = NameToken(name, index)
// map[token] = JsonMeta(child, descriptor?.get(name))
// }
// }
// }
// is JsonArray -> {
// //return children only if it is not value
// if (value == null) map.appendArray(json, JSON_ARRAY_KEY)
// }
// else -> {
// //do nothing
// }
// }
// map
// }
//
// 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)
//
// public companion object {
// /**
// * A key representing top-level json array of nodes, which could not be directly represented by a meta node
// */
// public const val JSON_ARRAY_KEY: String = "@jsonArray"
// }
//}

View File

@ -49,7 +49,13 @@ public interface Meta : MetaRepr {
} }
public fun equals(meta1: Meta?, meta2: Meta?): Boolean { public fun equals(meta1: Meta?, meta2: Meta?): Boolean {
return meta1?.value == meta2?.value && meta1?.items == meta2?.items if (meta1 == null && meta2 == null) return true
if (meta1 == null || meta2 == null) return false
if (meta1.value != meta2.value) return false
if (meta1.items.keys != meta2.items.keys) return false
return meta1.items.keys.all {
equals(meta1[it], meta2[it])
}
} }
private val json = Json { private val json = Json {

View File

@ -23,7 +23,7 @@ public fun Meta.item(key: Name? = null): MetaDelegate = ReadOnlyProperty { _, pr
public fun <R : Any> MetaDelegate.convert( public fun <R : Any> MetaDelegate.convert(
converter: MetaConverter<R>, converter: MetaConverter<R>,
): ReadOnlyProperty<Any?, R?> = ReadOnlyProperty { thisRef, property -> ): ReadOnlyProperty<Any?, R?> = ReadOnlyProperty { thisRef, property ->
this@convert.getValue(thisRef, property)?.let(converter::itemToObject) this@convert.getValue(thisRef, property)?.let(converter::metaToObject)
} }
/* /*
@ -33,7 +33,7 @@ public fun <R : Any> MetaDelegate.convert(
converter: MetaConverter<R>, converter: MetaConverter<R>,
default: () -> R, default: () -> R,
): ReadOnlyProperty<Any?, R> = ReadOnlyProperty<Any?, R> { thisRef, property -> ): ReadOnlyProperty<Any?, R> = ReadOnlyProperty<Any?, R> { thisRef, property ->
this@convert.getValue(thisRef, property)?.let(converter::itemToObject) ?: default() this@convert.getValue(thisRef, property)?.let(converter::metaToObject) ?: default()
} }
/** /**

View File

@ -1,30 +1,35 @@
package space.kscience.dataforge.meta package space.kscience.dataforge.meta
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.MapSerializer
import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.JsonDecoder
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import space.kscience.dataforge.names.NameToken import kotlinx.serialization.json.JsonEncoder
import space.kscience.dataforge.names.NameTokenSerializer
/** /**
* Serialized for meta * Serialized for [Meta]
*/ */
public object MetaSerializer : KSerializer<Meta> { public object MetaSerializer : KSerializer<Meta> {
private val genericMetaSerializer = SealedMeta.serializer()
private val itemsSerializer: KSerializer<Map<NameToken, Meta>> = MapSerializer( private val jsonSerializer = JsonElement.serializer()
NameTokenSerializer,
MetaSerializer
)
override val descriptor: SerialDescriptor = JsonElement.serializer().descriptor override val descriptor: SerialDescriptor = jsonSerializer.descriptor
override fun deserialize(decoder: Decoder): Meta = JsonElement.serializer().deserialize(decoder).toMeta() override fun deserialize(decoder: Decoder): Meta = if (decoder is JsonDecoder) {
jsonSerializer.deserialize(decoder).toMeta()
} else {
genericMetaSerializer.deserialize(decoder)
}
override fun serialize(encoder: Encoder, value: Meta) { override fun serialize(encoder: Encoder, value: Meta) {
JsonElement.serializer().serialize(encoder, value.toJson()) if (encoder is JsonEncoder) {
jsonSerializer.serialize(encoder, value.toJson())
} else {
genericMetaSerializer.serialize(encoder, value.seal())
}
} }
} }
@ -32,8 +37,8 @@ public object MetaSerializer : KSerializer<Meta> {
* A serializer for [MutableMeta] * A serializer for [MutableMeta]
*/ */
public object MutableMetaSerializer : KSerializer<MutableMeta> { public object MutableMetaSerializer : KSerializer<MutableMeta> {
override val descriptor: SerialDescriptor = MetaSerializer.descriptor
override val descriptor: SerialDescriptor = MetaSerializer.descriptor
override fun deserialize(decoder: Decoder): MutableMeta { override fun deserialize(decoder: Decoder): MutableMeta {
val meta = decoder.decodeSerializableValue(MetaSerializer) val meta = decoder.decodeSerializableValue(MetaSerializer)

View File

@ -1,20 +1,27 @@
package space.kscience.dataforge.meta package space.kscience.dataforge.meta
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import space.kscience.dataforge.misc.DFBuilder
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.* import space.kscience.dataforge.names.*
import space.kscience.dataforge.values.EnumValue import space.kscience.dataforge.values.EnumValue
import space.kscience.dataforge.values.Value import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.asValue import space.kscience.dataforge.values.asValue
import kotlin.js.JsName
import kotlin.jvm.Synchronized import kotlin.jvm.Synchronized
/**
* Mark a meta builder
*/
@DslMarker
public annotation class MetaBuilder
/** /**
* Mutable variant of [Meta] * Mutable variant of [Meta]
* TODO documentation * TODO documentation
*/ */
@Serializable(MutableMetaSerializer::class) @Serializable(MutableMetaSerializer::class)
@MetaBuilder
public interface MutableMeta : Meta { public interface MutableMeta : Meta {
override val items: Map<NameToken, MutableMeta> override val items: Map<NameToken, MutableMeta>
@ -30,9 +37,9 @@ public interface MutableMeta : Meta {
public operator fun set(name: Name, meta: Meta) public operator fun set(name: Name, meta: Meta)
/** /**
* Remove a note at a given [name] if it is present * Remove a node at a given [name] if it is present
*/ */
public fun removeNode(name: Name) public fun remove(name: Name)
/** /**
* Get existing node or create a new one * Get existing node or create a new one
@ -109,11 +116,17 @@ public interface MutableMeta : Meta {
toName() put repr.toMeta() toName() put repr.toMeta()
} }
public infix fun String.put(iterable: Iterable<Meta>) {
setIndexed(toName(), iterable)
}
public infix fun String.put(builder: MutableMeta.() -> Unit) { public infix fun String.put(builder: MutableMeta.() -> Unit) {
set(toName(), MutableMeta(builder)) set(toName(), MutableMeta(builder))
} }
} }
public fun MutableMeta.getOrCreate(string: String): MutableMeta = getOrCreate(string.toName())
@Serializable(MutableMetaSerializer::class) @Serializable(MutableMetaSerializer::class)
public interface MutableTypedMeta<M : MutableTypedMeta<M>> : TypedMeta<M>, MutableMeta { public interface MutableTypedMeta<M : MutableTypedMeta<M>> : TypedMeta<M>, MutableMeta {
/** /**
@ -125,34 +138,59 @@ public interface MutableTypedMeta<M : MutableTypedMeta<M>> : TypedMeta<M>, Mutab
override fun getOrCreate(name: Name): M override fun getOrCreate(name: Name): M
} }
public operator fun MutableMeta.set(key: String, item: Meta?): Unit = public fun <M : MutableTypedMeta<M>> M.getOrCreate(string: String): M = getOrCreate(string.toName())
set(key.toName(), item)
public fun MutableMeta.remove(name: String){
remove(name.toName())
}
public fun MutableMeta.remove(name: Name): Unit = set(name, null) // node setters
public operator fun MutableMeta.set(name: NameToken, value: Meta): Unit = set(name.asName(), value)
public operator fun MutableMeta.set(name: Name, meta: Meta?): Unit {
if (meta == null) {
remove(name)
} else {
set(name, meta)
}
}
public operator fun MutableMeta.set(key: String, meta: Meta?): Unit {
set(key.toName(), meta)
}
//value setters
public operator fun MutableMeta.set(name: NameToken, value: Value?): Unit = set(name.asName(), value)
public operator fun MutableMeta.set(key: String, value: Value?): Unit = set(key.toName(), value)
public operator fun MutableMeta.set(name: Name, value: String): Unit = set(name, value.asValue())
public operator fun MutableMeta.set(name: NameToken, value: String): Unit = set(name.asName(), value.asValue())
public operator fun MutableMeta.set(key: String, value: String): Unit = set(key.toName(), value.asValue())
public operator fun MutableMeta.set(name: Name, value: Boolean): Unit = set(name, value.asValue())
public operator fun MutableMeta.set(name: NameToken, value: Boolean): Unit = set(name.asName(), value.asValue())
public operator fun MutableMeta.set(key: String, value: Boolean): Unit = set(key.toName(), value.asValue())
public operator fun MutableMeta.set(name: Name, value: Number): Unit = set(name, value.asValue())
public operator fun MutableMeta.set(name: NameToken, value: Number): Unit = set(name.asName(), value.asValue())
public operator fun MutableMeta.set(key: String, value: Number): Unit = set(key.toName(), value.asValue())
public operator fun MutableMeta.set(name: Name, value: List<Value>): Unit = set(name, value.asValue())
public operator fun MutableMeta.set(name: NameToken, value: List<Value>): Unit = set(name.asName(), value.asValue())
public operator fun MutableMeta.set(key: String, value: List<Value>): Unit = set(key.toName(), value.asValue())
//public fun MutableMeta.set(key: String, index: String, value: Value?): Unit =
// set(key.toName().withIndex(index), value)
public fun MutableMeta.remove(name: String): Unit = remove(name.toName())
/** /**
* Universal unsafe set method * Universal unsafe set method
*/ */
public operator fun MutableMeta.set(name: Name, value: Any?) { public operator fun MutableMeta.set(name: Name, value: Value?) {
when (value) { getOrCreate(name).value = Value.of(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 */ /* Same name siblings generation */
public fun MutableMeta.setIndexedItems( public fun MutableMeta.setIndexedItems(
@ -177,10 +215,10 @@ public fun MutableMeta.setIndexed(
setIndexedItems(name, metas) { item, index -> indexFactory(item, index) } setIndexedItems(name, metas) { item, index -> indexFactory(item, index) }
} }
public operator fun <M : MutableTypedMeta<M>> MutableTypedMeta<M>.set(name: Name, metas: Iterable<Meta>): Unit = public operator fun MutableMeta.set(name: Name, metas: Iterable<Meta>): Unit =
setIndexed(name, metas) setIndexed(name, metas)
public operator fun <M : MutableTypedMeta<M>> MutableTypedMeta<M>.set(name: String, metas: Iterable<Meta>): Unit = public operator fun MutableMeta.set(name: String, metas: Iterable<Meta>): Unit =
setIndexed(name.toName(), metas) setIndexed(name.toName(), metas)
@ -190,7 +228,7 @@ public operator fun <M : MutableTypedMeta<M>> MutableTypedMeta<M>.set(name: Stri
* * node updates node and replaces anything but node * * 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 * * 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) { public fun MutableMeta.update(meta: Meta) {
meta.valueSequence().forEach { (name, value) -> meta.valueSequence().forEach { (name, value) ->
set(name, value) set(name, value)
} }
@ -217,38 +255,24 @@ public operator fun <M : MutableTypedMeta<M>> MutableTypedMeta<M>.set(name: Name
} }
} }
///**
// * 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]. * A general implementation of mutable [Meta] which implements both [MutableTypedMeta] and [ObservableMeta].
* The implementation uses blocking synchronization on mutation on JVM * The implementation uses blocking synchronization on mutation on JVM
*/ */
@DFBuilder
private class MutableMetaImpl( private class MutableMetaImpl(
override var value: Value?, value: Value?,
children: Map<NameToken, Meta> = emptyMap() children: Map<NameToken, Meta> = emptyMap()
) : ObservableMutableMeta { ) : ObservableMutableMeta {
private val children: LinkedHashMap<NameToken, MutableMetaImpl> = override var value = value
@Synchronized set
private val children: LinkedHashMap<NameToken, ObservableMutableMeta> =
LinkedHashMap(children.mapValues { (key, meta) -> LinkedHashMap(children.mapValues { (key, meta) ->
MutableMetaImpl(meta.value, meta.items).apply { adoptBy(this, key) } MutableMetaImpl(meta.value, meta.items).apply { adoptBy(this, key) }
}) })
override val items: Map<NameToken, MutableMetaImpl> get() = children override val items: Map<NameToken, ObservableMutableMeta> get() = children
private val listeners = HashSet<MetaListener>() private val listeners = HashSet<MetaListener>()
@ -267,60 +291,56 @@ private class MutableMetaImpl(
} }
private fun adoptBy(parent: MutableMetaImpl, key: NameToken) { private fun ObservableMeta.adoptBy(parent: MutableMetaImpl, key: NameToken) {
onChange(parent) { name -> onChange(parent) { name ->
parent.changed(key + name) parent.changed(key + name)
} }
} }
override fun attach(name: Name, node: ObservableMutableMeta) {
// fun attach(name: Name, node: MutableMetaImpl) { when (name.length) {
// when (name.length) { 0 -> error("Can't set a meta with empty name")
// 0 -> error("Can't set a meta with empty name") 1 -> {
// 1 -> { replaceItem(name.first(), get(name), node)
// val key = name.firstOrNull()!! }
// children[key] = node else -> get(name.cutLast())?.attach(name.lastOrNull()!!.asName(), node)
// adoptBy(this, key) }
// changed(name) }
// }
// else -> get(name.cutLast())?.attach(name.lastOrNull()!!.asName(), node)
// }
// }
/** /**
* Create and attach empty node * Create and attach empty node
*/ */
private fun createNode(name: Name): ObservableMutableMeta { private fun createNode(name: Name): ObservableMutableMeta = when (name.length) {
val newNode = MutableMetaImpl(null)
when (name.length) {
0 -> throw IllegalArgumentException("Can't create a node with empty name") 0 -> throw IllegalArgumentException("Can't create a node with empty name")
1 -> { 1 -> {
val newNode = MutableMetaImpl(null)
children[name.first()] = newNode children[name.first()] = newNode
newNode.adoptBy(this, name.first()) newNode.adoptBy(this, name.first())
newNode
} //do not notify, no value changed } //do not notify, no value changed
else -> getOrCreate(name.first().asName()).getOrCreate(name.cutFirst()) else -> getOrCreate(name.first().asName()).getOrCreate(name.cutFirst())
} }
return newNode
}
override fun getOrCreate(name: Name): ObservableMutableMeta = get(name) ?: createNode(name) override fun getOrCreate(name: Name): ObservableMutableMeta = get(name) ?: createNode(name)
override fun removeNode(name: Name) { override fun remove(name: Name) {
when (name.length) { when (name.length) {
0 -> error("Can't remove self") 0 -> error("Can't remove self")
1 -> if (children.remove(name.firstOrNull()!!) != null) changed(name) 1 -> if (children.remove(name.firstOrNull()!!) != null) changed(name)
else -> get(name.cutLast())?.removeNode(name.lastOrNull()!!.asName()) else -> get(name.cutLast())?.remove(name.lastOrNull()!!.asName())
} }
} }
@Synchronized
private fun replaceItem( private fun replaceItem(
key: NameToken, key: NameToken,
oldItem: ObservableMutableMeta?, oldItem: ObservableMutableMeta?,
newItem: MutableMetaImpl? newItem: ObservableMutableMeta?
) { ) {
if (oldItem != newItem) { if (oldItem != newItem) {
if (newItem == null) { if (newItem == null) {
children.remove(key) //remove child and remove stale listener
children.remove(key)?.removeListener(this)
} else { } else {
newItem.adoptBy(this, key) newItem.adoptBy(this, key)
children[key] = newItem children[key] = newItem
@ -364,7 +384,7 @@ private class MutableMetaImpl(
* Append the node with a same-name-sibling, automatically generating numerical index * Append the node with a same-name-sibling, automatically generating numerical index
*/ */
@DFExperimental @DFExperimental
public fun MutableMeta.append(name: Name, value: Any?) { public fun MutableMeta.append(name: Name, value: Value?) {
require(!name.isEmpty()) { "Name could not be empty for append operation" } require(!name.isEmpty()) { "Name could not be empty for append operation" }
val newIndex = name.lastOrNull()!!.index val newIndex = name.lastOrNull()!!.index
if (newIndex != null) { if (newIndex != null) {
@ -376,7 +396,7 @@ public fun MutableMeta.append(name: Name, value: Any?) {
} }
@DFExperimental @DFExperimental
public fun MutableMeta.append(name: String, value: Any?): Unit = append(name.toName(), value) public fun MutableMeta.append(name: String, value: Value?): Unit = append(name.toName(), value)
///** ///**
// * Apply existing node with given [builder] or create a new element with it. // * Apply existing node with given [builder] or create a new element with it.
@ -398,12 +418,15 @@ public fun Meta.toMutableMeta(): ObservableMutableMeta = MutableMetaImpl(value,
public fun Meta.asMutableMeta(): MutableMeta = (this as? MutableMeta) ?: toMutableMeta() public fun Meta.asMutableMeta(): MutableMeta = (this as? MutableMeta) ?: toMutableMeta()
@JsName("newMutableMeta")
public fun MutableMeta(): ObservableMutableMeta = MutableMetaImpl(null)
/** /**
* Build a [MutableMeta] using given transformation * Build a [MutableMeta] using given transformation
*/ */
@Suppress("FunctionName") @Suppress("FunctionName")
public fun MutableMeta(builder: MutableMeta.() -> Unit = {}): ObservableMutableMeta = public inline fun MutableMeta(builder: MutableMeta.() -> Unit = {}): ObservableMutableMeta =
MutableMetaImpl(null).apply(builder) MutableMeta().apply(builder)
/** /**
@ -412,3 +435,31 @@ public fun MutableMeta(builder: MutableMeta.() -> Unit = {}): ObservableMutableM
*/ */
public inline fun Meta.copy(block: MutableMeta.() -> Unit = {}): Meta = public inline fun Meta.copy(block: MutableMeta.() -> Unit = {}): Meta =
toMutableMeta().apply(block) toMutableMeta().apply(block)
private class MutableMetaWithDefault(val source: MutableMeta, val default: Meta, val name: Name) :
MutableMeta by source {
override val items: Map<NameToken, MutableMeta>
get() = (source.items.keys + default.items.keys).associateWith {
MutableMetaWithDefault(source, default, name + it)
}
override var value: Value?
get() = source[name]?.value ?: default[name]?.value
set(value) {
source[name] = value
}
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)
}
/**
* 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 MutableMeta.withDefault(default: Meta?): MutableMeta = if (default == null || default.isEmpty()) {
//Optimize for use with empty default
this
} else MutableMetaWithDefault(this, default, Name.EMPTY)

View File

@ -32,10 +32,10 @@ public fun <R : Any> MutableMetaDelegate.convert(
): ReadWriteProperty<Any?, R?> = object : ReadWriteProperty<Any?, R?> { ): ReadWriteProperty<Any?, R?> = object : ReadWriteProperty<Any?, R?> {
override fun getValue(thisRef: Any?, property: KProperty<*>): R? = override fun getValue(thisRef: Any?, property: KProperty<*>): R? =
this@convert.getValue(thisRef, property)?.let(converter::itemToObject) this@convert.getValue(thisRef, property)?.let(converter::metaToObject)
override fun setValue(thisRef: Any?, property: KProperty<*>, value: R?) { override fun setValue(thisRef: Any?, property: KProperty<*>, value: R?) {
val item = value?.let(converter::objectToMetaItem) val item = value?.let(converter::objectToMeta)
this@convert.setValue(thisRef, property, item) this@convert.setValue(thisRef, property, item)
} }
} }
@ -46,10 +46,10 @@ public fun <R : Any> MutableMetaDelegate.convert(
): ReadWriteProperty<Any?, R> = object : ReadWriteProperty<Any?, R> { ): ReadWriteProperty<Any?, R> = object : ReadWriteProperty<Any?, R> {
override fun getValue(thisRef: Any?, property: KProperty<*>): R = override fun getValue(thisRef: Any?, property: KProperty<*>): R =
this@convert.getValue(thisRef, property)?.let(converter::itemToObject) ?: default() this@convert.getValue(thisRef, property)?.let(converter::metaToObject) ?: default()
override fun setValue(thisRef: Any?, property: KProperty<*>, value: R) { override fun setValue(thisRef: Any?, property: KProperty<*>, value: R) {
val item = value.let(converter::objectToMetaItem) val item = value.let(converter::objectToMeta)
this@convert.setValue(thisRef, property, item) this@convert.setValue(thisRef, property, item)
} }
} }

View File

@ -32,7 +32,7 @@ public interface ObservableMeta : Meta {
/** /**
* A [Meta] which is both observable and mutable * A [Meta] which is both observable and mutable
*/ */
public interface ObservableMutableMeta : ObservableMeta, MutableMeta, TypedMeta<ObservableMutableMeta> public interface ObservableMutableMeta : ObservableMeta, MutableMeta, MutableTypedMeta<ObservableMutableMeta>
private class ObservableMetaWrapper( private class ObservableMetaWrapper(
val origin: MutableMeta, val origin: MutableMeta,
@ -68,8 +68,8 @@ private class ObservableMetaWrapper(
get(name) ?: ObservableMetaWrapper(origin.getOrCreate(name)) get(name) ?: ObservableMetaWrapper(origin.getOrCreate(name))
override fun removeNode(name: Name) { override fun remove(name: Name) {
origin.removeNode(name) origin.remove(name)
changed(name) changed(name)
} }
@ -84,10 +84,18 @@ private class ObservableMetaWrapper(
override fun toMeta(): Meta { override fun toMeta(): Meta {
return origin.toMeta() return origin.toMeta()
} }
override fun attach(name: Name, node: ObservableMutableMeta) {
TODO("Not yet implemented")
}
} }
public fun MutableMeta.asObservable(): ObservableMeta = /**
(this as? ObservableMeta) ?: ObservableMetaWrapper(this) * Cast this [MutableMeta] to [ObservableMutableMeta] or create an observable wrapper. Only changes made to the result
* are guaranteed to be observed.
*/
public fun MutableMeta.asObservable(): ObservableMutableMeta =
(this as? ObservableMutableMeta) ?: ObservableMetaWrapper(this)
/** /**

View File

@ -2,39 +2,43 @@ package space.kscience.dataforge.meta
import space.kscience.dataforge.meta.descriptors.* import space.kscience.dataforge.meta.descriptors.*
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.values.Value
/** /**
* A base for delegate-based or descriptor-based scheme. [Scheme] has an empty constructor to simplify usage from [Specification]. * A base for delegate-based or descriptor-based scheme. [Scheme] has an empty constructor to simplify usage from [Specification].
* Default item provider and [NodeDescriptor] are optional * Default item provider and [NodeDescriptor] are optional
*/ */
public open class Scheme internal constructor( public open class Scheme(
source: MutableMeta = MutableMeta() source: MutableMeta = MutableMeta()
) : Described, ObservableMutableMeta, Meta by source { ) : Described, MutableMeta, ObservableMeta, Meta by source {
private var source = source.asObservable() private var source = source.asObservable()
final override var descriptor: MetaDescriptor? = null final override var descriptor: MetaDescriptor? = null
internal set internal set
override var value: Value?
get() = source.value
set(value) {
source.value = value
}
override val items: Map<NameToken, MutableMeta> get() = source.items
internal fun wrap( internal fun wrap(
items: MutableMeta, items: MutableMeta,
preserveDefault: Boolean = false preserveDefault: Boolean = false
) { ) {
this.source = if (preserveDefault) items.withDefault(this.source) else items this.source = (if (preserveDefault) items.withDefault(this.source) else items).asObservable()
} }
//
// /**
// * 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] * Check if property with given [name] could be assigned to [meta]
*/ */
public open fun validate(name: Name, item: Meta?): Boolean { public open fun validate(name: Name, meta: Meta?): Boolean {
val descriptor = descriptor?.get(name) val descriptor = descriptor?.get(name)
return descriptor?.validateItem(item) ?: true return descriptor?.validate(meta) ?: true
} }
/** /**
@ -51,11 +55,25 @@ public open class Scheme internal constructor(
} }
} }
override fun toMeta(): Laminate = Laminate(source, descriptor?.defaultMeta) override fun toMeta(): Laminate = Laminate(source, descriptor?.defaultNode)
override fun remove(name: Name) {
source.remove(name)
}
override fun getOrCreate(name: Name): MutableMeta = source.getOrCreate(name)
override fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit) {
source.onChange(owner ?: this, callback)
}
override fun removeListener(owner: Any?) {
source.removeListener(owner ?: this)
}
} }
/** /**
* Relocate scheme target onto given [MutableTypedMeta]. Old provider does not get updates anymore. * Relocate scheme target onto given [MutableMeta]. Old provider does not get updates anymore.
* Current state of the scheme used as a default. * Current state of the scheme used as a default.
*/ */
public fun <T : Scheme> T.retarget(provider: MutableMeta): T = apply { public fun <T : Scheme> T.retarget(provider: MutableMeta): T = apply {
@ -74,8 +92,8 @@ public open class SchemeSpec<out T : Scheme>(
private val builder: () -> T, private val builder: () -> T,
) : Specification<T>, Described { ) : Specification<T>, Described {
override fun read(items: Meta): T = empty().also { override fun read(source: Meta): T = empty().also {
it.wrap(MutableMeta().withDefault(items)) it.wrap(MutableMeta().withDefault(source))
} }
override fun write(target: MutableMeta): T = empty().also { override fun write(target: MutableMeta): T = empty().also {

View File

@ -1,5 +1,6 @@
package space.kscience.dataforge.meta package space.kscience.dataforge.meta
import kotlinx.serialization.Serializable
import space.kscience.dataforge.names.NameToken import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.values.Value import space.kscience.dataforge.values.Value
@ -8,6 +9,7 @@ import space.kscience.dataforge.values.Value
* *
* If the argument is possibly mutable node, it is copied on creation * If the argument is possibly mutable node, it is copied on creation
*/ */
@Serializable
public class SealedMeta internal constructor( public class SealedMeta internal constructor(
override val value: Value?, override val value: Value?,
override val items: Map<NameToken, SealedMeta> override val items: Map<NameToken, SealedMeta>
@ -28,6 +30,6 @@ public fun Meta.seal(): SealedMeta = this as? SealedMeta ?: SealedMeta(value, it
public fun Meta(value: Value): SealedMeta = SealedMeta(value, emptyMap()) public fun Meta(value: Value): SealedMeta = SealedMeta(value, emptyMap())
@Suppress("FunctionName") @Suppress("FunctionName")
public fun Meta(builder: MutableMeta.() -> Unit): SealedMeta = public inline fun Meta(builder: MutableMeta.() -> Unit): SealedMeta =
MutableMeta(builder).seal() MutableMeta(builder).seal()

View File

@ -33,15 +33,15 @@ public interface ReadOnlySpecification<out T : Any> {
*/ */
public interface Specification<out T : Any> : ReadOnlySpecification<T> { public interface Specification<out T : Any> : ReadOnlySpecification<T> {
/** /**
* Wrap [MutableTypedMeta], using it as inner storage (changes to [Specification] are reflected on [MutableTypedMeta] * Wrap [MutableMeta], using it as inner storage (changes to [Specification] are reflected on [MutableMeta]
*/ */
public fun write(target: MutableMeta): T public fun write(target: MutableMeta): T
} }
/** /**
* Update a [MutableTypedMeta] using given specification * Update a [MutableMeta] using given specification
*/ */
public fun <M : MutableTypedMeta<M>, T : Any> M.update( public fun <T : Any> MutableMeta.update(
spec: Specification<T>, spec: Specification<T>,
action: T.() -> Unit action: T.() -> Unit
): T = spec.write(this).apply(action) ): T = spec.write(this).apply(action)

View File

@ -5,20 +5,4 @@ package space.kscience.dataforge.meta.descriptors
*/ */
public interface Described { public interface Described {
public val descriptor: MetaDescriptor? public val descriptor: MetaDescriptor?
public companion object {
//public const val DESCRIPTOR_NODE: String = "@descriptor"
}
} }
///**
// * If meta node supplies explicit descriptor, return it, otherwise try to use descriptor node from meta itself
// */
//val MetaRepr.descriptor: NodeDescriptor?
// get() {
// return if (this is Described) {
// descriptor
// } else {
// toMeta()[DESCRIPTOR_NODE].node?.let { NodeDescriptor.wrap(it) }
// }
// }

View File

@ -2,6 +2,8 @@ package space.kscience.dataforge.meta.descriptors
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.set
import space.kscience.dataforge.names.* import space.kscience.dataforge.names.*
import space.kscience.dataforge.values.Value import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.ValueType import space.kscience.dataforge.values.ValueType
@ -12,7 +14,7 @@ import space.kscience.dataforge.values.ValueType
* @param children child descriptors for this node * @param children child descriptors for this node
* @param multiple True if same name siblings with this name are allowed * @param multiple True if same name siblings with this name are allowed
* @param required True if the item is required * @param required True if the item is required
* @param type list of allowed types for [Meta.value], null if all values are allowed * @param type list of allowed types for [Meta.value], null if all values are allowed. If the type is [ValueType.NULL], no value is allowed for this node.
* @param indexKey An index field by which this node is identified in case of same name siblings construct * @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 defaultValue the default [Meta.value] for the node
* @param attributes additional attributes of this descriptor. For example validation and widget parameters * @param attributes additional attributes of this descriptor. For example validation and widget parameters
@ -27,7 +29,13 @@ public data class MetaDescriptor(
public val indexKey: String = Meta.INDEX_KEY, public val indexKey: String = Meta.INDEX_KEY,
public val defaultValue: Value? = null, public val defaultValue: Value? = null,
public val attributes: Meta = Meta.EMPTY, public val attributes: Meta = Meta.EMPTY,
) ) {
public companion object {
internal const val ALLOWED_VALUES_KEY = "allowedValues"
}
}
public val MetaDescriptor.allowedValues: List<Value>? get() = attributes[MetaDescriptor.ALLOWED_VALUES_KEY]?.value?.list
public operator fun MetaDescriptor.get(name: Name): MetaDescriptor? = when (name.length) { public operator fun MetaDescriptor.get(name: Name): MetaDescriptor? = when (name.length) {
0 -> this 0 -> this
@ -37,24 +45,22 @@ public operator fun MetaDescriptor.get(name: Name): MetaDescriptor? = when (name
public operator fun MetaDescriptor.get(name: String): MetaDescriptor? = get(name.toName()) public operator fun MetaDescriptor.get(name: String): MetaDescriptor? = get(name.toName())
public class MetaDescriptorBuilder { /**
public var info: String? = null * A node constructed of default values for this descriptor and its children
public var children: MutableMap<String, MetaDescriptor> = hashMapOf() */
public var multiple: Boolean = false public val MetaDescriptor.defaultNode: Meta
public var required: Boolean = false get() = Meta {
public var type: List<ValueType>? = null defaultValue?.let { defaultValue ->
public var indexKey: String = Meta.INDEX_KEY this.value = defaultValue
public var default: Value? = null }
public var attributes: Meta = Meta.EMPTY children.forEach { (key, descriptor) ->
set(key, descriptor.defaultNode)
}
}
internal fun build(): MetaDescriptor = MetaDescriptor( /**
info = info, * Check if given item suits the descriptor
children = children, */
multiple = multiple, public fun MetaDescriptor.validate(item: Meta?): Boolean = (item != null || !required) &&
required = required, allowedValues?.let { item?.value in it } ?: true &&
type = type, children.all { (key, childDescriptor) -> childDescriptor.validate(item?.get(key)) }
indexKey = indexKey,
defaultValue = default,
attributes = attributes
)
}

View File

@ -0,0 +1,124 @@
package space.kscience.dataforge.meta.descriptors
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.set
import space.kscience.dataforge.names.*
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.ValueType
import space.kscience.dataforge.values.asValue
public class MetaDescriptorBuilder {
public var info: String? = null
public var children: MutableMap<String, MetaDescriptorBuilder> = hashMapOf()
public var multiple: Boolean = false
public var required: Boolean = false
public var type: List<ValueType>? = null
public fun type(primaryType: ValueType, vararg otherTypes: ValueType) {
type = listOf(primaryType, *otherTypes)
}
public var indexKey: String = Meta.INDEX_KEY
public var default: Value? = null
public fun default(value: Any?) {
default = Value.of(value)
}
public var attributes: MutableMeta = MutableMeta()
public fun item(name: Name, block: MetaDescriptorBuilder.() -> Unit) {
when (name.length) {
0 -> apply(block)
1 -> {
val target = MetaDescriptorBuilder().apply(block)
children[name.first().body] = target
}
else -> {
children.getOrPut(name.first().body) { MetaDescriptorBuilder() }.item(name.cutFirst(), block)
}
}
}
public var allowedValues: List<Value>
get() = attributes[MetaDescriptor.ALLOWED_VALUES_KEY]?.value?.list ?: emptyList()
set(value) {
attributes[MetaDescriptor.ALLOWED_VALUES_KEY] = value
}
public fun allowedValues(vararg values: Any) {
allowedValues = values.map { Value.of(it) }
}
internal fun build(): MetaDescriptor = MetaDescriptor(
info = info,
children = children.mapValues { it.value.build() },
multiple = multiple,
required = required,
type = type,
indexKey = indexKey,
defaultValue = default,
attributes = attributes
)
}
public fun MetaDescriptorBuilder.item(name: String, block: MetaDescriptorBuilder.() -> Unit) {
item(name.toName(), block)
}
public fun MetaDescriptor(block: MetaDescriptorBuilder.() -> Unit): MetaDescriptor =
MetaDescriptorBuilder().apply(block).build()
/**
* Create and configure child value descriptor
*/
public fun MetaDescriptorBuilder.value(
name: Name,
type: ValueType,
vararg additionalTypes: ValueType,
block: MetaDescriptorBuilder.() -> Unit
) {
item(name) {
type(type, *additionalTypes)
block()
}
}
public fun MetaDescriptorBuilder.value(
name: String,
type: ValueType,
vararg additionalTypes: ValueType,
block: MetaDescriptorBuilder.() -> Unit
) {
value(name.toName(), type, additionalTypes = additionalTypes, block)
}
/**
* Create and configure child value descriptor
*/
public fun MetaDescriptorBuilder.node(name: Name, block: MetaDescriptorBuilder.() -> Unit) {
item(name) {
type(ValueType.NULL)
block()
}
}
public fun MetaDescriptorBuilder.node(name: String, block: MetaDescriptorBuilder.() -> Unit) {
node(name.toName(), block)
}
public inline fun <reified E : Enum<E>> MetaDescriptorBuilder.enum(
key: Name,
default: E?,
crossinline modifier: MetaDescriptorBuilder.() -> Unit = {},
): Unit = value(key,ValueType.STRING) {
default?.let {
this.default = default.asValue()
}
allowedValues = enumValues<E>().map { it.asValue() }
modifier()
}

View File

@ -1,18 +0,0 @@
package space.kscience.dataforge.meta.descriptors
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>> MetaDescriptorBuilder.enum(
key: Name,
default: E?,
crossinline modifier: MetaDescriptor.() -> Unit = {},
): Unit = value(key) {
type(ValueType.STRING)
default?.let {
default(default)
}
allowedValues = enumValues<E>().map { it.asValue() }
modifier()
}

View File

@ -1,124 +1,89 @@
package space.kscience.dataforge.meta.transformations package space.kscience.dataforge.meta.transformations
import space.kscience.dataforge.meta.* import space.kscience.dataforge.meta.*
import space.kscience.dataforge.values.* import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.asValue
/** /**
* A converter of generic object to and from [TypedMetaItem] * A converter of generic object to and from [Meta]
*/ */
public interface MetaConverter<T> { public interface MetaConverter<T> {
public fun itemToObject(item: MetaItem): T public fun metaToObject(meta: Meta): T?
public fun objectToMetaItem(obj: T): MetaItem public fun objectToMeta(obj: T): Meta
public companion object { public companion object {
public val item: MetaConverter<MetaItem> = object : MetaConverter<MetaItem> {
override fun itemToObject(item: MetaItem): MetaItem = item
override fun objectToMetaItem(obj: MetaItem): MetaItem = obj
}
public val meta: MetaConverter<Meta> = object : MetaConverter<Meta> { public val meta: MetaConverter<Meta> = object : MetaConverter<Meta> {
override fun itemToObject(item: MetaItem): Meta = when (item) { override fun metaToObject(meta: Meta): Meta = meta
is MetaItemNode -> item.node override fun objectToMeta(obj: Meta): Meta = obj
is MetaItemValue -> item.value.toMeta()
}
override fun objectToMetaItem(obj: Meta): MetaItem = MetaItemNode(obj)
} }
public val value: MetaConverter<Value> = object : MetaConverter<Value> { public val value: MetaConverter<Value> = object : MetaConverter<Value> {
override fun itemToObject(item: MetaItem): Value = when (item) { override fun metaToObject(meta: Meta): Value? = meta.value
is MetaItemNode -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value") override fun objectToMeta(obj: Value): Meta = Meta(obj)
is MetaItemValue -> item.value
}
override fun objectToMetaItem(obj: Value): MetaItem = MetaItemValue(obj)
} }
public val string: MetaConverter<String> = object : MetaConverter<String> { public val string: MetaConverter<String> = object : MetaConverter<String> {
override fun itemToObject(item: MetaItem): String = when (item) { override fun metaToObject(meta: Meta): String? = meta.string
is MetaItemNode -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value") override fun objectToMeta(obj: String): Meta = Meta(obj.asValue())
is MetaItemValue -> item.value
}.string
override fun objectToMetaItem(obj: String): MetaItem = MetaItemValue(obj.asValue())
} }
public val boolean: MetaConverter<Boolean> = object : MetaConverter<Boolean> { public val boolean: MetaConverter<Boolean> = object : MetaConverter<Boolean> {
override fun itemToObject(item: MetaItem): Boolean = when (item) { override fun metaToObject(meta: Meta): Boolean? = meta.boolean
is MetaItemNode -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value") override fun objectToMeta(obj: Boolean): Meta = Meta(obj.asValue())
is MetaItemValue -> item.value
}.boolean
override fun objectToMetaItem(obj: Boolean): MetaItem = MetaItemValue(obj.asValue())
} }
public val number: MetaConverter<Number> = object : MetaConverter<Number> { public val number: MetaConverter<Number> = object : MetaConverter<Number> {
override fun itemToObject(item: MetaItem): Number = when (item) { override fun metaToObject(meta: Meta): Number? = meta.number
is MetaItemNode -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value") override fun objectToMeta(obj: Number): Meta = Meta(obj.asValue())
is MetaItemValue -> item.value
}.number
override fun objectToMetaItem(obj: Number): MetaItem = MetaItemValue(obj.asValue())
} }
public val double: MetaConverter<Double> = object : MetaConverter<Double> { public val double: MetaConverter<Double> = object : MetaConverter<Double> {
override fun itemToObject(item: MetaItem): Double = when (item) { override fun metaToObject(meta: Meta): Double? = meta.double
is MetaItemNode -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value")
is MetaItemValue -> item.value
}.double
override fun objectToMetaItem(obj: Double): MetaItem = MetaItemValue(obj.asValue()) override fun objectToMeta(obj: Double): Meta = Meta(obj.asValue())
} }
public val float: MetaConverter<Float> = object : MetaConverter<Float> { public val float: MetaConverter<Float> = object : MetaConverter<Float> {
override fun itemToObject(item: MetaItem): Float = when (item) { override fun metaToObject(meta: Meta): Float? = meta.float
is MetaItemNode -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value")
is MetaItemValue -> item.value
}.float
override fun objectToMetaItem(obj: Float): MetaItem = MetaItemValue(obj.asValue()) override fun objectToMeta(obj: Float): Meta = Meta(obj.asValue())
} }
public val int: MetaConverter<Int> = object : MetaConverter<Int> { public val int: MetaConverter<Int> = object : MetaConverter<Int> {
override fun itemToObject(item: MetaItem): Int = when (item) { override fun metaToObject(meta: Meta): Int? = meta.int
is MetaItemNode -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value")
is MetaItemValue -> item.value
}.int
override fun objectToMetaItem(obj: Int): MetaItem = MetaItemValue(obj.asValue()) override fun objectToMeta(obj: Int): Meta = Meta(obj.asValue())
} }
public val long: MetaConverter<Long> = object : MetaConverter<Long> { public val long: MetaConverter<Long> = object : MetaConverter<Long> {
override fun itemToObject(item: MetaItem): Long = when (item) { override fun metaToObject(meta: Meta): Long? = meta.long
is MetaItemNode -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value")
is MetaItemValue -> item.value
}.long
override fun objectToMetaItem(obj: Long): MetaItem = MetaItemValue(obj.asValue()) override fun objectToMeta(obj: Long): Meta = Meta(obj.asValue())
} }
public inline fun <reified E : Enum<E>> enum(): MetaConverter<E> = object : MetaConverter<E> { public inline fun <reified E : Enum<E>> enum(): MetaConverter<E> = object : MetaConverter<E> {
@Suppress("USELESS_CAST") @Suppress("USELESS_CAST")
override fun itemToObject(item: MetaItem): E = item.enum<E>() as? E ?: error("The Item is not a Enum") override fun metaToObject(meta: Meta): E = meta.enum<E>() as? E ?: error("The Item is not a Enum")
override fun objectToMetaItem(obj: E): MetaItem = MetaItemValue(obj.asValue()) override fun objectToMeta(obj: E): Meta = Meta(obj.asValue())
} }
public fun <T> valueList(writer: (T) -> Value = { Value.of(it)}, reader: (Value) -> T): MetaConverter<List<T>> = public fun <T> valueList(
writer: (T) -> Value = { Value.of(it) },
reader: (Value) -> T
): MetaConverter<List<T>> =
object : MetaConverter<List<T>> { object : MetaConverter<List<T>> {
override fun itemToObject(item: MetaItem): List<T> = override fun metaToObject(meta: Meta): List<T> =
item.value?.list?.map(reader) ?: error("The item is not a value list") meta.value?.list?.map(reader) ?: error("The item is not a value list")
override fun objectToMetaItem(obj: List<T>): MetaItem = override fun objectToMeta(obj: List<T>): Meta = Meta(obj.map(writer).asValue())
MetaItemValue(obj.map(writer).asValue())
} }
} }
} }
public fun <T : Any> MetaConverter<T>.nullableItemToObject(item: MetaItem?): T? = item?.let { itemToObject(it) } public fun <T : Any> MetaConverter<T>.nullableMetaToObject(item: Meta?): T? = item?.let { metaToObject(it) }
public fun <T : Any> MetaConverter<T>.nullableObjectToMetaItem(obj: T?): MetaItem? = obj?.let { objectToMetaItem(it) } public fun <T : Any> MetaConverter<T>.nullableObjectToMeta(obj: T?): Meta? = obj?.let { objectToMeta(it) }
public fun <T> MetaConverter<T>.metaToObject(meta: Meta): T = itemToObject(MetaItemNode(meta)) public fun <T> MetaConverter<T>.valueToObject(value: Value): T? = metaToObject(Meta(value))
public fun <T> MetaConverter<T>.valueToObject(value: Value): T = itemToObject(MetaItemValue(value))

View File

@ -13,7 +13,7 @@ public interface TransformationRule {
/** /**
* Check if this transformation should be applied to a node with given name and value * Check if this transformation should be applied to a node with given name and value
*/ */
public fun matches(name: Name, item: MetaItem?): Boolean public fun matches(name: Name, item: Meta?): Boolean
/** /**
* Select all items to be transformed. Item could be a value as well as node * Select all items to be transformed. Item could be a value as well as node
@ -26,7 +26,7 @@ public interface TransformationRule {
/** /**
* Apply transformation for a single item (Node or Value) to the target * Apply transformation for a single item (Node or Value) to the target
*/ */
public fun transformItem(name: Name, item: MetaItem?, target: MutableTypedMeta): Unit public fun transformItem(name: Name, item: Meta?, target: MutableMeta): Unit
} }
/** /**
@ -34,14 +34,14 @@ public interface TransformationRule {
*/ */
public data class KeepTransformationRule(val selector: (Name) -> Boolean) : public data class KeepTransformationRule(val selector: (Name) -> Boolean) :
TransformationRule { TransformationRule {
override fun matches(name: Name, item: MetaItem?): Boolean { override fun matches(name: Name, item: Meta?): Boolean {
return selector(name) return selector(name)
} }
override fun selectItems(meta: Meta): Sequence<Name> = override fun selectItems(meta: Meta): Sequence<Name> =
meta.nodeSequence().map { it.first }.filter(selector) meta.nodeSequence().map { it.first }.filter(selector)
override fun transformItem(name: Name, item: MetaItem?, target: MutableTypedMeta) { override fun transformItem(name: Name, item: Meta?, target: MutableMeta) {
if (selector(name)) target.set(name, item) if (selector(name)) target.set(name, item)
} }
} }
@ -51,15 +51,15 @@ public data class KeepTransformationRule(val selector: (Name) -> Boolean) :
*/ */
public data class SingleItemTransformationRule( public data class SingleItemTransformationRule(
val from: Name, val from: Name,
val transform: MutableTypedMeta.(Name, MetaItem?) -> Unit, val transform: MutableMeta.(Name, Meta?) -> Unit,
) : TransformationRule { ) : TransformationRule {
override fun matches(name: Name, item: MetaItem?): Boolean { override fun matches(name: Name, item: Meta?): Boolean {
return name == from return name == from
} }
override fun selectItems(meta: Meta): Sequence<Name> = sequenceOf(from) override fun selectItems(meta: Meta): Sequence<Name> = sequenceOf(from)
override fun transformItem(name: Name, item: MetaItem?, target: MutableTypedMeta) { override fun transformItem(name: Name, item: Meta?, target: MutableMeta) {
if (name == this.from) { if (name == this.from) {
target.transform(name, item) target.transform(name, item)
} }
@ -68,13 +68,13 @@ public data class SingleItemTransformationRule(
public data class RegexItemTransformationRule( public data class RegexItemTransformationRule(
val from: Regex, val from: Regex,
val transform: MutableTypedMeta.(name: Name, MatchResult, MetaItem?) -> Unit, val transform: MutableMeta.(name: Name, MatchResult, Meta?) -> Unit,
) : TransformationRule { ) : TransformationRule {
override fun matches(name: Name, item: MetaItem?): Boolean { override fun matches(name: Name, item: Meta?): Boolean {
return from.matches(name.toString()) return from.matches(name.toString())
} }
override fun transformItem(name: Name, item: MetaItem?, target: MutableTypedMeta) { override fun transformItem(name: Name, item: Meta?, target: MutableMeta) {
val match = from.matchEntire(name.toString()) val match = from.matchEntire(name.toString())
if (match != null) { if (match != null) {
target.transform(name, match, item) target.transform(name, match, item)
@ -105,7 +105,7 @@ public value class MetaTransformation(private val transformations: Collection<Tr
* Generate an observable configuration that contains only elements defined by transformation rules and changes with the source * Generate an observable configuration that contains only elements defined by transformation rules and changes with the source
*/ */
@DFExperimental @DFExperimental
public fun generate(source: MutableMeta): ObservableMeta = MutableMeta().apply { public fun generate(source: ObservableMeta): ObservableMeta = MutableMeta().apply {
transformations.forEach { rule -> transformations.forEach { rule ->
rule.selectItems(source).forEach { name -> rule.selectItems(source).forEach { name ->
rule.transformItem(name, source[name], this) rule.transformItem(name, source[name], this)
@ -131,8 +131,9 @@ public value class MetaTransformation(private val transformations: Collection<Tr
/** /**
* Listens for changes in the source node and translates them into second node if transformation set contains a corresponding rule. * Listens for changes in the source node and translates them into second node if transformation set contains a corresponding rule.
*/ */
public fun bind(source: ObservableMeta, target: MutableTypedMeta) { public fun bind(source: ObservableMeta, target: MutableMeta) {
source.onChange(target) { name, _, newItem -> source.onChange(target) { name ->
val newItem = source[name]
transformations.forEach { t -> transformations.forEach { t ->
if (t.matches(name, newItem)) { if (t.matches(name, newItem)) {
t.transformItem(name, newItem, target) t.transformItem(name, newItem, target)
@ -172,15 +173,15 @@ public class MetaTransformationBuilder {
*/ */
public fun keep(regex: String) { public fun keep(regex: String) {
transformations.add( transformations.add(
RegexItemTransformationRule(regex.toRegex()) { name, _, metaItem -> RegexItemTransformationRule(regex.toRegex()) { name, _, Meta ->
set(name, metaItem) set(name, Meta)
}) })
} }
/** /**
* Move an item from [from] to [to], optionally applying [operation] it defined * Move an item from [from] to [to], optionally applying [operation] it defined
*/ */
public fun move(from: Name, to: Name, operation: (MetaItem?) -> Any? = { it }) { public fun move(from: Name, to: Name, operation: (Meta?) -> Meta? = { it }) {
transformations.add( transformations.add(
SingleItemTransformationRule(from) { _, item -> SingleItemTransformationRule(from) { _, item ->
set(to, operation(item)) set(to, operation(item))

View File

@ -1,6 +1,7 @@
package space.kscience.dataforge.values package space.kscience.dataforge.values
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlin.jvm.JvmInline
/** /**
@ -153,16 +154,10 @@ public class NumberValue(public val number: Number) : Value {
override fun hashCode(): Int = numberOrNull.hashCode() override fun hashCode(): Int = numberOrNull.hashCode()
} }
public class StringValue(public val string: String) : Value { @JvmInline
public value class StringValue(public val string: String) : Value {
override val value: Any get() = string override val value: Any get() = string
override val type: ValueType get() = ValueType.STRING override val type: ValueType get() = ValueType.STRING
override fun equals(other: Any?): Boolean {
return this.string == (other as? Value)?.string
}
override fun hashCode(): Int = string.hashCode()
override fun toString(): String = string override fun toString(): String = string
} }
@ -204,6 +199,9 @@ public class ListValue(override val list: List<Value>) : Value, Iterable<Value>
} }
} }
public fun ListValue(vararg numbers: Number): ListValue = ListValue(numbers.map{it.asValue()})
public fun ListValue(vararg strings: String): ListValue = ListValue(strings.map{it.asValue()})
public fun Number.asValue(): Value = NumberValue(this) public fun Number.asValue(): Value = NumberValue(this)
public fun Boolean.asValue(): Value = if (this) True else False public fun Boolean.asValue(): Value = if (this) True else False

View File

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

View File

@ -1,7 +1,8 @@
package space.kscience.dataforge.meta package space.kscience.dataforge.meta
import kotlinx.serialization.json.* import kotlinx.serialization.json.*
import space.kscience.dataforge.meta.descriptors.NodeDescriptor import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.item
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -30,8 +31,8 @@ class JsonMetaTest {
}) })
} }
val descriptor = NodeDescriptor{ val descriptor = MetaDescriptor {
node("nodeArray"){ item("nodeArray") {
indexKey = "index" indexKey = "index"
} }
} }
@ -43,8 +44,17 @@ class JsonMetaTest {
//println(meta) //println(meta)
val reconstructed = meta.toJson(descriptor) val reconstructed = meta.toJson(descriptor)
println(reconstructed) println(reconstructed)
assertEquals(2, assertEquals(
reconstructed["nodeArray"]?.jsonArray?.get(1)?.jsonObject?.get("index")?.jsonPrimitive?.int) 2,
assertEquals(json,reconstructed) reconstructed
.jsonObject["nodeArray"]
?.jsonArray
?.get(1)
?.jsonObject
?.get("index")
?.jsonPrimitive
?.int
)
assertEquals(json, reconstructed)
} }
} }

View File

@ -1,5 +1,6 @@
package space.kscience.dataforge.meta package space.kscience.dataforge.meta
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.asValue import space.kscience.dataforge.values.asValue
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -10,7 +11,7 @@ class MetaBuilderTest {
fun testBuilder() { fun testBuilder() {
val meta = Meta { val meta = Meta {
"a" put 22 "a" put 22
"b" put listOf(1, 2, 3) "b" put Value.of(listOf(1, 2, 3))
this["c"] = "myValue".asValue() this["c"] = "myValue".asValue()
"node" put { "node" put {
"e" put 12.2 "e" put 12.2

View File

@ -6,7 +6,7 @@ import kotlin.test.assertEquals
class MutableMetaTest{ class MutableMetaTest{
@Test @Test
fun testRemove(){ fun testRemove(){
val meta = Meta { val meta = MutableMeta {
"aNode" put { "aNode" put {
"innerNode" put { "innerNode" put {
"innerValue" put true "innerValue" put true

View File

@ -50,7 +50,7 @@ class SpecificationTest {
@Test @Test
fun testChildModification() { fun testChildModification() {
val config = MutableMeta() val config = MutableMeta()
val child = config.getChild("child") val child = config.getOrCreate("child")
val scheme = TestScheme.write(child) val scheme = TestScheme.write(child)
scheme.a = 22 scheme.a = 22
scheme.b = "test" scheme.b = "test"
@ -61,7 +61,7 @@ class SpecificationTest {
@Test @Test
fun testChildUpdate() { fun testChildUpdate() {
val config = MutableMeta() val config = MutableMeta()
val child = config.getChild("child") val child = config.getOrCreate("child")
child.update(TestScheme) { child.update(TestScheme) {
a = 22 a = 22
b = "test" b = "test"

View File

@ -9,16 +9,14 @@ import kotlin.test.assertNotNull
class DescriptorTest { class DescriptorTest {
val descriptor = NodeDescriptor { val descriptor = MetaDescriptor {
node("aNode") { node("aNode") {
info = "A root demo node" info = "A root demo node"
value("b") { value("b", ValueType.NUMBER) {
info = "b number value" info = "b number value"
type(ValueType.NUMBER)
} }
node("otherNode") { node("otherNode") {
value("otherValue") { value("otherValue", ValueType.BOOLEAN) {
type(ValueType.BOOLEAN)
default(false) default(false)
info = "default value" info = "default value"
} }
@ -30,13 +28,13 @@ class DescriptorTest {
fun testAllowedValues() { fun testAllowedValues() {
val child = descriptor["aNode.b"] val child = descriptor["aNode.b"]
assertNotNull(child) assertNotNull(child)
val allowed = descriptor.nodes["aNode"]?.values?.get("b")?.allowedValues val allowed = descriptor["aNode"]?.get("b")?.allowedValues
assertEquals(emptyList(), allowed) assertEquals(null, allowed)
} }
@Test @Test
fun testDefaultMetaNode(){ fun testDefaultMetaNode() {
val meta = descriptor.defaultMeta val meta = descriptor.defaultNode
assertEquals(false, meta["aNode.otherNode.otherValue"].boolean) assertEquals(false, meta["aNode.otherNode.otherValue"].boolean)
} }
} }

View File

@ -1,8 +1,8 @@
package space.kscience.dataforge.meta package space.kscience.dataforge.meta
import space.kscience.dataforge.names.NameToken import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.values.Null
import space.kscience.dataforge.values.Value import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.asValue
import space.kscience.dataforge.values.isList import space.kscience.dataforge.values.isList
@ -22,11 +22,6 @@ public fun Value.toDynamic(): dynamic {
public fun Meta.toDynamic(): dynamic { public fun Meta.toDynamic(): dynamic {
if (this is DynamicMeta) return this.obj if (this is DynamicMeta) return this.obj
fun MetaItem.toDynamic(): dynamic = when (this) {
is MetaItemValue -> this.value.toDynamic()
is MetaItemNode -> this.node.toDynamic()
}
val res = js("{}") val res = js("{}")
this.items.entries.groupBy { it.key.body }.forEach { (key, value) -> this.items.entries.groupBy { it.key.body }.forEach { (key, value) ->
val list = value.map { it.value } val list = value.map { it.value }
@ -38,46 +33,51 @@ public fun Meta.toDynamic(): dynamic {
return res return res
} }
public class DynamicMeta(internal val obj: dynamic) : AbstractTypedMeta() { public class DynamicMeta(internal val obj: dynamic) : Meta {
private fun keys(): Array<String> = js("Object").keys(obj) private fun keys(): Array<String> = js("Object").keys(obj)
private fun isArray(@Suppress("UNUSED_PARAMETER") obj: dynamic): Boolean = private fun isArray(@Suppress("UNUSED_PARAMETER") obj: dynamic): Boolean =
js("Array.isArray(obj)") as Boolean js("Array").isArray(obj) as Boolean
private fun isPrimitive(obj: dynamic): Boolean = private fun isPrimitive(obj: dynamic): Boolean =
(jsTypeOf(obj) != "object") (jsTypeOf(obj) != "object")
@Suppress("UNCHECKED_CAST", "USELESS_CAST") @Suppress("USELESS_CAST")
private fun asItem(obj: dynamic): TypedMetaItem<DynamicMeta>? { override val value: Value?
return when { get() = if (isArray(obj) && (obj as Array<Any?>).all { isPrimitive(it) }) Value.of(obj as Array<Any?>)
obj == null -> MetaItemValue(Null) else when (jsTypeOf(obj)) {
isArray(obj) && (obj as Array<Any?>).all { isPrimitive(it) } -> MetaItemValue(Value.of(obj as Array<Any?>)) "boolean" -> (obj as Boolean).asValue()
else -> when (jsTypeOf(obj)) { "number" -> (obj as Number).asValue()
"boolean" -> MetaItemValue(Value.of(obj as Boolean)) "string" -> (obj as String).asValue()
"number" -> MetaItemValue(Value.of(obj as Number))
"string" -> MetaItemValue(Value.of(obj as String))
"object" -> MetaItemNode(DynamicMeta(obj))
else -> null else -> null
} }
}
}
override val items: Map<NameToken, TypedMetaItem<DynamicMeta>> override val items: Map<NameToken, Meta>
get() = keys().flatMap<String, Pair<NameToken, TypedMetaItem<DynamicMeta>>> { key -> get() = keys().flatMap<String, Pair<NameToken, Meta>> { key ->
val value = obj[key] ?: return@flatMap emptyList() val value = obj[key] ?: return@flatMap emptyList()
if (isArray(value)) { when {
isArray(value) -> {
val array = value as Array<Any?> val array = value as Array<Any?>
return@flatMap if (array.all { isPrimitive(it) }) { if (array.all { isPrimitive(it) }) {
listOf(NameToken(key) to MetaItemValue(Value.of(array))) emptyList()
} else { } else {
array.mapIndexedNotNull { index, it -> array.mapIndexedNotNull { index, it ->
val item = asItem(it) ?: return@mapIndexedNotNull null val item = DynamicMeta(it)
NameToken(key, index.toString()) to item NameToken(key, index.toString()) to item
} }
} }
} else { }
val item = asItem(value) ?: return@flatMap emptyList() isPrimitive(obj) -> {
emptyList()
}
else -> {
val item = DynamicMeta(value)
listOf(NameToken(key) to item) listOf(NameToken(key) to item)
} }
}.associate { it } }
}.toMap()
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)
} }

View File

@ -1,5 +1,6 @@
package space.kscience.dataforge.meta package space.kscience.dataforge.meta
import space.kscience.dataforge.values.ListValue
import space.kscience.dataforge.values.int import space.kscience.dataforge.values.int
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -21,7 +22,7 @@ class DynamicMetaTest {
val meta = DynamicMeta(d) val meta = DynamicMeta(d)
println(meta) println(meta)
assertEquals(true, meta["ob.booleanNode"].boolean) assertEquals(true, meta["ob.booleanNode"].boolean)
assertEquals(2, meta["array"].value?.list?.get(1)?.int) assertEquals(2, meta["array"]?.value?.list?.get(1)?.int)
assertEquals(4, meta.items.size) assertEquals(4, meta.items.size)
} }
@ -29,7 +30,7 @@ class DynamicMetaTest {
fun testMetaToDynamic(){ fun testMetaToDynamic(){
val meta = Meta { val meta = Meta {
"a" put 22 "a" put 22
"array" put listOf(1, 2, 3) "array" put ListValue(1, 2, 3)
"b" put "myString" "b" put "myString"
"ob" put { "ob" put {
"childNode" put 18 "childNode" put 18

View File

@ -6,7 +6,7 @@ import space.kscience.dataforge.data.DataTree
import space.kscience.dataforge.data.GoalExecutionRestriction import space.kscience.dataforge.data.GoalExecutionRestriction
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.descriptors.Described import space.kscience.dataforge.meta.descriptors.Described
import space.kscience.dataforge.meta.descriptors.ItemDescriptor import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.misc.DFInternal import space.kscience.dataforge.misc.DFInternal
import space.kscience.dataforge.misc.Type import space.kscience.dataforge.misc.Type
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
@ -47,11 +47,11 @@ public class TaskResultBuilder<T : Any>(
@DFInternal @DFInternal
public fun <T : Any> Task( public fun <T : Any> Task(
resultType: KType, resultType: KType,
descriptor: ItemDescriptor? = null, descriptor: MetaDescriptor? = null,
builder: suspend TaskResultBuilder<T>.() -> Unit, builder: suspend TaskResultBuilder<T>.() -> Unit,
): Task<T> = object : Task<T> { ): Task<T> = object : Task<T> {
override val descriptor: ItemDescriptor? = descriptor override val descriptor: MetaDescriptor? = descriptor
override suspend fun execute( override suspend fun execute(
workspace: Workspace, workspace: Workspace,
@ -69,6 +69,6 @@ public fun <T : Any> Task(
@OptIn(DFInternal::class) @OptIn(DFInternal::class)
@Suppress("FunctionName") @Suppress("FunctionName")
public inline fun <reified T : Any> Task( public inline fun <reified T : Any> Task(
descriptor: ItemDescriptor? = null, descriptor: MetaDescriptor? = null,
noinline builder: suspend TaskResultBuilder<T>.() -> Unit, noinline builder: suspend TaskResultBuilder<T>.() -> Unit,
): Task<T> = Task(typeOf<T>(), descriptor, builder) ): Task<T> = Task(typeOf<T>(), descriptor, builder)

View File

@ -9,7 +9,8 @@ import space.kscience.dataforge.data.DataSetBuilder
import space.kscience.dataforge.data.DataTree import space.kscience.dataforge.data.DataTree
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MutableMeta import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.descriptors.NodeDescriptor import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.MetaDescriptorBuilder
import space.kscience.dataforge.misc.DFBuilder import space.kscience.dataforge.misc.DFBuilder
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
@ -26,16 +27,16 @@ public interface TaskContainer {
public inline fun <reified T : Any> TaskContainer.registerTask( public inline fun <reified T : Any> TaskContainer.registerTask(
name: String, name: String,
noinline descriptorBuilder: NodeDescriptor.() -> Unit = {}, noinline descriptorBuilder: MetaDescriptorBuilder.() -> Unit = {},
noinline builder: suspend TaskResultBuilder<T>.() -> Unit, noinline builder: suspend TaskResultBuilder<T>.() -> Unit,
): Unit = registerTask(name.toName(), Task(NodeDescriptor(descriptorBuilder), builder)) ): Unit = registerTask(name.toName(), Task(MetaDescriptor(descriptorBuilder), builder))
public inline fun <reified T : Any> TaskContainer.task( public inline fun <reified T : Any> TaskContainer.task(
noinline descriptorBuilder: NodeDescriptor.() -> Unit = {}, noinline descriptorBuilder: MetaDescriptorBuilder.() -> Unit = {},
noinline builder: suspend TaskResultBuilder<T>.() -> Unit, noinline builder: suspend TaskResultBuilder<T>.() -> Unit,
): PropertyDelegateProvider<Any?, ReadOnlyProperty<Any?, TaskReference<T>>> = PropertyDelegateProvider { _, property -> ): PropertyDelegateProvider<Any?, ReadOnlyProperty<Any?, TaskReference<T>>> = PropertyDelegateProvider { _, property ->
val taskName = property.name.toName() val taskName = property.name.toName()
val task = Task(NodeDescriptor(descriptorBuilder), builder) val task = Task(MetaDescriptor(descriptorBuilder), builder)
registerTask(taskName, task) registerTask(taskName, task)
ReadOnlyProperty { _, _ -> TaskReference(taskName, task) } ReadOnlyProperty { _, _ -> TaskReference(taskName, task) }
} }

View File

@ -86,7 +86,7 @@ public suspend fun <T : Any> DataSetBuilder<T>.file(
//otherwise, read as directory //otherwise, read as directory
plugin.run { plugin.run {
val data = readDataDirectory(path, formatResolver) val data = readDataDirectory(path, formatResolver)
val name = data.getMeta()[Envelope.ENVELOPE_NAME_KEY].string val name = data.getMeta()?.get(Envelope.ENVELOPE_NAME_KEY).string
?: path.fileName.toString().replace(".df", "") ?: path.fileName.toString().replace(".df", "")
emit(name, data) emit(name, data)
} }