Cover corner cases in JsonMeta
This commit is contained in:
parent
a3479e74f7
commit
c8bd3390cb
@ -10,6 +10,7 @@
|
||||
- Kotlin 1.5.10
|
||||
- Build tools 0.10.0
|
||||
- Relaxed type restriction on `MetaConverter`. Now nullables are available.
|
||||
- **Huge API-breaking refactoring of Meta**. Meta now can hava both value and children.
|
||||
|
||||
### Deprecated
|
||||
- Direct use of `Config`
|
||||
@ -17,6 +18,7 @@
|
||||
### Removed
|
||||
- Public PluginManager mutability
|
||||
- Tables and tables-exposed moved to the separate project `tables.kt`
|
||||
- BinaryMetaFormat. Use CBOR encoding instead
|
||||
|
||||
### Fixed
|
||||
- Proper json array index treatment.
|
||||
|
@ -95,8 +95,8 @@ public open class Context internal constructor(
|
||||
|
||||
override fun toMeta(): Meta = Meta {
|
||||
"parent" to parent?.name
|
||||
"properties" put properties.layers.firstOrNull()
|
||||
"plugins" put plugins.map { it.toMeta().asMetaItem() }
|
||||
properties.layers.firstOrNull()?.let { set("properties", it) }
|
||||
"plugins" put plugins.map { it.toMeta() }
|
||||
}
|
||||
|
||||
public companion object {
|
||||
|
@ -1,30 +1,30 @@
|
||||
package space.kscience.dataforge.properties
|
||||
|
||||
|
||||
import space.kscience.dataforge.meta.MutableMeta
|
||||
import space.kscience.dataforge.meta.get
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.meta.transformations.MetaConverter
|
||||
import space.kscience.dataforge.meta.transformations.nullableItemToObject
|
||||
import space.kscience.dataforge.meta.transformations.nullableObjectToMetaItem
|
||||
import space.kscience.dataforge.meta.transformations.nullableMetaToObject
|
||||
import space.kscience.dataforge.meta.transformations.nullableObjectToMeta
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.startsWith
|
||||
|
||||
@DFExperimental
|
||||
public class MetaProperty<T : Any>(
|
||||
public val meta: MutableMeta,
|
||||
public val meta: ObservableMutableMeta,
|
||||
public val name: Name,
|
||||
public val converter: MetaConverter<T>,
|
||||
) : Property<T?> {
|
||||
|
||||
override var value: T?
|
||||
get() = converter.nullableItemToObject(meta[name])
|
||||
get() = converter.nullableMetaToObject(meta[name])
|
||||
set(value) {
|
||||
meta[name] = converter.nullableObjectToMetaItem(value)
|
||||
meta[name] = converter.nullableObjectToMeta(value) ?: Meta.EMPTY
|
||||
}
|
||||
|
||||
override fun onChange(owner: Any?, callback: (T?) -> Unit) {
|
||||
meta.onChange(owner) { name, oldItem, newItem ->
|
||||
if (name.startsWith(this.name) && oldItem != newItem) callback(converter.nullableItemToObject(newItem))
|
||||
meta.onChange(owner) { name ->
|
||||
if (name.startsWith(this@MetaProperty.name)) callback(converter.nullableMetaToObject(get(name)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ package space.kscience.dataforge.properties
|
||||
|
||||
import space.kscience.dataforge.meta.ObservableMeta
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.names.startsWith
|
||||
import space.kscience.dataforge.names.toName
|
||||
import kotlin.reflect.KMutableProperty1
|
||||
|
||||
@ -16,8 +17,8 @@ public fun <P : ObservableMeta, T : Any> P.property(property: KMutableProperty1<
|
||||
}
|
||||
|
||||
override fun onChange(owner: Any?, callback: (T?) -> Unit) {
|
||||
this@property.onChange(this) { name, oldItem, newItem ->
|
||||
if (name.startsWith(property.name.toName()) && oldItem != newItem) {
|
||||
this@property.onChange(this) { name ->
|
||||
if (name.startsWith(property.name.toName())) {
|
||||
callback(property.get(this@property))
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import space.kscience.dataforge.data.Data.Companion.TYPE_OF_NOTHING
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.set
|
||||
import space.kscience.dataforge.names.*
|
||||
import kotlin.reflect.KType
|
||||
|
||||
|
@ -54,7 +54,7 @@ public interface GroupRule {
|
||||
val data = set.getData(name)
|
||||
|
||||
@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)
|
||||
}
|
||||
}
|
||||
|
@ -10,43 +10,49 @@ import space.kscience.dataforge.io.MetaFormat
|
||||
import space.kscience.dataforge.io.MetaFormatFactory
|
||||
import space.kscience.dataforge.io.readUtf8String
|
||||
import space.kscience.dataforge.io.writeUtf8String
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.meta.descriptors.ItemDescriptor
|
||||
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||
import space.kscience.dataforge.meta.descriptors.get
|
||||
import space.kscience.dataforge.meta.isLeaf
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.names.NameToken
|
||||
import space.kscience.dataforge.names.withIndex
|
||||
import space.kscience.dataforge.values.ListValue
|
||||
import space.kscience.dataforge.values.Null
|
||||
import space.kscience.dataforge.values.Value
|
||||
import space.kscience.dataforge.values.parseValue
|
||||
import kotlin.collections.component1
|
||||
import kotlin.collections.component2
|
||||
import kotlin.collections.set
|
||||
|
||||
public fun Meta.toYaml(): YamlMap {
|
||||
val map: Map<String, Any?> = items.entries.associate { (key, item) ->
|
||||
key.toString() to when (item) {
|
||||
is MetaItemValue -> {
|
||||
item.value.value
|
||||
}
|
||||
is MetaItemNode -> {
|
||||
item.node.toYaml()
|
||||
}
|
||||
key.toString() to if (item.isLeaf) {
|
||||
item.value?.value
|
||||
} else {
|
||||
item.toYaml()
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
val map = LinkedHashMap<NameToken, MetaItem>()
|
||||
override val value: Value?
|
||||
get() = yamlMap.getStringOrNull(null)?.parseValue()
|
||||
|
||||
private fun buildItems(): Map<NameToken, Meta> {
|
||||
val map = LinkedHashMap<NameToken, Meta>()
|
||||
|
||||
yamlMap.content.entries.forEach { (key, value) ->
|
||||
val stringKey = key.toString()
|
||||
val itemDescriptor = descriptor?.items?.get(stringKey)
|
||||
val itemDescriptor = descriptor?.get(stringKey)
|
||||
val token = NameToken(stringKey)
|
||||
when (value) {
|
||||
YamlNull -> Null.asMetaItem()
|
||||
is YamlLiteral -> map[token] = value.content.parseValue().asMetaItem()
|
||||
is YamlMap -> map[token] = value.toMeta().asMetaItem()
|
||||
YamlNull -> Meta(Null)
|
||||
is YamlLiteral -> map[token] = Meta(value.content.parseValue())
|
||||
is YamlMap -> map[token] = value.toMeta()
|
||||
is YamlList -> if (value.all { it is YamlLiteral }) {
|
||||
val listValue = ListValue(
|
||||
value.map {
|
||||
@ -54,29 +60,33 @@ private class YamlMeta(private val yamlMap: YamlMap, private val descriptor: Nod
|
||||
(it as YamlLiteral).content.parseValue()
|
||||
}
|
||||
)
|
||||
map[token] = MetaItemValue(listValue)
|
||||
map[token] = Meta(listValue)
|
||||
} 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)
|
||||
?: index.toString() //In case index is non-string, the backward transformation will be broken.
|
||||
|
||||
val tokenWithIndex = token.withIndex(indexValue)
|
||||
map[tokenWithIndex] = yamlElement.toMetaItem(itemDescriptor)
|
||||
map[tokenWithIndex] = yamlElement.toMeta(itemDescriptor)
|
||||
}
|
||||
}
|
||||
}
|
||||
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) {
|
||||
YamlNull -> Null.asMetaItem()
|
||||
is YamlLiteral -> content.parseValue().asMetaItem()
|
||||
is YamlMap -> toMeta().asMetaItem()
|
||||
public fun YamlElement.toMeta(descriptor: MetaDescriptor? = null): Meta = when (this) {
|
||||
YamlNull -> Meta(Null)
|
||||
is YamlLiteral -> Meta(content.parseValue())
|
||||
is YamlMap -> toMeta()
|
||||
//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)
|
||||
@ -88,13 +98,13 @@ public fun YamlMap.toMeta(): Meta = YamlMeta(this)
|
||||
@DFExperimental
|
||||
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 string = Yaml.encodeToString(yaml)
|
||||
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())
|
||||
return yaml.toMeta()
|
||||
}
|
||||
@ -113,10 +123,10 @@ public class YamlMetaFormat(private val meta: Meta) : MetaFormat {
|
||||
|
||||
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)
|
||||
|
||||
override fun readMeta(input: Input, descriptor: NodeDescriptor?): Meta =
|
||||
override fun readMeta(input: Input, descriptor: MetaDescriptor?): Meta =
|
||||
default.readMeta(input, descriptor)
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@ import space.kscience.dataforge.names.toName
|
||||
private class PartDescriptor : Scheme() {
|
||||
var offset 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) {
|
||||
val MULTIPART_KEY = ENVELOPE_NODE_KEY + "multipart"
|
||||
@ -86,15 +86,15 @@ public fun EnvelopeBuilder.envelopes(
|
||||
public fun Envelope.parts(): EnvelopeParts {
|
||||
if (data == null) return emptyList()
|
||||
//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)
|
||||
}
|
||||
return if (parts.isEmpty()) {
|
||||
listOf(EnvelopePart(data!!, meta[MULTIPART_KEY].node))
|
||||
listOf(EnvelopePart(data!!, meta[MULTIPART_KEY]))
|
||||
} else {
|
||||
parts.map {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -6,13 +6,11 @@ import space.kscience.dataforge.context.Factory
|
||||
import space.kscience.dataforge.io.IOFormat.Companion.NAME_KEY
|
||||
import space.kscience.dataforge.io.IOFormatFactory.Companion.IO_FORMAT_TYPE
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.MetaItemValue
|
||||
import space.kscience.dataforge.meta.MetaRepr
|
||||
import space.kscience.dataforge.misc.Named
|
||||
import space.kscience.dataforge.misc.Type
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.asName
|
||||
import space.kscience.dataforge.values.Value
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.typeOf
|
||||
|
||||
@ -117,19 +115,19 @@ public object DoubleIOFormat : IOFormat<Double>, IOFormatFactory<Double> {
|
||||
override fun readObject(input: Input): Double = input.readDouble()
|
||||
}
|
||||
|
||||
public object ValueIOFormat : IOFormat<Value>, IOFormatFactory<Value> {
|
||||
override fun invoke(meta: Meta, context: Context): IOFormat<Value> = this
|
||||
|
||||
override val name: Name = "value".asName()
|
||||
|
||||
override val type: KType get() = typeOf<Value>()
|
||||
|
||||
override fun writeObject(output: Output, obj: Value) {
|
||||
BinaryMetaFormat.run { output.writeValue(obj) }
|
||||
}
|
||||
|
||||
override fun readObject(input: Input): Value {
|
||||
return (BinaryMetaFormat.run { input.readMetaItem() } as? MetaItemValue)?.value
|
||||
?: error("The item is not a value")
|
||||
}
|
||||
}
|
||||
//public object ValueIOFormat : IOFormat<Value>, IOFormatFactory<Value> {
|
||||
// override fun invoke(meta: Meta, context: Context): IOFormat<Value> = this
|
||||
//
|
||||
// override val name: Name = "value".asName()
|
||||
//
|
||||
// override val type: KType get() = typeOf<Value>()
|
||||
//
|
||||
// override fun writeObject(output: Output, obj: Value) {
|
||||
// BinaryMetaFormat.run { output.writeValue(obj) }
|
||||
// }
|
||||
//
|
||||
// override fun readObject(input: Input): Value {
|
||||
// return (BinaryMetaFormat.run { input.readMetaItem() } as? MetaItemValue)?.value
|
||||
// ?: error("The item is not a value")
|
||||
// }
|
||||
//}
|
@ -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.IOFormatFactory.Companion.IO_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.toName
|
||||
import kotlin.native.concurrent.ThreadLocal
|
||||
@ -19,13 +21,13 @@ public class IOPlugin(meta: Meta) : AbstractPlugin(meta) {
|
||||
context.gather<IOFormatFactory<*>>(IO_FORMAT_TYPE).values
|
||||
}
|
||||
|
||||
public fun <T : Any> resolveIOFormat(item: MetaItem, type: KClass<out T>): IOFormat<T>? {
|
||||
val key = item.string ?: item.node[NAME_KEY]?.string ?: error("Format name not defined")
|
||||
public fun <T : Any> resolveIOFormat(item: Meta, type: KClass<out T>): IOFormat<T>? {
|
||||
val key = item.string ?: item[NAME_KEY]?.string ?: error("Format name not defined")
|
||||
val name = key.toName()
|
||||
return ioFormatFactories.find { it.name == name }?.let {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
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? =
|
||||
envelopeFormatFactories.find { it.name == name }?.invoke(meta, context)
|
||||
|
||||
public fun resolveEnvelopeFormat(item: MetaItem): EnvelopeFormat? {
|
||||
val name = item.string ?: item.node[NAME_KEY]?.string ?: error("Envelope format name not defined")
|
||||
val meta = item.node[META_KEY].node ?: Meta.EMPTY
|
||||
public fun resolveEnvelopeFormat(item: Meta): EnvelopeFormat? {
|
||||
val name = item.string ?: item[NAME_KEY]?.string ?: error("Envelope format name not defined")
|
||||
val meta = item[META_KEY] ?: Meta.EMPTY
|
||||
return resolveEnvelopeFormat(name.toName(), meta)
|
||||
}
|
||||
|
||||
@ -62,7 +64,7 @@ public class IOPlugin(meta: Meta) : AbstractPlugin(meta) {
|
||||
}
|
||||
|
||||
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> =
|
||||
listOf(TaggedEnvelopeFormat, TaglessEnvelopeFormat)
|
||||
|
||||
|
@ -7,7 +7,7 @@ import io.ktor.utils.io.core.use
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.io.MetaFormatFactory.Companion.META_FORMAT_TYPE
|
||||
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.names.Name
|
||||
import space.kscience.dataforge.names.asName
|
||||
@ -30,10 +30,10 @@ public interface MetaFormat : IOFormat<Meta> {
|
||||
public fun writeMeta(
|
||||
output: Output,
|
||||
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)
|
||||
|
@ -2,11 +2,12 @@ package space.kscience.dataforge.io
|
||||
|
||||
import kotlinx.serialization.json.*
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.meta.JsonMeta.Companion.JSON_ARRAY_KEY
|
||||
import space.kscience.dataforge.values.ListValue
|
||||
import space.kscience.dataforge.values.double
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
|
||||
fun Meta.toByteArray(format: MetaFormat = JsonMetaFormat) = buildByteArray {
|
||||
format.writeObject(this@buildByteArray, this@toByteArray)
|
||||
}
|
||||
@ -16,20 +17,6 @@ fun MetaFormat.fromByteArray(packet: ByteArray): Meta {
|
||||
}
|
||||
|
||||
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
|
||||
fun testJsonMetaFormat() {
|
||||
@ -50,36 +37,36 @@ class MetaFormatTest {
|
||||
if (meta[it] != result[it]) error("${meta[it]} != ${result[it]}")
|
||||
}
|
||||
|
||||
assertEquals<Meta>(meta, result)
|
||||
assertEquals(meta, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testJsonToMeta() {
|
||||
val json = buildJsonArray {
|
||||
//top level array
|
||||
add(buildJsonArray {
|
||||
add(JsonPrimitive(88))
|
||||
add(buildJsonObject {
|
||||
addJsonArray {
|
||||
add(88)
|
||||
addJsonObject {
|
||||
put("c", "aasdad")
|
||||
put("d", true)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
add("value")
|
||||
add(buildJsonArray {
|
||||
add(JsonPrimitive(1.0))
|
||||
add(JsonPrimitive(2.0))
|
||||
add(JsonPrimitive(3.0))
|
||||
})
|
||||
addJsonArray {
|
||||
add(1.0)
|
||||
add(2.0)
|
||||
add(3.0)
|
||||
}
|
||||
}
|
||||
val meta = json.toMeta().node!!
|
||||
val meta = json.toMeta()
|
||||
|
||||
assertEquals(true, meta["$JSON_ARRAY_KEY[0].$JSON_ARRAY_KEY[1].d"].boolean)
|
||||
assertEquals("value", 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(true, meta["${Meta.JSON_ARRAY_KEY}[0].${Meta.JSON_ARRAY_KEY}[1].d"].boolean)
|
||||
assertEquals("value", meta["${Meta.JSON_ARRAY_KEY}[1]"].string)
|
||||
assertEquals(listOf(1.0, 2.0, 3.0), meta["${Meta.JSON_ARRAY_KEY}[2]"]?.value?.list?.map { it.double })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testJsonStringToMeta(){
|
||||
fun testJsonStringToMeta() {
|
||||
val jsonString = """
|
||||
{
|
||||
"comments": [
|
||||
@ -97,8 +84,8 @@ class MetaFormatTest {
|
||||
}
|
||||
""".trimIndent()
|
||||
val json = Json.parseToJsonElement(jsonString)
|
||||
val meta = json.toMeta().node!!
|
||||
assertEquals(ListValue.EMPTY, meta["comments"].value)
|
||||
val meta = json.toMeta()
|
||||
assertEquals(ListValue.EMPTY, meta["comments"]?.value)
|
||||
}
|
||||
|
||||
}
|
@ -5,7 +5,7 @@ import kotlinx.serialization.cbor.Cbor
|
||||
import kotlinx.serialization.json.Json
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
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.toName
|
||||
import kotlin.test.Test
|
||||
@ -28,7 +28,7 @@ class MetaSerializerTest {
|
||||
fun testMetaSerialization() {
|
||||
val string = JSON_PRETTY.encodeToString(MetaSerializer, meta)
|
||||
println(string)
|
||||
val restored = JSON_PLAIN.decodeFromString(MetaSerializer, string)
|
||||
val restored = JSON_PLAIN.decodeFromString(MetaSerializer, string).seal()
|
||||
assertEquals(meta, restored)
|
||||
}
|
||||
|
||||
@ -52,7 +52,7 @@ class MetaSerializerTest {
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
@Test
|
||||
fun testMetaItemDescriptor() {
|
||||
val descriptor = TypedMetaItem.serializer(MetaSerializer).descriptor.getElementDescriptor(0)
|
||||
val descriptor = MetaSerializer.descriptor.getElementDescriptor(0)
|
||||
println(descriptor)
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ package space.kscience.dataforge.io
|
||||
import io.ktor.utils.io.core.*
|
||||
import io.ktor.utils.io.streams.asOutput
|
||||
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.misc.DFExperimental
|
||||
import java.nio.file.Files
|
||||
@ -97,7 +97,7 @@ public inline fun <reified T : Any> IOPlugin.resolveIOFormat(): IOFormat<T>? {
|
||||
public fun IOPlugin.readMetaFile(
|
||||
path: Path,
|
||||
formatOverride: MetaFormat? = null,
|
||||
descriptor: NodeDescriptor? = null,
|
||||
descriptor: MetaDescriptor? = null,
|
||||
): Meta {
|
||||
if (!Files.exists(path)) error("Meta file $path does not exist")
|
||||
|
||||
@ -125,7 +125,7 @@ public fun IOPlugin.writeMetaFile(
|
||||
path: Path,
|
||||
meta: Meta,
|
||||
metaFormat: MetaFormatFactory = JsonMetaFormat,
|
||||
descriptor: NodeDescriptor? = null,
|
||||
descriptor: MetaDescriptor? = null,
|
||||
) {
|
||||
val actualPath = if (Files.isDirectory(path)) {
|
||||
path.resolve("@" + metaFormat.name.toString())
|
||||
|
@ -4,9 +4,13 @@ package space.kscience.dataforge.meta
|
||||
|
||||
import kotlinx.serialization.json.*
|
||||
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
|
||||
import space.kscience.dataforge.meta.descriptors.get
|
||||
import space.kscience.dataforge.names.NameToken
|
||||
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
|
||||
@ -15,7 +19,7 @@ public fun Value.toJson(descriptor: MetaDescriptor? = null): JsonElement = when
|
||||
ValueType.NUMBER -> JsonPrimitive(numberOrNull)
|
||||
ValueType.STRING -> JsonPrimitive(string)
|
||||
ValueType.BOOLEAN -> JsonPrimitive(boolean)
|
||||
ValueType.LIST -> JsonArray(list.map { it.toJson() })
|
||||
ValueType.LIST -> JsonArray(list.map { it.toJson(descriptor) })
|
||||
ValueType.NULL -> JsonNull
|
||||
}
|
||||
|
||||
@ -46,7 +50,7 @@ private fun Meta.toJsonWithIndex(descriptor: MetaDescriptor?, index: String?): J
|
||||
|
||||
//Add index if needed
|
||||
if (index != null) {
|
||||
pairs += Meta.INDEX_KEY to JsonPrimitive(index)
|
||||
pairs += (descriptor?.indexKey ?: Meta.INDEX_KEY) to JsonPrimitive(index)
|
||||
}
|
||||
|
||||
//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 JsonObject.toMeta(descriptor: MetaDescriptor? = null): JsonMeta = JsonMeta(this, descriptor)
|
||||
|
||||
/**
|
||||
* Convert a Json primitive to a [Value]
|
||||
*/
|
||||
public fun JsonPrimitive.toValue(descriptor: MetaDescriptor?): Value {
|
||||
return when (this) {
|
||||
JsonNull -> Null
|
||||
@ -68,62 +73,160 @@ public fun JsonPrimitive.toValue(descriptor: MetaDescriptor?): Value {
|
||||
if (isString) {
|
||||
StringValue(content)
|
||||
} else {
|
||||
//consider using LazyParse
|
||||
content.parseValue()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun JsonElement.toMeta(descriptor: MetaDescriptor? = null): TypedMeta<JsonMeta> = JsonMeta(this, descriptor)
|
||||
/**
|
||||
* Turn this [JsonArray] into a [ListValue] with recursion or return null if it contains objects
|
||||
*/
|
||||
public fun JsonElement.toValueOrNull(descriptor: MetaDescriptor?): Value? = when (this) {
|
||||
is JsonPrimitive -> toValue(descriptor)
|
||||
is JsonObject -> get(Meta.VALUE_KEY)?.toValueOrNull(descriptor)
|
||||
is JsonArray -> {
|
||||
if(isEmpty()) ListValue.EMPTY else {
|
||||
val values = map { it.toValueOrNull(descriptor) }
|
||||
values.map { it ?: return null }.asValue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A meta wrapping json object
|
||||
* Fill a mutable map with children produced from [element] with given top level [key]
|
||||
*/
|
||||
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 {
|
||||
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()
|
||||
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 {
|
||||
null
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
is JsonObject -> {
|
||||
val indexKey = descriptor?.indexKey ?: Meta.INDEX_KEY
|
||||
val index = element[indexKey]?.jsonPrimitive?.content
|
||||
put(NameToken(key, index), element.toMeta(descriptor))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
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"
|
||||
// }
|
||||
//}
|
@ -49,7 +49,13 @@ public interface Meta : MetaRepr {
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -23,7 +23,7 @@ public fun Meta.item(key: Name? = null): MetaDelegate = ReadOnlyProperty { _, pr
|
||||
public fun <R : Any> MetaDelegate.convert(
|
||||
converter: MetaConverter<R>,
|
||||
): 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>,
|
||||
default: () -> R,
|
||||
): 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()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,30 +1,35 @@
|
||||
package space.kscience.dataforge.meta
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.builtins.MapSerializer
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.json.JsonDecoder
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import space.kscience.dataforge.names.NameToken
|
||||
import space.kscience.dataforge.names.NameTokenSerializer
|
||||
import kotlinx.serialization.json.JsonEncoder
|
||||
|
||||
/**
|
||||
* Serialized for meta
|
||||
* Serialized for [Meta]
|
||||
*/
|
||||
public object MetaSerializer : KSerializer<Meta> {
|
||||
private val genericMetaSerializer = SealedMeta.serializer()
|
||||
|
||||
private val itemsSerializer: KSerializer<Map<NameToken, Meta>> = MapSerializer(
|
||||
NameTokenSerializer,
|
||||
MetaSerializer
|
||||
)
|
||||
private val jsonSerializer = JsonElement.serializer()
|
||||
|
||||
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) {
|
||||
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]
|
||||
*/
|
||||
public object MutableMetaSerializer : KSerializer<MutableMeta> {
|
||||
override val descriptor: SerialDescriptor = MetaSerializer.descriptor
|
||||
|
||||
override val descriptor: SerialDescriptor = MetaSerializer.descriptor
|
||||
|
||||
override fun deserialize(decoder: Decoder): MutableMeta {
|
||||
val meta = decoder.decodeSerializableValue(MetaSerializer)
|
||||
|
@ -1,20 +1,27 @@
|
||||
package space.kscience.dataforge.meta
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import space.kscience.dataforge.misc.DFBuilder
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.names.*
|
||||
import space.kscience.dataforge.values.EnumValue
|
||||
import space.kscience.dataforge.values.Value
|
||||
import space.kscience.dataforge.values.asValue
|
||||
import kotlin.js.JsName
|
||||
import kotlin.jvm.Synchronized
|
||||
|
||||
|
||||
/**
|
||||
* Mark a meta builder
|
||||
*/
|
||||
@DslMarker
|
||||
public annotation class MetaBuilder
|
||||
|
||||
/**
|
||||
* Mutable variant of [Meta]
|
||||
* TODO documentation
|
||||
*/
|
||||
@Serializable(MutableMetaSerializer::class)
|
||||
@MetaBuilder
|
||||
public interface MutableMeta : Meta {
|
||||
|
||||
override val items: Map<NameToken, MutableMeta>
|
||||
@ -30,9 +37,9 @@ public interface MutableMeta : 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
|
||||
@ -109,11 +116,17 @@ public interface MutableMeta : Meta {
|
||||
toName() put repr.toMeta()
|
||||
}
|
||||
|
||||
public infix fun String.put(iterable: Iterable<Meta>) {
|
||||
setIndexed(toName(), iterable)
|
||||
}
|
||||
|
||||
public infix fun String.put(builder: MutableMeta.() -> Unit) {
|
||||
set(toName(), MutableMeta(builder))
|
||||
}
|
||||
}
|
||||
|
||||
public fun MutableMeta.getOrCreate(string: String): MutableMeta = getOrCreate(string.toName())
|
||||
|
||||
@Serializable(MutableMetaSerializer::class)
|
||||
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
|
||||
}
|
||||
|
||||
public operator fun MutableMeta.set(key: String, item: Meta?): Unit =
|
||||
set(key.toName(), item)
|
||||
public fun <M : MutableTypedMeta<M>> M.getOrCreate(string: String): M = getOrCreate(string.toName())
|
||||
|
||||
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
|
||||
*/
|
||||
public operator fun MutableMeta.set(name: Name, value: Any?) {
|
||||
when (value) {
|
||||
null -> remove(name)
|
||||
else -> set(name, Value.of(value))
|
||||
}
|
||||
public operator fun MutableMeta.set(name: Name, value: Value?) {
|
||||
getOrCreate(name).value = Value.of(value)
|
||||
}
|
||||
|
||||
public operator fun MutableMeta.set(name: NameToken, value: Any?): Unit =
|
||||
set(name.asName(), value)
|
||||
|
||||
public operator fun MutableMeta.set(key: String, value: Any?): Unit =
|
||||
set(key.toName(), value)
|
||||
|
||||
public operator fun MutableMeta.set(key: String, index: String, value: Any?): Unit =
|
||||
set(key.toName().withIndex(index), value)
|
||||
|
||||
|
||||
/* Same name siblings generation */
|
||||
|
||||
public fun MutableMeta.setIndexedItems(
|
||||
@ -177,10 +215,10 @@ public fun MutableMeta.setIndexed(
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@ -190,7 +228,7 @@ public operator fun <M : MutableTypedMeta<M>> MutableTypedMeta<M>.set(name: Stri
|
||||
* * node updates node and replaces anything but node
|
||||
* * node list updates node list if number of nodes in the list is the same and replaces anything otherwise
|
||||
*/
|
||||
public fun <M : MutableTypedMeta<M>> MutableTypedMeta<M>.update(meta: Meta) {
|
||||
public fun MutableMeta.update(meta: Meta) {
|
||||
meta.valueSequence().forEach { (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].
|
||||
* The implementation uses blocking synchronization on mutation on JVM
|
||||
*/
|
||||
@DFBuilder
|
||||
private class MutableMetaImpl(
|
||||
override var value: Value?,
|
||||
value: Value?,
|
||||
children: Map<NameToken, Meta> = emptyMap()
|
||||
) : ObservableMutableMeta {
|
||||
|
||||
private val children: LinkedHashMap<NameToken, MutableMetaImpl> =
|
||||
override var value = value
|
||||
@Synchronized set
|
||||
|
||||
private val children: LinkedHashMap<NameToken, ObservableMutableMeta> =
|
||||
LinkedHashMap(children.mapValues { (key, meta) ->
|
||||
MutableMetaImpl(meta.value, meta.items).apply { adoptBy(this, key) }
|
||||
})
|
||||
|
||||
override val items: Map<NameToken, MutableMetaImpl> get() = children
|
||||
override val items: Map<NameToken, ObservableMutableMeta> get() = children
|
||||
|
||||
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 ->
|
||||
parent.changed(key + name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// fun attach(name: Name, node: MutableMetaImpl) {
|
||||
// when (name.length) {
|
||||
// 0 -> error("Can't set a meta with empty name")
|
||||
// 1 -> {
|
||||
// val key = name.firstOrNull()!!
|
||||
// children[key] = node
|
||||
// adoptBy(this, key)
|
||||
// changed(name)
|
||||
// }
|
||||
// else -> get(name.cutLast())?.attach(name.lastOrNull()!!.asName(), node)
|
||||
// }
|
||||
// }
|
||||
override fun attach(name: Name, node: ObservableMutableMeta) {
|
||||
when (name.length) {
|
||||
0 -> error("Can't set a meta with empty name")
|
||||
1 -> {
|
||||
replaceItem(name.first(), get(name), node)
|
||||
}
|
||||
else -> get(name.cutLast())?.attach(name.lastOrNull()!!.asName(), node)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and attach empty node
|
||||
*/
|
||||
private fun createNode(name: Name): ObservableMutableMeta {
|
||||
val newNode = MutableMetaImpl(null)
|
||||
when (name.length) {
|
||||
0 -> throw IllegalArgumentException("Can't create a node with empty name")
|
||||
1 -> {
|
||||
children[name.first()] = newNode
|
||||
newNode.adoptBy(this, name.first())
|
||||
} //do not notify, no value changed
|
||||
else -> getOrCreate(name.first().asName()).getOrCreate(name.cutFirst())
|
||||
}
|
||||
return newNode
|
||||
private fun createNode(name: Name): ObservableMutableMeta = when (name.length) {
|
||||
0 -> throw IllegalArgumentException("Can't create a node with empty name")
|
||||
1 -> {
|
||||
val newNode = MutableMetaImpl(null)
|
||||
children[name.first()] = newNode
|
||||
newNode.adoptBy(this, name.first())
|
||||
newNode
|
||||
} //do not notify, no value changed
|
||||
else -> getOrCreate(name.first().asName()).getOrCreate(name.cutFirst())
|
||||
}
|
||||
|
||||
override fun getOrCreate(name: Name): ObservableMutableMeta = get(name) ?: createNode(name)
|
||||
|
||||
override fun removeNode(name: Name) {
|
||||
override fun remove(name: Name) {
|
||||
when (name.length) {
|
||||
0 -> error("Can't remove self")
|
||||
1 -> if (children.remove(name.firstOrNull()!!) != null) changed(name)
|
||||
else -> get(name.cutLast())?.removeNode(name.lastOrNull()!!.asName())
|
||||
else -> get(name.cutLast())?.remove(name.lastOrNull()!!.asName())
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun replaceItem(
|
||||
key: NameToken,
|
||||
oldItem: ObservableMutableMeta?,
|
||||
newItem: MutableMetaImpl?
|
||||
newItem: ObservableMutableMeta?
|
||||
) {
|
||||
if (oldItem != newItem) {
|
||||
if (newItem == null) {
|
||||
children.remove(key)
|
||||
//remove child and remove stale listener
|
||||
children.remove(key)?.removeListener(this)
|
||||
} else {
|
||||
newItem.adoptBy(this, key)
|
||||
children[key] = newItem
|
||||
@ -364,7 +384,7 @@ private class MutableMetaImpl(
|
||||
* Append the node with a same-name-sibling, automatically generating numerical index
|
||||
*/
|
||||
@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" }
|
||||
val newIndex = name.lastOrNull()!!.index
|
||||
if (newIndex != null) {
|
||||
@ -376,7 +396,7 @@ public fun MutableMeta.append(name: Name, value: Any?) {
|
||||
}
|
||||
|
||||
@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.
|
||||
@ -398,12 +418,15 @@ public fun Meta.toMutableMeta(): ObservableMutableMeta = MutableMetaImpl(value,
|
||||
|
||||
public fun Meta.asMutableMeta(): MutableMeta = (this as? MutableMeta) ?: toMutableMeta()
|
||||
|
||||
@JsName("newMutableMeta")
|
||||
public fun MutableMeta(): ObservableMutableMeta = MutableMetaImpl(null)
|
||||
|
||||
/**
|
||||
* Build a [MutableMeta] using given transformation
|
||||
*/
|
||||
@Suppress("FunctionName")
|
||||
public fun MutableMeta(builder: MutableMeta.() -> Unit = {}): ObservableMutableMeta =
|
||||
MutableMetaImpl(null).apply(builder)
|
||||
public inline fun MutableMeta(builder: MutableMeta.() -> Unit = {}): ObservableMutableMeta =
|
||||
MutableMeta().apply(builder)
|
||||
|
||||
|
||||
/**
|
||||
@ -411,4 +434,32 @@ public fun MutableMeta(builder: MutableMeta.() -> Unit = {}): ObservableMutableM
|
||||
* The listeners of the original Config are not retained.
|
||||
*/
|
||||
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)
|
||||
|
@ -32,10 +32,10 @@ public fun <R : Any> MutableMetaDelegate.convert(
|
||||
): ReadWriteProperty<Any?, R?> = object : ReadWriteProperty<Any?, 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?) {
|
||||
val item = value?.let(converter::objectToMetaItem)
|
||||
val item = value?.let(converter::objectToMeta)
|
||||
this@convert.setValue(thisRef, property, item)
|
||||
}
|
||||
}
|
||||
@ -46,10 +46,10 @@ public fun <R : Any> MutableMetaDelegate.convert(
|
||||
): ReadWriteProperty<Any?, R> = object : ReadWriteProperty<Any?, 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) {
|
||||
val item = value.let(converter::objectToMetaItem)
|
||||
val item = value.let(converter::objectToMeta)
|
||||
this@convert.setValue(thisRef, property, item)
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ public interface ObservableMeta : Meta {
|
||||
/**
|
||||
* 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(
|
||||
val origin: MutableMeta,
|
||||
@ -68,8 +68,8 @@ private class ObservableMetaWrapper(
|
||||
get(name) ?: ObservableMetaWrapper(origin.getOrCreate(name))
|
||||
|
||||
|
||||
override fun removeNode(name: Name) {
|
||||
origin.removeNode(name)
|
||||
override fun remove(name: Name) {
|
||||
origin.remove(name)
|
||||
changed(name)
|
||||
}
|
||||
|
||||
@ -84,10 +84,18 @@ private class ObservableMetaWrapper(
|
||||
override fun toMeta(): Meta {
|
||||
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)
|
||||
|
||||
|
||||
/**
|
||||
|
@ -2,39 +2,43 @@ package space.kscience.dataforge.meta
|
||||
|
||||
import space.kscience.dataforge.meta.descriptors.*
|
||||
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].
|
||||
* Default item provider and [NodeDescriptor] are optional
|
||||
*/
|
||||
public open class Scheme internal constructor(
|
||||
public open class Scheme(
|
||||
source: MutableMeta = MutableMeta()
|
||||
) : Described, ObservableMutableMeta, Meta by source {
|
||||
) : Described, MutableMeta, ObservableMeta, Meta by source {
|
||||
|
||||
private var source = source.asObservable()
|
||||
|
||||
final override var descriptor: MetaDescriptor? = null
|
||||
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(
|
||||
items: MutableMeta,
|
||||
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)
|
||||
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.
|
||||
*/
|
||||
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,
|
||||
) : Specification<T>, Described {
|
||||
|
||||
override fun read(items: Meta): T = empty().also {
|
||||
it.wrap(MutableMeta().withDefault(items))
|
||||
override fun read(source: Meta): T = empty().also {
|
||||
it.wrap(MutableMeta().withDefault(source))
|
||||
}
|
||||
|
||||
override fun write(target: MutableMeta): T = empty().also {
|
||||
|
@ -1,5 +1,6 @@
|
||||
package space.kscience.dataforge.meta
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import space.kscience.dataforge.names.NameToken
|
||||
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
|
||||
*/
|
||||
@Serializable
|
||||
public class SealedMeta internal constructor(
|
||||
override val value: Value?,
|
||||
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())
|
||||
|
||||
@Suppress("FunctionName")
|
||||
public fun Meta(builder: MutableMeta.() -> Unit): SealedMeta =
|
||||
public inline fun Meta(builder: MutableMeta.() -> Unit): SealedMeta =
|
||||
MutableMeta(builder).seal()
|
||||
|
||||
|
@ -33,15 +33,15 @@ public interface ReadOnlySpecification<out T : Any> {
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>,
|
||||
action: T.() -> Unit
|
||||
): T = spec.write(this).apply(action)
|
||||
|
@ -5,20 +5,4 @@ package space.kscience.dataforge.meta.descriptors
|
||||
*/
|
||||
public interface Described {
|
||||
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) }
|
||||
// }
|
||||
// }
|
||||
}
|
@ -2,6 +2,8 @@ package space.kscience.dataforge.meta.descriptors
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
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.values.Value
|
||||
import space.kscience.dataforge.values.ValueType
|
||||
@ -12,7 +14,7 @@ import space.kscience.dataforge.values.ValueType
|
||||
* @param children child descriptors for this node
|
||||
* @param multiple True if same name siblings with this name are allowed
|
||||
* @param required True if the item is required
|
||||
* @param type list of allowed types for [Meta.value], null if all values are allowed
|
||||
* @param 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 defaultValue the default [Meta.value] for the node
|
||||
* @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 defaultValue: Value? = null,
|
||||
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) {
|
||||
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 class MetaDescriptorBuilder {
|
||||
public var info: String? = null
|
||||
public var children: MutableMap<String, MetaDescriptor> = hashMapOf()
|
||||
public var multiple: Boolean = false
|
||||
public var required: Boolean = false
|
||||
public var type: List<ValueType>? = null
|
||||
public var indexKey: String = Meta.INDEX_KEY
|
||||
public var default: Value? = null
|
||||
public var attributes: Meta = Meta.EMPTY
|
||||
/**
|
||||
* A node constructed of default values for this descriptor and its children
|
||||
*/
|
||||
public val MetaDescriptor.defaultNode: Meta
|
||||
get() = Meta {
|
||||
defaultValue?.let { defaultValue ->
|
||||
this.value = defaultValue
|
||||
}
|
||||
children.forEach { (key, descriptor) ->
|
||||
set(key, descriptor.defaultNode)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun build(): MetaDescriptor = MetaDescriptor(
|
||||
info = info,
|
||||
children = children,
|
||||
multiple = multiple,
|
||||
required = required,
|
||||
type = type,
|
||||
indexKey = indexKey,
|
||||
defaultValue = default,
|
||||
attributes = attributes
|
||||
)
|
||||
}
|
||||
/**
|
||||
* Check if given item suits the descriptor
|
||||
*/
|
||||
public fun MetaDescriptor.validate(item: Meta?): Boolean = (item != null || !required) &&
|
||||
allowedValues?.let { item?.value in it } ?: true &&
|
||||
children.all { (key, childDescriptor) -> childDescriptor.validate(item?.get(key)) }
|
@ -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()
|
||||
}
|
@ -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()
|
||||
}
|
@ -1,124 +1,89 @@
|
||||
package space.kscience.dataforge.meta.transformations
|
||||
|
||||
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 fun itemToObject(item: MetaItem): T
|
||||
public fun objectToMetaItem(obj: T): MetaItem
|
||||
public fun metaToObject(meta: Meta): T?
|
||||
public fun objectToMeta(obj: T): Meta
|
||||
|
||||
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> {
|
||||
override fun itemToObject(item: MetaItem): Meta = when (item) {
|
||||
is MetaItemNode -> item.node
|
||||
is MetaItemValue -> item.value.toMeta()
|
||||
}
|
||||
|
||||
override fun objectToMetaItem(obj: Meta): MetaItem = MetaItemNode(obj)
|
||||
override fun metaToObject(meta: Meta): Meta = meta
|
||||
override fun objectToMeta(obj: Meta): Meta = obj
|
||||
}
|
||||
|
||||
public val value: MetaConverter<Value> = object : MetaConverter<Value> {
|
||||
override fun itemToObject(item: MetaItem): Value = when (item) {
|
||||
is MetaItemNode -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value")
|
||||
is MetaItemValue -> item.value
|
||||
}
|
||||
|
||||
override fun objectToMetaItem(obj: Value): MetaItem = MetaItemValue(obj)
|
||||
override fun metaToObject(meta: Meta): Value? = meta.value
|
||||
override fun objectToMeta(obj: Value): Meta = Meta(obj)
|
||||
}
|
||||
|
||||
public val string: MetaConverter<String> = object : MetaConverter<String> {
|
||||
override fun itemToObject(item: MetaItem): String = when (item) {
|
||||
is MetaItemNode -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value")
|
||||
is MetaItemValue -> item.value
|
||||
}.string
|
||||
|
||||
override fun objectToMetaItem(obj: String): MetaItem = MetaItemValue(obj.asValue())
|
||||
override fun metaToObject(meta: Meta): String? = meta.string
|
||||
override fun objectToMeta(obj: String): Meta = Meta(obj.asValue())
|
||||
}
|
||||
|
||||
public val boolean: MetaConverter<Boolean> = object : MetaConverter<Boolean> {
|
||||
override fun itemToObject(item: MetaItem): Boolean = when (item) {
|
||||
is MetaItemNode -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value")
|
||||
is MetaItemValue -> item.value
|
||||
}.boolean
|
||||
|
||||
override fun objectToMetaItem(obj: Boolean): MetaItem = MetaItemValue(obj.asValue())
|
||||
override fun metaToObject(meta: Meta): Boolean? = meta.boolean
|
||||
override fun objectToMeta(obj: Boolean): Meta = Meta(obj.asValue())
|
||||
}
|
||||
|
||||
public val number: MetaConverter<Number> = object : MetaConverter<Number> {
|
||||
override fun itemToObject(item: MetaItem): Number = when (item) {
|
||||
is MetaItemNode -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value")
|
||||
is MetaItemValue -> item.value
|
||||
}.number
|
||||
|
||||
override fun objectToMetaItem(obj: Number): MetaItem = MetaItemValue(obj.asValue())
|
||||
override fun metaToObject(meta: Meta): Number? = meta.number
|
||||
override fun objectToMeta(obj: Number): Meta = Meta(obj.asValue())
|
||||
}
|
||||
|
||||
public val double: MetaConverter<Double> = object : MetaConverter<Double> {
|
||||
override fun itemToObject(item: MetaItem): Double = when (item) {
|
||||
is MetaItemNode -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value")
|
||||
is MetaItemValue -> item.value
|
||||
}.double
|
||||
override fun metaToObject(meta: Meta): Double? = meta.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> {
|
||||
override fun itemToObject(item: MetaItem): Float = when (item) {
|
||||
is MetaItemNode -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value")
|
||||
is MetaItemValue -> item.value
|
||||
}.float
|
||||
override fun metaToObject(meta: Meta): Float? = meta.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> {
|
||||
override fun itemToObject(item: MetaItem): Int = when (item) {
|
||||
is MetaItemNode -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value")
|
||||
is MetaItemValue -> item.value
|
||||
}.int
|
||||
override fun metaToObject(meta: Meta): Int? = meta.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> {
|
||||
override fun itemToObject(item: MetaItem): Long = when (item) {
|
||||
is MetaItemNode -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value")
|
||||
is MetaItemValue -> item.value
|
||||
}.long
|
||||
override fun metaToObject(meta: Meta): Long? = meta.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> {
|
||||
@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>> {
|
||||
override fun itemToObject(item: MetaItem): List<T> =
|
||||
item.value?.list?.map(reader) ?: error("The item is not a value list")
|
||||
override fun metaToObject(meta: Meta): List<T> =
|
||||
meta.value?.list?.map(reader) ?: error("The item is not a value list")
|
||||
|
||||
override fun objectToMetaItem(obj: List<T>): MetaItem =
|
||||
MetaItemValue(obj.map(writer).asValue())
|
||||
override fun objectToMeta(obj: List<T>): Meta = Meta(obj.map(writer).asValue())
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public fun <T : Any> MetaConverter<T>.nullableItemToObject(item: MetaItem?): T? = item?.let { itemToObject(it) }
|
||||
public fun <T : Any> MetaConverter<T>.nullableObjectToMetaItem(obj: T?): MetaItem? = obj?.let { objectToMetaItem(it) }
|
||||
public fun <T : Any> MetaConverter<T>.nullableMetaToObject(item: Meta?): T? = item?.let { metaToObject(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 = itemToObject(MetaItemValue(value))
|
||||
public fun <T> MetaConverter<T>.valueToObject(value: Value): T? = metaToObject(Meta(value))
|
||||
|
@ -13,7 +13,7 @@ public interface TransformationRule {
|
||||
/**
|
||||
* 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
|
||||
@ -26,7 +26,7 @@ public interface TransformationRule {
|
||||
/**
|
||||
* 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) :
|
||||
TransformationRule {
|
||||
override fun matches(name: Name, item: MetaItem?): Boolean {
|
||||
override fun matches(name: Name, item: Meta?): Boolean {
|
||||
return selector(name)
|
||||
}
|
||||
|
||||
override fun selectItems(meta: Meta): Sequence<Name> =
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -51,15 +51,15 @@ public data class KeepTransformationRule(val selector: (Name) -> Boolean) :
|
||||
*/
|
||||
public data class SingleItemTransformationRule(
|
||||
val from: Name,
|
||||
val transform: MutableTypedMeta.(Name, MetaItem?) -> Unit,
|
||||
val transform: MutableMeta.(Name, Meta?) -> Unit,
|
||||
) : TransformationRule {
|
||||
override fun matches(name: Name, item: MetaItem?): Boolean {
|
||||
override fun matches(name: Name, item: Meta?): Boolean {
|
||||
return name == 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) {
|
||||
target.transform(name, item)
|
||||
}
|
||||
@ -68,13 +68,13 @@ public data class SingleItemTransformationRule(
|
||||
|
||||
public data class RegexItemTransformationRule(
|
||||
val from: Regex,
|
||||
val transform: MutableTypedMeta.(name: Name, MatchResult, MetaItem?) -> Unit,
|
||||
val transform: MutableMeta.(name: Name, MatchResult, Meta?) -> Unit,
|
||||
) : TransformationRule {
|
||||
override fun matches(name: Name, item: MetaItem?): Boolean {
|
||||
override fun matches(name: Name, item: Meta?): Boolean {
|
||||
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())
|
||||
if (match != null) {
|
||||
target.transform(name, match, item)
|
||||
@ -105,7 +105,7 @@ public value class MetaTransformation(private val transformations: Collection<Tr
|
||||
* Generate an observable configuration that contains only elements defined by transformation rules and changes with the source
|
||||
*/
|
||||
@DFExperimental
|
||||
public fun generate(source: MutableMeta): ObservableMeta = MutableMeta().apply {
|
||||
public fun generate(source: ObservableMeta): ObservableMeta = MutableMeta().apply {
|
||||
transformations.forEach { rule ->
|
||||
rule.selectItems(source).forEach { name ->
|
||||
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.
|
||||
*/
|
||||
public fun bind(source: ObservableMeta, target: MutableTypedMeta) {
|
||||
source.onChange(target) { name, _, newItem ->
|
||||
public fun bind(source: ObservableMeta, target: MutableMeta) {
|
||||
source.onChange(target) { name ->
|
||||
val newItem = source[name]
|
||||
transformations.forEach { t ->
|
||||
if (t.matches(name, newItem)) {
|
||||
t.transformItem(name, newItem, target)
|
||||
@ -172,15 +173,15 @@ public class MetaTransformationBuilder {
|
||||
*/
|
||||
public fun keep(regex: String) {
|
||||
transformations.add(
|
||||
RegexItemTransformationRule(regex.toRegex()) { name, _, metaItem ->
|
||||
set(name, metaItem)
|
||||
RegexItemTransformationRule(regex.toRegex()) { name, _, Meta ->
|
||||
set(name, Meta)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(
|
||||
SingleItemTransformationRule(from) { _, item ->
|
||||
set(to, operation(item))
|
||||
|
@ -1,6 +1,7 @@
|
||||
package space.kscience.dataforge.values
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
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 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
|
||||
}
|
||||
|
||||
@ -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 Boolean.asValue(): Value = if (this) True else False
|
||||
|
@ -1,7 +1,6 @@
|
||||
package space.kscience.dataforge.values
|
||||
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.MutableMeta
|
||||
|
||||
/**
|
||||
* 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)
|
@ -1,7 +1,8 @@
|
||||
package space.kscience.dataforge.meta
|
||||
|
||||
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.assertEquals
|
||||
|
||||
@ -30,8 +31,8 @@ class JsonMetaTest {
|
||||
})
|
||||
}
|
||||
|
||||
val descriptor = NodeDescriptor{
|
||||
node("nodeArray"){
|
||||
val descriptor = MetaDescriptor {
|
||||
item("nodeArray") {
|
||||
indexKey = "index"
|
||||
}
|
||||
}
|
||||
@ -43,8 +44,17 @@ class JsonMetaTest {
|
||||
//println(meta)
|
||||
val reconstructed = meta.toJson(descriptor)
|
||||
println(reconstructed)
|
||||
assertEquals(2,
|
||||
reconstructed["nodeArray"]?.jsonArray?.get(1)?.jsonObject?.get("index")?.jsonPrimitive?.int)
|
||||
assertEquals(json,reconstructed)
|
||||
assertEquals(
|
||||
2,
|
||||
reconstructed
|
||||
.jsonObject["nodeArray"]
|
||||
?.jsonArray
|
||||
?.get(1)
|
||||
?.jsonObject
|
||||
?.get("index")
|
||||
?.jsonPrimitive
|
||||
?.int
|
||||
)
|
||||
assertEquals(json, reconstructed)
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package space.kscience.dataforge.meta
|
||||
|
||||
import space.kscience.dataforge.values.Value
|
||||
import space.kscience.dataforge.values.asValue
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
@ -10,7 +11,7 @@ class MetaBuilderTest {
|
||||
fun testBuilder() {
|
||||
val meta = Meta {
|
||||
"a" put 22
|
||||
"b" put listOf(1, 2, 3)
|
||||
"b" put Value.of(listOf(1, 2, 3))
|
||||
this["c"] = "myValue".asValue()
|
||||
"node" put {
|
||||
"e" put 12.2
|
||||
|
@ -6,7 +6,7 @@ import kotlin.test.assertEquals
|
||||
class MutableMetaTest{
|
||||
@Test
|
||||
fun testRemove(){
|
||||
val meta = Meta {
|
||||
val meta = MutableMeta {
|
||||
"aNode" put {
|
||||
"innerNode" put {
|
||||
"innerValue" put true
|
||||
|
@ -50,7 +50,7 @@ class SpecificationTest {
|
||||
@Test
|
||||
fun testChildModification() {
|
||||
val config = MutableMeta()
|
||||
val child = config.getChild("child")
|
||||
val child = config.getOrCreate("child")
|
||||
val scheme = TestScheme.write(child)
|
||||
scheme.a = 22
|
||||
scheme.b = "test"
|
||||
@ -61,7 +61,7 @@ class SpecificationTest {
|
||||
@Test
|
||||
fun testChildUpdate() {
|
||||
val config = MutableMeta()
|
||||
val child = config.getChild("child")
|
||||
val child = config.getOrCreate("child")
|
||||
child.update(TestScheme) {
|
||||
a = 22
|
||||
b = "test"
|
||||
|
@ -9,16 +9,14 @@ import kotlin.test.assertNotNull
|
||||
|
||||
class DescriptorTest {
|
||||
|
||||
val descriptor = NodeDescriptor {
|
||||
val descriptor = MetaDescriptor {
|
||||
node("aNode") {
|
||||
info = "A root demo node"
|
||||
value("b") {
|
||||
value("b", ValueType.NUMBER) {
|
||||
info = "b number value"
|
||||
type(ValueType.NUMBER)
|
||||
}
|
||||
node("otherNode") {
|
||||
value("otherValue") {
|
||||
type(ValueType.BOOLEAN)
|
||||
value("otherValue", ValueType.BOOLEAN) {
|
||||
default(false)
|
||||
info = "default value"
|
||||
}
|
||||
@ -30,13 +28,13 @@ class DescriptorTest {
|
||||
fun testAllowedValues() {
|
||||
val child = descriptor["aNode.b"]
|
||||
assertNotNull(child)
|
||||
val allowed = descriptor.nodes["aNode"]?.values?.get("b")?.allowedValues
|
||||
assertEquals(emptyList(), allowed)
|
||||
val allowed = descriptor["aNode"]?.get("b")?.allowedValues
|
||||
assertEquals(null, allowed)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDefaultMetaNode(){
|
||||
val meta = descriptor.defaultMeta
|
||||
fun testDefaultMetaNode() {
|
||||
val meta = descriptor.defaultNode
|
||||
assertEquals(false, meta["aNode.otherNode.otherValue"].boolean)
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
package space.kscience.dataforge.meta
|
||||
|
||||
import space.kscience.dataforge.names.NameToken
|
||||
import space.kscience.dataforge.values.Null
|
||||
import space.kscience.dataforge.values.Value
|
||||
import space.kscience.dataforge.values.asValue
|
||||
import space.kscience.dataforge.values.isList
|
||||
|
||||
|
||||
@ -22,11 +22,6 @@ public fun Value.toDynamic(): dynamic {
|
||||
public fun Meta.toDynamic(): dynamic {
|
||||
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("{}")
|
||||
this.items.entries.groupBy { it.key.body }.forEach { (key, value) ->
|
||||
val list = value.map { it.value }
|
||||
@ -38,46 +33,51 @@ public fun Meta.toDynamic(): dynamic {
|
||||
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 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 =
|
||||
(jsTypeOf(obj) != "object")
|
||||
|
||||
@Suppress("UNCHECKED_CAST", "USELESS_CAST")
|
||||
private fun asItem(obj: dynamic): TypedMetaItem<DynamicMeta>? {
|
||||
return when {
|
||||
obj == null -> MetaItemValue(Null)
|
||||
isArray(obj) && (obj as Array<Any?>).all { isPrimitive(it) } -> MetaItemValue(Value.of(obj as Array<Any?>))
|
||||
else -> when (jsTypeOf(obj)) {
|
||||
"boolean" -> MetaItemValue(Value.of(obj as Boolean))
|
||||
"number" -> MetaItemValue(Value.of(obj as Number))
|
||||
"string" -> MetaItemValue(Value.of(obj as String))
|
||||
"object" -> MetaItemNode(DynamicMeta(obj))
|
||||
else -> null
|
||||
}
|
||||
@Suppress("USELESS_CAST")
|
||||
override val value: Value?
|
||||
get() = if (isArray(obj) && (obj as Array<Any?>).all { isPrimitive(it) }) Value.of(obj as Array<Any?>)
|
||||
else when (jsTypeOf(obj)) {
|
||||
"boolean" -> (obj as Boolean).asValue()
|
||||
"number" -> (obj as Number).asValue()
|
||||
"string" -> (obj as String).asValue()
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
override val items: Map<NameToken, TypedMetaItem<DynamicMeta>>
|
||||
get() = keys().flatMap<String, Pair<NameToken, TypedMetaItem<DynamicMeta>>> { key ->
|
||||
override val items: Map<NameToken, Meta>
|
||||
get() = keys().flatMap<String, Pair<NameToken, Meta>> { key ->
|
||||
val value = obj[key] ?: return@flatMap emptyList()
|
||||
if (isArray(value)) {
|
||||
val array = value as Array<Any?>
|
||||
return@flatMap if (array.all { isPrimitive(it) }) {
|
||||
listOf(NameToken(key) to MetaItemValue(Value.of(array)))
|
||||
} else {
|
||||
array.mapIndexedNotNull { index, it ->
|
||||
val item = asItem(it) ?: return@mapIndexedNotNull null
|
||||
NameToken(key, index.toString()) to item
|
||||
when {
|
||||
isArray(value) -> {
|
||||
val array = value as Array<Any?>
|
||||
if (array.all { isPrimitive(it) }) {
|
||||
emptyList()
|
||||
} else {
|
||||
array.mapIndexedNotNull { index, it ->
|
||||
val item = DynamicMeta(it)
|
||||
NameToken(key, index.toString()) to item
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val item = asItem(value) ?: return@flatMap emptyList()
|
||||
listOf(NameToken(key) to item)
|
||||
isPrimitive(obj) -> {
|
||||
emptyList()
|
||||
}
|
||||
else -> {
|
||||
val item = DynamicMeta(value)
|
||||
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)
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package space.kscience.dataforge.meta
|
||||
|
||||
import space.kscience.dataforge.values.ListValue
|
||||
import space.kscience.dataforge.values.int
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
@ -21,7 +22,7 @@ class DynamicMetaTest {
|
||||
val meta = DynamicMeta(d)
|
||||
println(meta)
|
||||
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)
|
||||
}
|
||||
|
||||
@ -29,7 +30,7 @@ class DynamicMetaTest {
|
||||
fun testMetaToDynamic(){
|
||||
val meta = Meta {
|
||||
"a" put 22
|
||||
"array" put listOf(1, 2, 3)
|
||||
"array" put ListValue(1, 2, 3)
|
||||
"b" put "myString"
|
||||
"ob" put {
|
||||
"childNode" put 18
|
||||
|
@ -6,7 +6,7 @@ import space.kscience.dataforge.data.DataTree
|
||||
import space.kscience.dataforge.data.GoalExecutionRestriction
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
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.Type
|
||||
import space.kscience.dataforge.names.Name
|
||||
@ -47,11 +47,11 @@ public class TaskResultBuilder<T : Any>(
|
||||
@DFInternal
|
||||
public fun <T : Any> Task(
|
||||
resultType: KType,
|
||||
descriptor: ItemDescriptor? = null,
|
||||
descriptor: MetaDescriptor? = null,
|
||||
builder: suspend TaskResultBuilder<T>.() -> Unit,
|
||||
): Task<T> = object : Task<T> {
|
||||
|
||||
override val descriptor: ItemDescriptor? = descriptor
|
||||
override val descriptor: MetaDescriptor? = descriptor
|
||||
|
||||
override suspend fun execute(
|
||||
workspace: Workspace,
|
||||
@ -69,6 +69,6 @@ public fun <T : Any> Task(
|
||||
@OptIn(DFInternal::class)
|
||||
@Suppress("FunctionName")
|
||||
public inline fun <reified T : Any> Task(
|
||||
descriptor: ItemDescriptor? = null,
|
||||
descriptor: MetaDescriptor? = null,
|
||||
noinline builder: suspend TaskResultBuilder<T>.() -> Unit,
|
||||
): Task<T> = Task(typeOf<T>(), descriptor, builder)
|
@ -9,7 +9,8 @@ import space.kscience.dataforge.data.DataSetBuilder
|
||||
import space.kscience.dataforge.data.DataTree
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
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.DFExperimental
|
||||
import space.kscience.dataforge.names.Name
|
||||
@ -26,16 +27,16 @@ public interface TaskContainer {
|
||||
|
||||
public inline fun <reified T : Any> TaskContainer.registerTask(
|
||||
name: String,
|
||||
noinline descriptorBuilder: NodeDescriptor.() -> Unit = {},
|
||||
noinline descriptorBuilder: MetaDescriptorBuilder.() -> 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(
|
||||
noinline descriptorBuilder: NodeDescriptor.() -> Unit = {},
|
||||
noinline descriptorBuilder: MetaDescriptorBuilder.() -> Unit = {},
|
||||
noinline builder: suspend TaskResultBuilder<T>.() -> Unit,
|
||||
): PropertyDelegateProvider<Any?, ReadOnlyProperty<Any?, TaskReference<T>>> = PropertyDelegateProvider { _, property ->
|
||||
val taskName = property.name.toName()
|
||||
val task = Task(NodeDescriptor(descriptorBuilder), builder)
|
||||
val task = Task(MetaDescriptor(descriptorBuilder), builder)
|
||||
registerTask(taskName, task)
|
||||
ReadOnlyProperty { _, _ -> TaskReference(taskName, task) }
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ public suspend fun <T : Any> DataSetBuilder<T>.file(
|
||||
//otherwise, read as directory
|
||||
plugin.run {
|
||||
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", "")
|
||||
emit(name, data)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user