Cover corner cases in JsonMeta

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

View File

@ -10,6 +10,7 @@
- Kotlin 1.5.10
- 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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,7 +15,7 @@ import space.kscience.dataforge.names.toName
private class PartDescriptor : Scheme() {
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)
}
}

View File

@ -6,13 +6,11 @@ import space.kscience.dataforge.context.Factory
import space.kscience.dataforge.io.IOFormat.Companion.NAME_KEY
import space.kscience.dataforge.io.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")
// }
//}

View File

@ -6,7 +6,9 @@ import space.kscience.dataforge.io.IOFormat.Companion.META_KEY
import space.kscience.dataforge.io.IOFormat.Companion.NAME_KEY
import space.kscience.dataforge.io.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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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()
}
/**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,18 +0,0 @@
package space.kscience.dataforge.meta.descriptors
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.values.ValueType
import space.kscience.dataforge.values.asValue
public inline fun <reified E : Enum<E>> MetaDescriptorBuilder.enum(
key: Name,
default: E?,
crossinline modifier: MetaDescriptor.() -> Unit = {},
): Unit = value(key) {
type(ValueType.STRING)
default?.let {
default(default)
}
allowedValues = enumValues<E>().map { it.asValue() }
modifier()
}

View File

@ -1,124 +1,89 @@
package space.kscience.dataforge.meta.transformations
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))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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