diff --git a/README.md b/README.md index a6e6ed99..4bd6df9f 100644 --- a/README.md +++ b/README.md @@ -63,17 +63,17 @@ In this section we will try to cover DataForge main ideas in the form of questio * **Context encapsulation**. Every DataForge task is executed in some context. The context isolates environment for the task and also works as dependency injection base and specifies interaction of the task with the external world. -
+
-**Q:** Declarations and metadata are good, but I want my scripts back! +
@@ -88,12 +88,12 @@ In this section we will try to cover DataForge main ideas in the form of questio **Q:** How does DataForge compare to cluster computation frameworks like Hadoop or Spark? **A:** Again, it is not the purpose of DataForge to replace cluster software. DataForge has some internal parallelism mechanics and implementations, but they are most certainly worse then specially developed programs. Still, DataForge is not fixed on one single implementation. Your favourite parallel processing tool could be still used as a back-end for the DataForge. With full benefit of configuration tools, integrations and no performance overhead. - +
diff --git a/build.gradle.kts b/build.gradle.kts index dd159c0b..ac454902 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ plugins { id("org.jetbrains.dokka") version "0.10.1" } -val dataforgeVersion by extra("0.1.8-dev-4") +val dataforgeVersion by extra("0.1.8") val bintrayRepo by extra("dataforge") val githubProject by extra("dataforge-core") diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeFormat.kt index c634d696..9b819a97 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeFormat.kt @@ -16,7 +16,6 @@ import kotlin.reflect.KClass @ExperimentalUnsignedTypes data class PartialEnvelope(val meta: Meta, val dataOffset: UInt, val dataSize: ULong?) - interface EnvelopeFormat : IOFormat { val defaultMetaFormat: MetaFormatFactory get() = JsonMetaFormat @@ -33,6 +32,10 @@ interface EnvelopeFormat : IOFormat { override fun Output.writeObject(obj: Envelope): Unit = writeEnvelope(obj) } +fun EnvelopeFormat.readPartial(input: Input) = input.readPartial() + +fun EnvelopeFormat.read(input: Input) = input.readObject() + @Type(ENVELOPE_FORMAT_TYPE) interface EnvelopeFormatFactory : IOFormatFactory, EnvelopeFormat { override val name: Name get() = "envelope".asName() diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt index 17fbc589..ebcca243 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt @@ -166,6 +166,8 @@ class TaggedEnvelopeFormat( override fun Input.readObject(): Envelope = default.run { readObject() } + + } } \ No newline at end of file diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaFormatTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaFormatTest.kt index dcfc2265..a470cf0a 100644 --- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaFormatTest.kt +++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaFormatTest.kt @@ -1,6 +1,7 @@ package hep.dataforge.io import hep.dataforge.meta.* +import hep.dataforge.meta.JsonMeta.Companion.JSON_ARRAY_KEY import kotlinx.io.asBinary import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.json @@ -74,9 +75,9 @@ class MetaFormatTest { } val meta = json.toMetaItem().node!! - assertEquals(true, meta["@value[0].@value[1].d"].boolean) - assertEquals("value", meta["@value[1]"].string) - assertEquals(listOf(1.0, 2.0, 3.0), meta["@value[2"].value?.list?.map { it.number.toDouble() }) + 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() }) } } \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Configurable.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Configurable.kt index a078c37f..794292b9 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Configurable.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Configurable.kt @@ -2,8 +2,11 @@ package hep.dataforge.meta import hep.dataforge.meta.descriptors.* import hep.dataforge.names.Name +import hep.dataforge.names.asName import hep.dataforge.names.toName import hep.dataforge.values.Value +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty /** * A container that holds a [Config] and a default item provider. @@ -11,7 +14,7 @@ import hep.dataforge.values.Value * It is not possible to know if some property is declared by provider just by looking on [Configurable], * this information should be provided externally. */ -interface Configurable : Described { +interface Configurable : Described, MutableItemProvider { /** * Backing config */ @@ -35,15 +38,15 @@ interface Configurable : Described { /** * Get a property with default */ - fun getProperty(name: Name): MetaItem<*>? = + override fun getItem(name: Name): MetaItem<*>? = config[name] ?: getDefaultItem(name) ?: descriptor?.get(name)?.defaultItem() /** * Set a configurable property */ - fun setProperty(name: Name, item: MetaItem<*>?) { + override fun setItem(name: Name, item: MetaItem<*>?) { if (validateItem(name, item)) { - config[name] = item + config.setItem(name, item) } else { error("Validation failed for property $name with value $item") } @@ -54,22 +57,59 @@ interface Configurable : Described { * Reset the property to its default value */ fun Configurable.resetProperty(name: Name) { - setProperty(name, null) + setItem(name, null) } -fun Configurable.getProperty(key: String) = getProperty(key.toName()) +fun Configurable.getItem(key: String) = getItem(key.toName()) -fun Configurable.setProperty(name: Name, value: Value?) = setProperty(name, value?.let { MetaItem.ValueItem(value) }) -fun Configurable.setProperty(name: Name, meta: Meta?) = setProperty(name, meta?.let { MetaItem.NodeItem(meta) }) +fun Configurable.setItem(name: Name, value: Value?) = setItem(name, value?.let { MetaItem.ValueItem(value) }) +fun Configurable.setItem(name: Name, meta: Meta?) = setItem(name, meta?.let { MetaItem.NodeItem(meta) }) -fun Configurable.setProperty(key: String, item: MetaItem<*>?) { - setProperty(key.toName(), item) -} +fun Configurable.setItem(key: String, item: MetaItem<*>?) = setItem(key.toName(), item) -fun Configurable.setProperty(key: String, value: Value?) = setProperty(key, value?.let { MetaItem.ValueItem(value) }) -fun Configurable.setProperty(key: String, meta: Meta?) = setProperty(key, meta?.let { MetaItem.NodeItem(meta) }) +fun Configurable.setItem(key: String, value: Value?) = setItem(key, value?.let { MetaItem.ValueItem(value) }) +fun Configurable.setItem(key: String, meta: Meta?) = setItem(key, meta?.let { MetaItem.NodeItem(meta) }) fun T.configure(meta: Meta): T = this.apply { config.update(meta) } @DFBuilder inline fun T.configure(action: Config.() -> Unit): T = apply { config.apply(action) } + +/* Node delegates */ + +fun Configurable.config(key: Name? = null): ReadWriteProperty = + config.node(key) + +fun MutableItemProvider.node(key: Name? = null): ReadWriteProperty = item(key).convert( + reader = { it.node }, + writer = { it?.let { MetaItem.NodeItem(it) } } +) + +fun Configurable.spec( + spec: Specification, key: Name? = null +): ReadWriteProperty = object : ReadWriteProperty { + override fun getValue(thisRef: Any?, property: KProperty<*>): T? { + val name = key ?: property.name.asName() + return config[name].node?.let { spec.wrap(it) } + } + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) { + val name = key ?: property.name.asName() + config[name] = value?.config + } +} + +fun Configurable.spec( + spec: Specification, default: T, key: Name? = null +): ReadWriteProperty = object : ReadWriteProperty { + override fun getValue(thisRef: Any?, property: KProperty<*>): T { + val name = key ?: property.name.asName() + return config[name].node?.let { spec.wrap(it) } ?: default + } + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { + val name = key ?: property.name.asName() + config[name] = value.config + } +} + diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/ConfigurableDelegate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/ConfigurableDelegate.kt deleted file mode 100644 index a94b3667..00000000 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/ConfigurableDelegate.kt +++ /dev/null @@ -1,239 +0,0 @@ -package hep.dataforge.meta - -import hep.dataforge.names.Name -import hep.dataforge.names.asName -import hep.dataforge.values.* -import kotlin.jvm.JvmName -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KProperty - - -//delegates - -/** - * A delegate that uses a [Configurable] object and delegate read and write operations to its properties - */ -open class ConfigurableDelegate( - val owner: Configurable, - val key: Name? = null, - open val default: MetaItem<*>? = null -) : ReadWriteProperty?> { - - override fun getValue(thisRef: Any?, property: KProperty<*>): MetaItem<*>? { - val name = key ?: property.name.asName() - return owner.getProperty(name) ?: default - } - - override fun setValue(thisRef: Any?, property: KProperty<*>, value: MetaItem<*>?) { - val name = key ?: property.name.asName() - owner.setProperty(name, value) - } -} - -class LazyConfigurableDelegate( - configurable: Configurable, - key: Name? = null, - defaultProvider: () -> MetaItem<*>? = { null } -) : ConfigurableDelegate(configurable, key) { - override val default by lazy(defaultProvider) -} - -/** - * A property delegate that uses custom key - */ -fun Configurable.item(default: Any? = null, key: Name? = null): ConfigurableDelegate = - ConfigurableDelegate( - this, - key, - default?.let { MetaItem.of(it) }) - -/** - * Generation of item delegate with lazy default. - * Lazy default could be used also for validation - */ -fun Configurable.lazyItem(key: Name? = null, default: () -> Any?): ConfigurableDelegate = - LazyConfigurableDelegate(this, key) { - default()?.let { - MetaItem.of(it) - } - } - -fun Configurable.item( - default: T? = null, - key: Name? = null, - writer: (T) -> MetaItem<*>? = { - MetaItem.of(it) - }, - reader: (MetaItem<*>?) -> T -): ReadWriteProperty = - ConfigurableDelegate( - this, - key, - default?.let { MetaItem.of(it) }).map(reader = reader, writer = writer) - -fun Configurable.value(default: Any? = null, key: Name? = null): ReadWriteProperty = - item(default, key).transform { it.value } - -fun Configurable.value( - default: T? = null, - key: Name? = null, - writer: (T) -> Value? = { Value.of(it) }, - reader: (Value?) -> T -): ReadWriteProperty = - ConfigurableDelegate( - this, - key, - default?.let { MetaItem.of(it) } - ).map( - reader = { reader(it.value) }, - writer = { value -> writer(value)?.let { MetaItem.ValueItem(it) } } - ) - -fun Configurable.string(default: String? = null, key: Name? = null): ReadWriteProperty = - item(default, key).transform { it.value?.string } - -fun Configurable.boolean(default: Boolean? = null, key: Name? = null): ReadWriteProperty = - item(default, key).transform { it.value?.boolean } - -fun Configurable.number(default: Number? = null, key: Name? = null): ReadWriteProperty = - item(default, key).transform { it.value?.number } - -/* Number delegates*/ - -fun Configurable.int(default: Int? = null, key: Name? = null): ReadWriteProperty = - item(default, key).transform { it.value?.int } - -fun Configurable.double(default: Double? = null, key: Name? = null): ReadWriteProperty = - item(default, key).transform { it.value?.double } - -fun Configurable.long(default: Long? = null, key: Name? = null): ReadWriteProperty = - item(default, key).transform { it.value?.long } - -fun Configurable.short(default: Short? = null, key: Name? = null): ReadWriteProperty = - item(default, key).transform { it.value?.short } - -fun Configurable.float(default: Float? = null, key: Name? = null): ReadWriteProperty = - item(default, key).transform { it.value?.float } - - -@JvmName("safeString") -fun Configurable.string(default: String, key: Name? = null): ReadWriteProperty = - item(default, key).transform { it.value!!.string } - -@JvmName("safeBoolean") -fun Configurable.boolean(default: Boolean, key: Name? = null): ReadWriteProperty = - item(default, key).transform { it.value!!.boolean } - -@JvmName("safeNumber") -fun Configurable.number(default: Number, key: Name? = null): ReadWriteProperty = - item(default, key).transform { it.value!!.number } - -/* Lazy initializers for values */ - -@JvmName("lazyString") -fun Configurable.string(key: Name? = null, default: () -> String): ReadWriteProperty = - lazyItem(key, default).transform { it.value!!.string } - -@JvmName("lazyBoolean") -fun Configurable.boolean(key: Name? = null, default: () -> Boolean): ReadWriteProperty = - lazyItem(key, default).transform { it.value!!.boolean } - -@JvmName("lazyNumber") -fun Configurable.number(key: Name? = null, default: () -> Number): ReadWriteProperty = - lazyItem(key, default).transform { it.value!!.number } - -/* Safe number delegates*/ - -@JvmName("safeInt") -fun Configurable.int(default: Int, key: Name? = null): ReadWriteProperty = - item(default, key).transform { it.value!!.int } - -@JvmName("safeDouble") -fun Configurable.double(default: Double, key: Name? = null): ReadWriteProperty = - item(default, key).transform { it.value!!.double } - -@JvmName("safeLong") -fun Configurable.long(default: Long, key: Name? = null): ReadWriteProperty = - item(default, key).transform { it.value!!.long } - -@JvmName("safeShort") -fun Configurable.short(default: Short, key: Name? = null): ReadWriteProperty = - item(default, key).transform { it.value!!.short } - -@JvmName("safeFloat") -fun Configurable.float(default: Float, key: Name? = null): ReadWriteProperty = - item(default, key).transform { it.value!!.float } - -/** - * Enum delegate - */ -inline fun > Configurable.enum( - default: E, key: Name? = null -): ReadWriteProperty = - item(default, key).transform { item -> item?.string?.let {str-> - @Suppress("USELESS_CAST") - enumValueOf(str) as E - } ?: default } - -/* - * Extra delegates for special cases - */ -fun Configurable.stringList(vararg strings: String, key: Name? = null): ReadWriteProperty> = - item(listOf(*strings), key) { - it?.value?.stringList ?: emptyList() - } - -fun Configurable.numberList(vararg numbers: Number, key: Name? = null): ReadWriteProperty> = - item(listOf(*numbers), key) { item -> - item?.value?.list?.map { it.number } ?: emptyList() - } - -/** - * A special delegate for double arrays - */ -fun Configurable.doubleArray(vararg doubles: Double, key: Name? = null): ReadWriteProperty = - item(doubleArrayOf(*doubles), key) { - (it.value as? DoubleArrayValue)?.value - ?: it?.value?.list?.map { value -> value.number.toDouble() }?.toDoubleArray() - ?: doubleArrayOf() - } - - -/* Node delegates */ - -fun Configurable.config(default: Config? = null, key: Name? = null): ReadWriteProperty = - config.node(default,key) - -fun Configurable.node(key: Name? = null): ReadWriteProperty = item(key).map( - reader = { it.node }, - writer = { it?.let { MetaItem.NodeItem(it) } } -) - -fun Configurable.spec( - spec: Specification, key: Name? = null -): ReadWriteProperty = object : ReadWriteProperty { - override fun getValue(thisRef: Any?, property: KProperty<*>): T? { - val name = key ?: property.name.asName() - return config[name].node?.let { spec.wrap(it) } - } - - override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) { - val name = key ?: property.name.asName() - config[name] = value?.config - } -} - -fun Configurable.spec( - spec: Specification, default: T, key: Name? = null -): ReadWriteProperty = object : ReadWriteProperty { - override fun getValue(thisRef: Any?, property: KProperty<*>): T { - val name = key ?: property.name.asName() - return config[name].node?.let { spec.wrap(it) } ?: default - } - - override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { - val name = key ?: property.name.asName() - config[name] = value.config - } -} - diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/ItemDelegate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/ItemDelegate.kt new file mode 100644 index 00000000..aa2dec23 --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/ItemDelegate.kt @@ -0,0 +1,66 @@ +package hep.dataforge.meta + +import hep.dataforge.meta.transformations.MetaConverter +import hep.dataforge.names.Name +import hep.dataforge.names.asName +import hep.dataforge.values.Value +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +/* Meta delegates */ + +open class ItemDelegate( + open val owner: ItemProvider, + val key: Name? = null, + open val default: MetaItem<*>? = null +) : ReadOnlyProperty?> { + override fun getValue(thisRef: Any?, property: KProperty<*>): MetaItem<*>? { + return owner.getItem(key ?: property.name.asName()) ?: default + } +} + +fun ItemProvider.item(key: Name? = null): ItemDelegate = ItemDelegate(this, key) + +//TODO add caching for sealed nodes + + +//Read-only delegates for Metas + +/** + * A property delegate that uses custom key + */ +fun ItemProvider.value(key: Name? = null): ReadOnlyProperty = + item(key).convert(MetaConverter.value) + +fun ItemProvider.string(key: Name? = null): ReadOnlyProperty = + item(key).convert(MetaConverter.string) + +fun ItemProvider.boolean(key: Name? = null): ReadOnlyProperty = + item(key).convert(MetaConverter.boolean) + +fun ItemProvider.number(key: Name? = null): ReadOnlyProperty = + item(key).convert(MetaConverter.number) + +fun ItemProvider.node(key: Name? = null): ReadOnlyProperty = + item(key).convert(MetaConverter.meta) + +fun ItemProvider.string(default: String, key: Name? = null): ReadOnlyProperty = + item(key).convert(MetaConverter.string) { default } + +fun ItemProvider.boolean(default: Boolean, key: Name? = null): ReadOnlyProperty = + item(key).convert(MetaConverter.boolean) { default } + +fun ItemProvider.number(default: Number, key: Name? = null): ReadOnlyProperty = + item(key).convert(MetaConverter.number) { default } + +inline fun > ItemProvider.enum(default: E, key: Name? = null): ReadOnlyProperty = + item(key).convert(MetaConverter.enum()) { default } + +fun ItemProvider.string(key: Name? = null, default: () -> String): ReadOnlyProperty = + item(key).convert(MetaConverter.string, default) + +fun ItemProvider.boolean(key: Name? = null, default: () -> Boolean): ReadOnlyProperty = + item(key).convert(MetaConverter.boolean, default) + +fun ItemProvider.number(key: Name? = null, default: () -> Number): ReadOnlyProperty = + item(key).convert(MetaConverter.number, default) diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/JsonMeta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/JsonMeta.kt index 48ef6c4e..221b040d 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/JsonMeta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/JsonMeta.kt @@ -2,11 +2,12 @@ package hep.dataforge.meta +import hep.dataforge.meta.JsonMeta.Companion.JSON_ARRAY_KEY import hep.dataforge.meta.descriptors.ItemDescriptor import hep.dataforge.meta.descriptors.NodeDescriptor import hep.dataforge.meta.descriptors.ValueDescriptor import hep.dataforge.names.NameToken -import hep.dataforge.names.toName +import hep.dataforge.names.withIndex import hep.dataforge.values.* import kotlinx.serialization.json.* @@ -35,29 +36,29 @@ private fun String.toJsonKey(descriptor: ItemDescriptor?) = descriptor?.attribut /** * Convert given [Meta] to [JsonObject]. Primitives and nodes are copied as is, same name siblings are treated as json arrays */ -fun Meta.toJson(descriptor: NodeDescriptor? = null, index: String? = null): JsonObject { +private fun Meta.toJsonWithIndex(descriptor: NodeDescriptor?, indexValue: String?): JsonObject { val elementMap = HashMap() - fun MetaItem<*>.toJsonElement(itemDescriptor: ItemDescriptor?, index: String? = null): JsonElement = when (this) { + fun MetaItem<*>.toJsonElement(itemDescriptor: ItemDescriptor?, index: String?): JsonElement = when (this) { is MetaItem.ValueItem -> { value.toJson(itemDescriptor as? ValueDescriptor) } is MetaItem.NodeItem -> { - node.toJson(itemDescriptor as? NodeDescriptor, index) + node.toJsonWithIndex(itemDescriptor as? NodeDescriptor, index) } } fun addElement(key: String) { val itemDescriptor = descriptor?.items?.get(key) val jsonKey = key.toJsonKey(itemDescriptor) - val items = getIndexed(key) + val items: Map> = getIndexed(key) when (items.size) { 0 -> { //do nothing } 1 -> { - elementMap[jsonKey] = items.values.first().toJsonElement(itemDescriptor) + elementMap[jsonKey] = items.values.first().toJsonElement(itemDescriptor, null) } else -> { val array = jsonArray { @@ -73,38 +74,29 @@ fun Meta.toJson(descriptor: NodeDescriptor? = null, index: String? = null): Json ((descriptor?.items?.keys ?: emptySet()) + items.keys.map { it.body }).forEach(::addElement) - if (index != null) { - elementMap["@index"] = JsonPrimitive(index) + if (indexValue != null) { + val indexKey = descriptor?.indexKey ?: NodeDescriptor.DEFAULT_INDEX_KEY + elementMap[indexKey] = JsonPrimitive(indexValue) } return JsonObject(elementMap) - -// // use descriptor keys in the order they are declared -// val keys = (descriptor?.items?.keys ?: emptySet()) + this.items.keys.map { it.body } -// -// //TODO search for same name siblings and arrange them into arrays -// val map = this.items.entries.associate { (name, item) -> -// val itemDescriptor = descriptor?.items?.get(name.body) -// val key = name.toJsonKey(itemDescriptor) -// val value = when (item) { -// is MetaItem.ValueItem -> { -// item.value.toJson(itemDescriptor as? ValueDescriptor) -// } -// is MetaItem.NodeItem -> { -// item.node.toJson(itemDescriptor as? NodeDescriptor) -// } -// } -// key to value -// } -// return JsonObject(map) } -fun JsonObject.toMeta(descriptor: NodeDescriptor? = null): Meta = JsonMeta(this, descriptor) +fun Meta.toJson(descriptor: NodeDescriptor? = null): JsonObject = toJsonWithIndex(descriptor, null) + +fun JsonObject.toMeta(descriptor: NodeDescriptor? = null): JsonMeta = JsonMeta(this, descriptor) fun JsonPrimitive.toValue(descriptor: ValueDescriptor?): Value { return when (this) { JsonNull -> Null - else -> this.content.parseValue() // Optimize number and boolean parsing + is JsonLiteral -> { + when (body) { + true -> True + false -> False + is Number -> NumberValue(body as Number) + else -> StringValue(content) + } + } } } @@ -122,18 +114,15 @@ fun JsonElement.toMetaItem(descriptor: ItemDescriptor? = null): MetaItem { - //We already checked that all values are primitives - (it as JsonPrimitive).toValue(descriptor as? ValueDescriptor) - } - ) + map { + //We already checked that all values are primitives + (it as JsonPrimitive).toValue(descriptor as? ValueDescriptor) + }.asValue() } MetaItem.ValueItem(value) } else { - json { - "@value" to this@toMetaItem - }.toMetaItem(descriptor) + //We can't return multiple items therefore we create top level node + json { JSON_ARRAY_KEY to this@toMetaItem }.toMetaItem(descriptor) } } } @@ -143,44 +132,52 @@ fun JsonElement.toMetaItem(descriptor: ItemDescriptor? = null): MetaItem>.set(key: String, value: JsonElement): Unit { - val itemDescriptor = descriptor?.items?.get(key) - when (value) { - is JsonPrimitive -> { - this[key] = - MetaItem.ValueItem(value.toValue(itemDescriptor as? ValueDescriptor)) as MetaItem - } - is JsonObject -> { - this[key] = MetaItem.NodeItem( - JsonMeta( - value, - itemDescriptor as? NodeDescriptor - ) - ) - } - is JsonArray -> { - when { - value.all { it is JsonPrimitive } -> { - val listValue = ListValue( - value.map { - //We already checked that all values are primitives - (it as JsonPrimitive).toValue(itemDescriptor as? ValueDescriptor) - } + private fun buildItems(): Map> { + val map = LinkedHashMap>() + + json.forEach { (jsonKey, value) -> + val key = NameToken(jsonKey) + val itemDescriptor = descriptor?.items?.get(jsonKey) + when (value) { + is JsonPrimitive -> { + map[key] = MetaItem.ValueItem(value.toValue(itemDescriptor as? ValueDescriptor)) + } + is JsonObject -> { + map[key] = MetaItem.NodeItem( + JsonMeta( + value, + itemDescriptor as? NodeDescriptor ) - this[key] = MetaItem.ValueItem(listValue) as MetaItem - } - else -> value.forEachIndexed { index, jsonElement -> - this["$key[$index]"] = jsonElement.toMetaItem(itemDescriptor) - } + ) + } + is JsonArray -> if (value.all { it is JsonPrimitive }) { + val listValue = ListValue( + value.map { + //We already checked that all values are primitives + (it as JsonPrimitive).toValue(itemDescriptor as? ValueDescriptor) + } + ) + map[key] = MetaItem.ValueItem(listValue) + } else value.forEachIndexed { index, jsonElement -> + val indexKey = (itemDescriptor as? NodeDescriptor)?.indexKey ?: NodeDescriptor.DEFAULT_INDEX_KEY + val indexValue: String = (jsonElement as? JsonObject) + ?.get(indexKey)?.contentOrNull + ?: index.toString() //In case index is non-string, the backward transformation will be broken. + + val token = key.withIndex(indexValue) + map[token] = jsonElement.toMetaItem(itemDescriptor) } } } + return map } - override val items: Map> by lazy { - val map = LinkedHashMap>() - json.forEach { (key, value) -> map[key] = value } - map.mapKeys { it.key.toName().first()!! } + override val items: Map> by lazy(::buildItems) + + companion object{ + /** + * A key representing top-level json array of nodes, which could not be directly represented by a meta node + */ + const val JSON_ARRAY_KEY = "@jsonArray" } } \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt index f0e3725f..a89d79c9 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt @@ -85,6 +85,10 @@ interface MetaRepr { fun toMeta(): Meta } +interface ItemProvider{ + fun getItem(name: Name): MetaItem<*>? +} + /** * Generic meta tree representation. Elements are [MetaItem] objects that could be represented by three different entities: * * [MetaItem.ValueItem] (leaf) @@ -92,12 +96,23 @@ interface MetaRepr { * * * Same name siblings are supported via elements with the same [Name] but different queries */ -interface Meta : MetaRepr { +interface Meta : MetaRepr, ItemProvider { /** * Top level items of meta tree */ val items: Map> + override fun getItem(name: Name): MetaItem<*>? { + if (name.isEmpty()) return NodeItem(this) + return name.first()?.let { token -> + val tail = name.cutFirst() + when (tail.length) { + 0 -> items[token] + else -> items[token]?.node?.get(tail) + } + } + } + override fun toMeta(): Meta = seal() override fun equals(other: Any?): Boolean @@ -127,17 +142,7 @@ interface Meta : MetaRepr { * * If [name] is empty return current [Meta] as a [NodeItem] */ -operator fun Meta?.get(name: Name): MetaItem<*>? { - if (this == null) return null - if (name.isEmpty()) return NodeItem(this) - return name.first()?.let { token -> - val tail = name.cutFirst() - when (tail.length) { - 0 -> items[token] - else -> items[token]?.node?.get(tail) - } - } -} +operator fun Meta?.get(name: Name): MetaItem<*>? = this?.getItem(name) operator fun Meta?.get(token: NameToken): MetaItem<*>? = this?.items?.get(token) diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaDelegate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaDelegate.kt deleted file mode 100644 index 50764a7c..00000000 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaDelegate.kt +++ /dev/null @@ -1,98 +0,0 @@ -package hep.dataforge.meta - -import hep.dataforge.names.Name -import hep.dataforge.names.asName -import hep.dataforge.values.Value -import kotlin.jvm.JvmName -import kotlin.properties.ReadOnlyProperty -import kotlin.reflect.KProperty - -/* Meta delegates */ - -open class MetaDelegate( - open val owner: Meta, - val key: Name? = null, - open val default: MetaItem<*>? = null -) : ReadOnlyProperty?> { - override fun getValue(thisRef: Any?, property: KProperty<*>): MetaItem<*>? { - return owner[key ?: property.name.asName()] ?: default - } -} - -class LazyMetaDelegate( - owner: Meta, - key: Name? = null, - defaultProvider: () -> MetaItem<*>? = { null } -) : MetaDelegate(owner, key) { - override val default by lazy(defaultProvider) -} - -class DelegateWrapper( - val delegate: ReadOnlyProperty, - val reader: (T) -> R -) : ReadOnlyProperty { - override fun getValue(thisRef: Any?, property: KProperty<*>): R { - return reader(delegate.getValue(thisRef, property)) - } -} - -fun ReadOnlyProperty.map(reader: (T) -> R): DelegateWrapper = - DelegateWrapper(this, reader) - - -fun Meta.item(default: Any? = null, key: Name? = null): MetaDelegate = - MetaDelegate(this, key, default?.let { MetaItem.of(it) }) - -fun Meta.lazyItem(key: Name? = null, defaultProvider: () -> Any?): LazyMetaDelegate = - LazyMetaDelegate(this, key) { defaultProvider()?.let { MetaItem.of(it) } } - -//TODO add caching for sealed nodes - - -//Read-only delegates for Metas - -/** - * A property delegate that uses custom key - */ -fun Meta.value(default: Value? = null, key: Name? = null) = - item(default, key).map { it.value } - -fun Meta.string(default: String? = null, key: Name? = null) = - item(default, key).map { it.string } - -fun Meta.boolean(default: Boolean? = null, key: Name? = null) = - item(default, key).map { it.boolean } - -fun Meta.number(default: Number? = null, key: Name? = null) = - item(default, key).map { it.number } - -fun Meta.node(key: Name? = null) = - item(key).map { it.node } - -@JvmName("safeString") -fun Meta.string(default: String, key: Name? = null) = - item(default, key).map { it.string!! } - -@JvmName("safeBoolean") -fun Meta.boolean(default: Boolean, key: Name? = null) = - item(default, key).map { it.boolean!! } - -@JvmName("safeNumber") -fun Meta.number(default: Number, key: Name? = null) = - item(default, key).map { it.number!! } - -@JvmName("lazyString") -fun Meta.string(key: Name? = null, default: () -> String) = - lazyItem(key, default).map { it.string!! } - -@JvmName("lazyBoolean") -fun Meta.boolean(key: Name? = null, default: () -> Boolean) = - lazyItem(key, default).map { it.boolean!! } - -@JvmName("lazyNumber") -fun Meta.number(key: Name? = null, default: () -> Number) = - lazyItem(key, default).map { it.number!! } - - -inline fun > Meta.enum(default: E, key: Name? = null) = - item(default, key).map { it.enum()!! } \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableItemDelegate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableItemDelegate.kt new file mode 100644 index 00000000..3885b809 --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableItemDelegate.kt @@ -0,0 +1,167 @@ +package hep.dataforge.meta + +import hep.dataforge.meta.transformations.MetaConverter +import hep.dataforge.names.Name +import hep.dataforge.names.asName +import hep.dataforge.values.DoubleArrayValue +import hep.dataforge.values.Value +import hep.dataforge.values.stringList +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + +/* Read-write delegates */ + +open class MutableItemDelegate( + override val owner: MutableItemProvider, + key: Name? = null, + default: MetaItem<*>? = null +) : ItemDelegate(owner, key, default), ReadWriteProperty?> { + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: MetaItem<*>?) { + val name = key ?: property.name.asName() + owner.setItem(name, value) + } +} + +fun MutableItemProvider.item(key: Name? = null): MutableItemDelegate = + MutableItemDelegate(this, key) + +//Read-write delegates + +/** + * A property delegate that uses custom key + */ +fun MutableItemProvider.value(key: Name? = null): ReadWriteProperty = + item(key).convert(MetaConverter.value) + +fun MutableItemProvider.string(key: Name? = null): ReadWriteProperty = + item(key).convert(MetaConverter.string) + +fun MutableItemProvider.boolean(key: Name? = null): ReadWriteProperty = + item(key).convert(MetaConverter.boolean) + +fun MutableItemProvider.number(key: Name? = null): ReadWriteProperty = + item(key).convert(MetaConverter.number) + +fun MutableItemProvider.string(default: String, key: Name? = null): ReadWriteProperty = + item(key).convert(MetaConverter.string) { default } + +fun MutableItemProvider.boolean(default: Boolean, key: Name? = null): ReadWriteProperty = + item(key).convert(MetaConverter.boolean) { default } + +fun MutableItemProvider.number(default: Number, key: Name? = null): ReadWriteProperty = + item(key).convert(MetaConverter.number) { default } + +fun MutableItemProvider.value(key: Name? = null, default: () -> Value): ReadWriteProperty = + item(key).convert(MetaConverter.value, default) + +fun MutableItemProvider.string(key: Name? = null, default: () -> String): ReadWriteProperty = + item(key).convert(MetaConverter.string, default) + +fun MutableItemProvider.boolean(key: Name? = null, default: () -> Boolean): ReadWriteProperty = + item(key).convert(MetaConverter.boolean, default) + +fun MutableItemProvider.number(key: Name? = null, default: () -> Number): ReadWriteProperty = + item(key).convert(MetaConverter.number, default) + +inline fun > MutableItemProvider.enum(default: E, key: Name? = null): ReadWriteProperty = + item(key).convert(MetaConverter.enum()) { default } + +inline fun > M.node(key: Name? = null): ReadWriteProperty = + item(key).convert(reader = { it?.let { it.node as M } }, writer = { it?.let { MetaItem.NodeItem(it) } }) + + +fun MutableItemProvider.item( + default: T? = null, + key: Name? = null, + writer: (T) -> MetaItem<*>? = { MetaItem.of(it) }, + reader: (MetaItem<*>?) -> T +): ReadWriteProperty = MutableItemDelegate( + this, + key, + default?.let { MetaItem.of(it) } +).convert(reader = reader, writer = writer) + +fun Configurable.value(key: Name? = null): ReadWriteProperty = + item(key).convert(MetaConverter.value) + +fun MutableItemProvider.value( + default: T? = null, + key: Name? = null, + writer: (T) -> Value? = { Value.of(it) }, + reader: (Value?) -> T +): ReadWriteProperty = MutableItemDelegate( + this, + key, + default?.let { MetaItem.of(it) } +).convert( + reader = { reader(it.value) }, + writer = { value -> writer(value)?.let { MetaItem.ValueItem(it) } } +) + +/* Number delegates*/ + +fun MutableItemProvider.int(key: Name? = null): ReadWriteProperty = + item(key).convert(MetaConverter.int) + +fun MutableItemProvider.double(key: Name? = null): ReadWriteProperty = + item(key).convert(MetaConverter.double) + +fun MutableItemProvider.long(key: Name? = null): ReadWriteProperty = + item(key).convert(MetaConverter.long) + +fun MutableItemProvider.float(key: Name? = null): ReadWriteProperty = + item(key).convert(MetaConverter.float) + + +/* Safe number delegates*/ + +fun MutableItemProvider.int(default: Int, key: Name? = null): ReadWriteProperty = + item(key).convert(MetaConverter.int) { default } + +fun MutableItemProvider.double(default: Double, key: Name? = null): ReadWriteProperty = + item(key).convert(MetaConverter.double) { default } + +fun MutableItemProvider.long(default: Long, key: Name? = null): ReadWriteProperty = + item(key).convert(MetaConverter.long) { default } + +fun MutableItemProvider.float(default: Float, key: Name? = null): ReadWriteProperty = + item(key).convert(MetaConverter.float) { default } + + +/* + * Extra delegates for special cases + */ +fun MutableItemProvider.stringList(vararg strings: String, key: Name? = null): ReadWriteProperty> = + item(listOf(*strings), key) { + it?.value?.stringList ?: emptyList() + } + +fun MutableItemProvider.stringListOrNull( + vararg strings: String, + key: Name? = null +): ReadWriteProperty?> = + item(listOf(*strings), key) { + it?.value?.stringList + } + +fun MutableItemProvider.numberList(vararg numbers: Number, key: Name? = null): ReadWriteProperty> = + item(listOf(*numbers), key) { item -> + item?.value?.list?.map { it.number } ?: emptyList() + } + +/** + * A special delegate for double arrays + */ +fun MutableItemProvider.doubleArray(vararg doubles: Double, key: Name? = null): ReadWriteProperty = + item(doubleArrayOf(*doubles), key) { + (it.value as? DoubleArrayValue)?.value + ?: it?.value?.list?.map { value -> value.number.toDouble() }?.toDoubleArray() + ?: doubleArrayOf() + } + +fun MutableItemProvider.listValue( + key: Name? = null, + writer: (T) -> Value = { Value.of(it) }, + reader: (Value) -> T +): ReadWriteProperty?> = item(key).convert(MetaConverter.valueList(writer, reader)) diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt index 16fcf40d..03245b00 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt @@ -3,9 +3,12 @@ package hep.dataforge.meta import hep.dataforge.names.* import hep.dataforge.values.Value -interface MutableMeta> : MetaNode { +interface MutableItemProvider : ItemProvider { + fun setItem(name: Name, item: MetaItem<*>?) +} + +interface MutableMeta> : MetaNode, MutableItemProvider { override val items: Map> - operator fun set(name: Name, item: MetaItem<*>?) // fun onChange(owner: Any? = null, action: (Name, MetaItem<*>?, MetaItem<*>?) -> Unit) // fun removeListener(owner: Any? = null) } @@ -49,7 +52,7 @@ abstract class AbstractMutableMeta> : AbstractMetaNode(), */ internal abstract fun empty(): M - override operator fun set(name: Name, item: MetaItem<*>?) { + override fun setItem(name: Name, item: MetaItem<*>?) { when (name.length) { 0 -> error("Can't setValue meta item for empty name") 1 -> { @@ -63,7 +66,7 @@ abstract class AbstractMutableMeta> : AbstractMetaNode(), if (items[token] == null) { replaceItem(token, null, MetaItem.NodeItem(empty())) } - items[token]?.node!![name.cutFirst()] = item + items[token]?.node!!.setItem(name.cutFirst(), item) } } } @@ -71,27 +74,21 @@ abstract class AbstractMutableMeta> : AbstractMetaNode(), @Suppress("NOTHING_TO_INLINE") -inline fun MutableMeta<*>.remove(name: Name) = set(name, null) +inline fun MutableMeta<*>.remove(name: Name) = setItem(name, null) @Suppress("NOTHING_TO_INLINE") inline fun MutableMeta<*>.remove(name: String) = remove(name.toName()) -fun MutableMeta<*>.setValue(name: Name, value: Value) = set(name, MetaItem.ValueItem(value)) +operator fun MutableMeta<*>.set(name: Name, item: MetaItem<*>?) = setItem(name, item) + +fun MutableMeta<*>.setValue(name: Name, value: Value) = setItem(name, MetaItem.ValueItem(value)) fun MutableMeta<*>.setValue(name: String, value: Value) = set(name.toName(), value) -fun MutableMeta<*>.setItem(name: Name, item: MetaItem<*>?) { - when (item) { - null -> remove(name) - is MetaItem.ValueItem -> setValue(name, item.value) - is MetaItem.NodeItem<*> -> setNode(name, item.node) - } -} - fun MutableMeta<*>.setItem(name: String, item: MetaItem<*>?) = setItem(name.toName(), item) fun MutableMeta<*>.setNode(name: Name, node: Meta) = - set(name, MetaItem.NodeItem(node)) + setItem(name, MetaItem.NodeItem(node)) fun MutableMeta<*>.setNode(name: String, node: Meta) = setNode(name.toName(), node) @@ -135,23 +132,23 @@ fun > M.update(meta: Meta) { fun MutableMeta<*>.setIndexedItems( name: Name, items: Iterable>, - indexFactory: MetaItem<*>.(index: Int) -> String = { it.toString() } + indexFactory: (MetaItem<*>, index: Int) -> String = { _, index -> index.toString() } ) { val tokens = name.tokens.toMutableList() val last = tokens.last() items.forEachIndexed { index, meta -> - val indexedToken = NameToken(last.body, last.index + meta.indexFactory(index)) + val indexedToken = NameToken(last.body, last.index + indexFactory(meta, index)) tokens[tokens.lastIndex] = indexedToken - set(Name(tokens), meta) + setItem(Name(tokens), meta) } } fun MutableMeta<*>.setIndexed( name: Name, metas: Iterable, - indexFactory: MetaItem<*>.(index: Int) -> String = { it.toString() } + indexFactory: (Meta, index: Int) -> String = { _, index -> index.toString() } ) { - setIndexedItems(name, metas.map { MetaItem.NodeItem(it) }, indexFactory) + setIndexedItems(name, metas.map { MetaItem.NodeItem(it) }) { item, index -> indexFactory(item.node!!, index) } } operator fun MutableMeta<*>.set(name: Name, metas: Iterable): Unit = setIndexed(name, metas) @@ -178,7 +175,7 @@ fun > M.append(name: String, value: Any?) = append(name.toNam */ @DFExperimental fun > M.edit(name: Name, builder: M.() -> Unit) { - val item = when(val existingItem = get(name)){ + val item = when (val existingItem = get(name)) { null -> empty().also { set(name, it) } is MetaItem.NodeItem -> existingItem.node else -> error("Can't edit value meta item") diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMetaDelegate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMetaDelegate.kt deleted file mode 100644 index 3230afbe..00000000 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMetaDelegate.kt +++ /dev/null @@ -1,120 +0,0 @@ -package hep.dataforge.meta - -import hep.dataforge.names.Name -import hep.dataforge.names.asName -import hep.dataforge.values.Value -import kotlin.jvm.JvmName -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KProperty - -/* Read-write delegates */ - -open class MutableMetaDelegate>( - override val owner: M, - key: Name? = null, - default: MetaItem<*>? = null -) : MetaDelegate(owner, key, default), ReadWriteProperty?> { - - override fun setValue(thisRef: Any?, property: KProperty<*>, value: MetaItem<*>?) { - val name = key ?: property.name.asName() - owner.setItem(name, value) - } -} - -class LazyMutableMetaDelegate>( - owner: M, - key: Name? = null, - defaultProvider: () -> MetaItem<*>? = { null } -) : MutableMetaDelegate(owner, key) { - override val default by lazy(defaultProvider) -} - -class ReadWriteDelegateWrapper( - val delegate: ReadWriteProperty, - val reader: (T) -> R, - val writer: (R) -> T -) : ReadWriteProperty { - override fun getValue(thisRef: Any?, property: KProperty<*>): R = reader(delegate.getValue(thisRef, property)) - - override fun setValue(thisRef: Any?, property: KProperty<*>, value: R) { - delegate.setValue(thisRef, property, writer(value)) - } -} - -fun ReadWriteProperty.map(reader: (T) -> R, writer: (R) -> T): ReadWriteDelegateWrapper = - ReadWriteDelegateWrapper(this, reader, writer) - -fun ReadWriteProperty?>.transform(reader: (MetaItem<*>?) -> R): ReadWriteProperty = - map(reader = reader, writer = { MetaItem.of(it) }) - -fun ReadWriteProperty.transform(reader: (Value?) -> R): ReadWriteDelegateWrapper = - map(reader = reader, writer = { Value.of(it) }) - -/** - * A delegate that throws - */ -fun ReadWriteProperty.notNull(default: () -> R): ReadWriteProperty { - return ReadWriteDelegateWrapper(this, - reader = { it ?: default() }, - writer = { it } - ) -} - - -fun > M.item(default: Any? = null, key: Name? = null): MutableMetaDelegate = - MutableMetaDelegate(this, key, default?.let { MetaItem.of(it) }) - -fun > M.lazyItem(key: Name? = null, defaultProvider: () -> Any?): LazyMutableMetaDelegate = - LazyMutableMetaDelegate(this, key) { defaultProvider()?.let { MetaItem.of(it) } } - -//Read-write delegates - -/** - * A property delegate that uses custom key - */ -fun > M.value(default: Value? = null, key: Name? = null): ReadWriteProperty = - item(default, key).transform { it.value } - -fun > M.string(default: String? = null, key: Name? = null): ReadWriteProperty = - item(default, key).transform { it.string } - -fun > M.boolean(default: Boolean? = null, key: Name? = null): ReadWriteProperty = - item(default, key).transform { it.boolean } - -fun > M.number(default: Number? = null, key: Name? = null): ReadWriteProperty = - item(default, key).transform { it.number } - -inline fun > M.node(default: M? = null, key: Name? = null) = - item(default, key = key).transform { it.node as? M } - -@JvmName("safeString") -fun > M.string(default: String, key: Name? = null) = - item(default, key).transform { it.string!! } - -@JvmName("safeBoolean") -fun > M.boolean(default: Boolean, key: Name? = null) = - item(default, key).transform { it.boolean!! } - -@JvmName("safeNumber") -fun > M.number(default: Number, key: Name? = null) = - item(default, key).transform { it.number!! } - -@JvmName("lazyValue") -fun > M.string(key: Name? = null, default: () -> Value) = - lazyItem(key, default).transform { it.value!! } - -@JvmName("lazyString") -fun > M.string(key: Name? = null, default: () -> String) = - lazyItem(key, default).transform { it.string!! } - -@JvmName("safeBoolean") -fun > M.boolean(key: Name? = null, default: () -> Boolean) = - lazyItem(key, default).transform { it.boolean!! } - -@JvmName("safeNumber") -fun > M.number(key: Name? = null, default: () -> Number) = - lazyItem(key, default).transform { it.number!! } - - -inline fun , reified E : Enum> M.enum(default: E, key: Name? = null) = - item(default, key).transform { it.enum()!! } \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/ItemDescriptor.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/ItemDescriptor.kt index 7f748ff2..a277e18d 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/ItemDescriptor.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/ItemDescriptor.kt @@ -2,10 +2,7 @@ package hep.dataforge.meta.descriptors import hep.dataforge.meta.* import hep.dataforge.names.* -import hep.dataforge.values.False -import hep.dataforge.values.True -import hep.dataforge.values.Value -import hep.dataforge.values.ValueType +import hep.dataforge.values.* @DFBuilder sealed class ItemDescriptor(val config: Config) { @@ -95,6 +92,11 @@ class NodeDescriptor(config: Config = Config()) : ItemDescriptor(config) { */ var default by config.node() + /** + * An index field by which this node is identified in case of same name siblings construct + */ + var indexKey by config.string(DEFAULT_INDEX_KEY) + val items: Map get() = config.getIndexed(ITEM_KEY).mapValues { (_, item) -> val node = item.node ?: error("Node descriptor must be a node") @@ -182,6 +184,8 @@ class NodeDescriptor(config: Config = Config()) : ItemDescriptor(config) { val ITEM_KEY = "item".asName() val IS_NODE_KEY = "@isNode".asName() + const val DEFAULT_INDEX_KEY = "@index" + inline operator fun invoke(block: NodeDescriptor.() -> Unit) = NodeDescriptor().apply(block) //TODO infer descriptor from spec @@ -234,9 +238,7 @@ class ValueDescriptor(config: Config = Config()) : ItemDescriptor(config) { * * @return */ - var type: List by config.item().transform { - it?.value?.list?.map { v -> ValueType.valueOf(v.string) } ?: emptyList() - } + var type: List? by config.listValue { ValueType.valueOf(it.string) } fun type(vararg t: ValueType) { this.type = listOf(*t) @@ -250,9 +252,8 @@ class ValueDescriptor(config: Config = Config()) : ItemDescriptor(config) { * @return */ fun isAllowedValue(value: Value): Boolean { - return (type.isEmpty() || type.contains(ValueType.STRING) || type.contains(value.type)) && (allowedValues.isEmpty() || allowedValues.contains( - value - )) + return (type?.let { it.contains(ValueType.STRING) || it.contains(value.type) } ?: true) + && (allowedValues.isEmpty() || allowedValues.contains(value)) } /** @@ -261,13 +262,19 @@ class ValueDescriptor(config: Config = Config()) : ItemDescriptor(config) { * * @return */ - var allowedValues: List by config.value().transform { - when { - it?.list != null -> it.list - type.size == 1 && type[0] === ValueType.BOOLEAN -> listOf(True, False) - else -> emptyList() + var allowedValues: List by config.item().convert( + reader = { + val value = it.value + when { + value?.list != null -> value.list + type?.let { type -> type.size == 1 && type[0] === ValueType.BOOLEAN} ?: false -> listOf(True, False) + else -> emptyList() + } + }, + writer = { + MetaItem.ValueItem(it.asValue()) } - } + ) /** * Allow given list of value and forbid others diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/propertyConverter.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/propertyConverter.kt new file mode 100644 index 00000000..ebe55218 --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/propertyConverter.kt @@ -0,0 +1,83 @@ +package hep.dataforge.meta + +import hep.dataforge.meta.transformations.MetaConverter +import kotlin.properties.ReadOnlyProperty +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + +/** + * Apply a converter to this delegate creating a delegate with a custom type + */ +fun ItemDelegate.convert( + converter: MetaConverter +): ReadOnlyProperty = object : ReadOnlyProperty { + override fun getValue(thisRef: Any?, property: KProperty<*>): R? = + this@convert.getValue(thisRef, property)?.let(converter::itemToObject) +} + +/* + * + */ +fun ItemDelegate.convert( + converter: MetaConverter, + default: () -> R +): ReadOnlyProperty = object : ReadOnlyProperty { + override fun getValue(thisRef: Any?, property: KProperty<*>): R = + this@convert.getValue(thisRef, property)?.let(converter::itemToObject) ?: default() +} + +/** + * A converter with a custom reader transformation + */ +fun ItemDelegate.convert( + reader: (MetaItem<*>?) -> R +): ReadOnlyProperty = object : ReadOnlyProperty { + override fun getValue(thisRef: Any?, property: KProperty<*>): R = + this@convert.getValue(thisRef, property).let(reader) +} + +/*Mutable converters*/ + +/** + * A type converter for a mutable [MetaItem] delegate + */ +fun MutableItemDelegate.convert( + converter: MetaConverter +): ReadWriteProperty = object : ReadWriteProperty { + + override fun getValue(thisRef: Any?, property: KProperty<*>): R? = + this@convert.getValue(thisRef, property)?.let(converter::itemToObject) + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: R?) { + val item = value?.let(converter::objectToMetaItem) + this@convert.setValue(thisRef, property, item) + } +} + +fun MutableItemDelegate.convert( + converter: MetaConverter, + default: () -> R +): ReadWriteProperty = object : ReadWriteProperty { + + override fun getValue(thisRef: Any?, property: KProperty<*>): R = + this@convert.getValue(thisRef, property)?.let(converter::itemToObject) ?: default() + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: R) { + val item = value.let(converter::objectToMetaItem) + this@convert.setValue(thisRef, property, item) + } +} + +fun MutableItemDelegate.convert( + reader: (MetaItem<*>?) -> R, + writer: (R) -> MetaItem<*>? +): ReadWriteProperty = object : ReadWriteProperty { + + override fun getValue(thisRef: Any?, property: KProperty<*>): R = + this@convert.getValue(thisRef, property).let(reader) + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: R) { + val item = value?.let(writer) + this@convert.setValue(thisRef, property, item) + } +} diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaConverter.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaConverter.kt index bd967a85..931e7cf3 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaConverter.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaConverter.kt @@ -1,9 +1,6 @@ package hep.dataforge.meta.transformations -import hep.dataforge.meta.Meta -import hep.dataforge.meta.MetaItem -import hep.dataforge.meta.get -import hep.dataforge.meta.value +import hep.dataforge.meta.* import hep.dataforge.values.* /** @@ -15,7 +12,7 @@ interface MetaConverter { companion object { - val item = object :MetaConverter>{ + val item = object : MetaConverter> { override fun itemToObject(item: MetaItem<*>): MetaItem<*> = item override fun objectToMetaItem(obj: MetaItem<*>): MetaItem<*> = obj } @@ -56,6 +53,15 @@ interface MetaConverter { override fun objectToMetaItem(obj: Boolean): MetaItem<*> = MetaItem.ValueItem(obj.asValue()) } + val number = object : MetaConverter { + override fun itemToObject(item: MetaItem<*>): Number = when (item) { + is MetaItem.NodeItem -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value") + is MetaItem.ValueItem -> item.value + }.number + + override fun objectToMetaItem(obj: Number): MetaItem<*> = MetaItem.ValueItem(obj.asValue()) + } + val double = object : MetaConverter { override fun itemToObject(item: MetaItem<*>): Double = when (item) { is MetaItem.NodeItem -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value") @@ -65,6 +71,15 @@ interface MetaConverter { override fun objectToMetaItem(obj: Double): MetaItem<*> = MetaItem.ValueItem(obj.asValue()) } + val float = object : MetaConverter { + override fun itemToObject(item: MetaItem<*>): Float = when (item) { + is MetaItem.NodeItem -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value") + is MetaItem.ValueItem -> item.value + }.float + + override fun objectToMetaItem(obj: Float): MetaItem<*> = MetaItem.ValueItem(obj.asValue()) + } + val int = object : MetaConverter { override fun itemToObject(item: MetaItem<*>): Int = when (item) { is MetaItem.NodeItem -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value") @@ -73,8 +88,37 @@ interface MetaConverter { override fun objectToMetaItem(obj: Int): MetaItem<*> = MetaItem.ValueItem(obj.asValue()) } + + val long = object : MetaConverter { + override fun itemToObject(item: MetaItem<*>): Long = when (item) { + is MetaItem.NodeItem -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value") + is MetaItem.ValueItem -> item.value + }.long + + override fun objectToMetaItem(obj: Long): MetaItem<*> = MetaItem.ValueItem(obj.asValue()) + } + + inline fun > enum(): MetaConverter = object : MetaConverter { + @Suppress("USELESS_CAST") + override fun itemToObject(item: MetaItem<*>): E = item.enum() as? E ?: error("The Item is not a Enum") + + override fun objectToMetaItem(obj: E): MetaItem<*> = MetaItem.ValueItem(obj.asValue()) + } + + fun valueList(writer: (T) -> Value = {Value.of(it)}, reader: (Value) -> T): MetaConverter> = + object : MetaConverter> { + override fun itemToObject(item: MetaItem<*>): List = + item.value?.list?.map(reader) ?: error("The item is not a value list") + + override fun objectToMetaItem(obj: List): MetaItem<*> = + MetaItem.ValueItem(obj.map(writer).asValue()) + } + } } +fun MetaConverter.nullableItemToObject(item: MetaItem<*>?): T? = item?.let { itemToObject(it) } +fun MetaConverter.nullableObjectToMetaItem(obj: T?): MetaItem<*>? = obj?.let { objectToMetaItem(it) } + fun MetaConverter.metaToObject(meta: Meta): T = itemToObject(MetaItem.NodeItem(meta)) fun MetaConverter.valueToObject(value: Value): T = itemToObject(MetaItem.ValueItem(value)) diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaTransformation.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaTransformation.kt index f02545fe..38984520 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaTransformation.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaTransformation.kt @@ -40,7 +40,7 @@ data class KeepTransformationRule(val selector: (Name) -> Boolean) : meta.sequence().map { it.first }.filter(selector) override fun > transformItem(name: Name, item: MetaItem<*>?, target: M) { - if (selector(name)) target[name] = item + if (selector(name)) target.setItem(name, item) } } diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt index 141af13c..b3f8231a 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt @@ -113,6 +113,8 @@ data class NameToken(val body: String, val index: String = "") { } } +fun NameToken.withIndex(newIndex: String) = NameToken(body, newIndex) + /** * Convert a [String] to name parsing it and extracting name tokens and index syntax. * This operation is rather heavy so it should be used with care in high performance code. diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/JsonMetaTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/JsonMetaTest.kt index 127649da..9086e156 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/JsonMetaTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/JsonMetaTest.kt @@ -1,5 +1,6 @@ package hep.dataforge.meta +import hep.dataforge.meta.descriptors.NodeDescriptor import kotlinx.serialization.json.int import kotlinx.serialization.json.json import kotlinx.serialization.json.jsonArray @@ -17,23 +18,34 @@ class JsonMetaTest { } "nodeArray" to jsonArray { +json { - "index" to 1 + "index" to "1" + "value" to 2 } +json { - "index" to 2 + "index" to "2" + "value" to 3 } +json { - "index" to 3 + "index" to "3" + "value" to 4 } } } + val descriptor = NodeDescriptor{ + node("nodeArray"){ + indexKey = "index" + } + } + @Test fun jsonMetaConversion() { - val meta = json.toMeta() - val reconstructed = meta.toJson() println(json) + val meta = json.toMeta(descriptor) + //println(meta) + val reconstructed = meta.toJson(descriptor) println(reconstructed) assertEquals(2, reconstructed["nodeArray"]?.jsonArray?.get(1)?.jsonObject?.get("index")?.int) + assertEquals(json,reconstructed) } } \ No newline at end of file diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt index 939b8bb2..1f7d7e97 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt @@ -19,7 +19,7 @@ class SchemeTest{ assertEquals(10, meta.values().count()) - val bNode = styled.getProperty("b").node + val bNode = styled.getItem("b").node val aNodes = bNode?.getIndexed("a") diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt index dbb90cf2..db80b6fa 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt @@ -1,28 +1,23 @@ package hep.dataforge.tables -import kotlin.reflect.KClass - /** - * @param C bottom type for all columns in the table + * @param T bottom type for all columns in the table */ -class ColumnTable(override val columns: Collection>) : Table { +class ColumnTable(override val columns: Collection>) : Table { private val rowsNum = columns.first().size init { require(columns.all { it.size == rowsNum }) { "All columns must be of the same size" } } - override val rows: List> + override val rows: List> get() = (0 until rowsNum).map { VirtualRow(this, it) } - override fun getValue(row: Int, column: String, type: KClass): T? { - val value = columns[column]?.get(row) - return type.cast(value) - } + override fun getValue(row: Int, column: String): T? = columns[column]?.get(row) } -internal class VirtualRow(val table: Table, val index: Int) : Row { - override fun getValue(column: String, type: KClass): T? = table.getValue(index, column, type) +internal class VirtualRow(val table: Table, val index: Int) : Row { + override fun getValue(column: String): T? = table.getValue(index, column) // override fun get(columnHeader: ColumnHeader): T? { // return table.co[columnHeader][index] diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt deleted file mode 100644 index 04c7d3a4..00000000 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt +++ /dev/null @@ -1,10 +0,0 @@ -package hep.dataforge.tables - -import kotlin.reflect.KClass - -inline class MapRow(val values: Map) : Row { - override fun getValue(column: String, type: KClass): T? { - val value = values[column] - return type.cast(value) - } -} \ No newline at end of file diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableColumnTable.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableColumnTable.kt index 8a9b06bc..4b93a13d 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableColumnTable.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableColumnTable.kt @@ -1,8 +1,5 @@ package hep.dataforge.tables -import hep.dataforge.meta.Meta -import kotlin.reflect.KClass - /** * Mutable table with a fixed size, but dynamic columns */ @@ -14,10 +11,7 @@ class MutableColumnTable(val size: Int) : Table { VirtualRow(this, it) } - override fun getValue(row: Int, column: String, type: KClass): T? { - val value = columns[column]?.get(row) - return type.cast(value) - } + override fun getValue(row: Int, column: String): C? = columns[column]?.get(row) /** * Add a fixed column to the end of the table @@ -35,27 +29,3 @@ class MutableColumnTable(val size: Int) : Table { _columns.add(index, column) } } - -class MapColumn( - val source: Column, - override val type: KClass, - override val name: String, - override val meta: Meta = source.meta, - val mapper: (T?) -> R? -) : Column { - override val size: Int get() = source.size - - override fun get(index: Int): R? = mapper(source[index]) -} - -class CachedMapColumn( - val source: Column, - override val type: KClass, - override val name: String, - override val meta: Meta = source.meta, - val mapper: (T?) -> R? -) : Column { - override val size: Int get() = source.size - private val values: HashMap = HashMap() - override fun get(index: Int): R? = values.getOrPut(index) { mapper(source[index]) } -} \ No newline at end of file diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableTable.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableTable.kt index aa2d9abf..a59108a6 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableTable.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableTable.kt @@ -9,7 +9,7 @@ class MutableTable( override val header: MutableList> ) : RowTable(rows, header) { - fun column(name: String, type: KClass, meta: Meta): ColumnHeader { + fun column(name: String, type: KClass, meta: Meta): ColumnHeader { val column = SimpleColumnHeader(name, type, meta) header.add(column) return column diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt index c565d9cd..aa3449fb 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt @@ -4,6 +4,10 @@ import hep.dataforge.meta.Meta import kotlinx.coroutines.flow.toList import kotlin.reflect.KClass +inline class MapRow(val values: Map) : Row { + override fun getValue(column: String): C? = values[column] +} + internal class RowTableColumn(val table: Table, val header: ColumnHeader) : Column { override val name: String get() = header.name override val type: KClass get() = header.type @@ -14,8 +18,7 @@ internal class RowTableColumn(val table: Table, val header: C } open class RowTable(override val rows: List>, override val header: List>) : Table { - override fun getValue(row: Int, column: String, type: KClass): T? = - rows[row].getValue(column, type) + override fun getValue(row: Int, column: String): C? = rows[row].getValue(column) override val columns: List> get() = header.map { RowTableColumn(this, it) } } diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt index bba90d38..0197955d 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt @@ -3,32 +3,23 @@ package hep.dataforge.tables import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.asFlow import kotlin.reflect.KClass - -//TODO to be removed in 1.3.70 -@Suppress("UNCHECKED_CAST") -internal fun KClass.cast(value: Any?): T? { - return when { - value == null -> null - !isInstance(value) -> error("Expected type is $this, but found ${value::class}") - else -> value as T - } -} +import kotlin.reflect.cast /** * Finite or infinite row set. Rows are produced in a lazy suspendable [Flow]. * Each row must contain at least all the fields mentioned in [header]. */ -interface Rows { - val header: TableHeader - fun rowFlow(): Flow> +interface Rows { + val header: TableHeader + fun rowFlow(): Flow> } -interface Table : Rows { - fun getValue(row: Int, column: String, type: KClass): T? - val columns: Collection> - override val header: TableHeader get() = columns.toList() - val rows: List> - override fun rowFlow(): Flow> = rows.asFlow() +interface Table : Rows { + fun getValue(row: Int, column: String): T? + val columns: Collection> + override val header: TableHeader get() = columns.toList() + val rows: List> + override fun rowFlow(): Flow> = rows.asFlow() /** * Apply typed query to this table and return lazy [Flow] of resulting rows. The flow could be empty. @@ -40,14 +31,18 @@ interface Table : Rows { } } -operator fun Collection>.get(name: String): Column<*>? = find { it.name == name } +fun Table.getValue(row: Int, column: String, type: KClass): T? = + type.cast(getValue(row, column)) + +operator fun Collection>.get(name: String): Column? = find { it.name == name } inline operator fun Table.get(row: Int, column: String): T? = getValue(row, column, T::class) -operator fun Table.get(row: Int, column: ColumnHeader): T? = getValue(row, column.name, column.type) +operator fun Table.get(row: Int, column: ColumnHeader): T? = + getValue(row, column.name, column.type) -interface Column : ColumnHeader { +interface Column : ColumnHeader { val size: Int operator fun get(index: Int): T? } @@ -60,9 +55,11 @@ operator fun Column.iterator() = iterator { } } -interface Row { - fun getValue(column: String, type: KClass): T? +interface Row { + fun getValue(column: String): T? } -inline operator fun Row.get(column: String): T? = getValue(column, T::class) +fun Row.getValue(column: String, type: KClass): T? = type.cast(getValue(column)) + +inline operator fun Row.get(column: String): T? = T::class.cast(getValue(column)) operator fun Row.get(column: ColumnHeader): T? = getValue(column.name, column.type) \ No newline at end of file diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/TransformationColumn.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/TransformationColumn.kt new file mode 100644 index 00000000..3c4fa2b4 --- /dev/null +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/TransformationColumn.kt @@ -0,0 +1,60 @@ +package hep.dataforge.tables + +import hep.dataforge.meta.Meta +import kotlin.reflect.KClass + +/** + * A virtual column obtained by transforming Given row to a single value + */ +class TransformationColumn( + val table: Table, + override val type: KClass, + override val name: String, + override val meta: Meta, + val mapper: (Row) -> R? +) : Column { + override val size: Int get() = table.rows.size + + override fun get(index: Int): R? = mapper(table.rows[index]) +} + +/** + * A virtual column obtained via transformation of single column with caching results on call (evaluation is lazy). + * + * Calls are not thread safe + */ +class CachedTransformationColumn( + val table: Table, + override val type: KClass, + override val name: String, + override val meta: Meta, + val mapper: (Row) -> R? +) : Column { + override val size: Int get() = table.rows.size + private val values: HashMap = HashMap() + override fun get(index: Int): R? = values.getOrPut(index) { mapper(table.rows[index]) } +} + +/** + * Create a virtual column from a given column + */ +inline fun Table.mapRows( + name: String, + meta: Meta = Meta.EMPTY, + cache: Boolean = false, + noinline mapper: (Row) -> R? +): Column = if (cache) { + CachedTransformationColumn(this, R::class, name, meta, mapper) +} else { + TransformationColumn(this, R::class, name, meta, mapper) +} + +fun Table.mapRowsToDouble(name: String, meta: Meta = Meta.EMPTY, block: (Row) -> Double): RealColumn { + val data = DoubleArray(rows.size) { block(rows[it]) } + return RealColumn(name, data, meta) +} + +fun Table.mapRowsToInt(name: String, meta: Meta = Meta.EMPTY, block: (Row) -> Int): IntColumn { + val data = IntArray(rows.size) { block(rows[it]) } + return IntColumn(name, data, meta) +} \ No newline at end of file diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/TextRows.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/TextRows.kt index 00715844..293031f8 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/TextRows.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/TextRows.kt @@ -13,7 +13,6 @@ import kotlinx.io.text.forEachUtf8Line import kotlinx.io.text.readUtf8Line import kotlinx.io.text.readUtf8StringUntilDelimiter import kotlinx.io.text.writeUtf8String -import kotlin.reflect.KClass /** * Read a lin as a fixed width [Row] @@ -92,9 +91,9 @@ class TextTable( } } - override fun getValue(row: Int, column: String, type: KClass): T? { + override fun getValue(row: Int, column: String): Value? { val offset = index[row] - return type.cast(readAt(offset)[column]) + return readAt(offset)[column] } companion object { diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/NumberColumn.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/numericColumns.kt similarity index 83% rename from dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/NumberColumn.kt rename to dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/numericColumns.kt index 1bd8d6a3..7f80b082 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/NumberColumn.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/numericColumns.kt @@ -3,9 +3,8 @@ package hep.dataforge.tables import hep.dataforge.meta.Meta import kotlin.reflect.KClass -//interface NumberColumn : Column -data class RealColumn( +class RealColumn( override val name: String, val data: DoubleArray, override val meta: Meta = Meta.EMPTY @@ -44,12 +43,7 @@ data class RealColumn( } } -fun Column.map(meta: Meta = this.meta, block: (T?) -> Double): RealColumn { - val data = DoubleArray(size) { block(get(it)) } - return RealColumn(name, data, meta) -} - -data class IntColumn( +class IntColumn( override val name: String, val data: IntArray, override val meta: Meta = Meta.EMPTY @@ -86,9 +80,4 @@ data class IntColumn( noinline metaBuilder: ColumnScheme.() -> Unit ): IntColumn = IntColumn(name, data, ColumnScheme(metaBuilder).toMeta()) } -} - -fun Column.map(meta: Meta = this.meta, block: (T?) -> Int): IntColumn { - val data = IntArray(size) { block(get(it)) } - return IntColumn(name, data, meta) } \ No newline at end of file diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt index 2f7b6998..acd16b2d 100644 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt @@ -36,7 +36,11 @@ data class TaskModel( val dataDependencies = dependencies.filterIsInstance() val taskDependencies = dependencies.filterIsInstance>() setIndexed("data".toName(), dataDependencies.map { it.toMeta() }) - setIndexed("task".toName(), taskDependencies.map { it.toMeta() }) { taskDependencies[it].name.toString() } + setIndexed( + "task".toName(), + taskDependencies.map { it.toMeta() }) { _, index -> + taskDependencies[index].name.toString() + } //TODO ensure all dependencies are listed } } diff --git a/docs/images/df.svg b/docs/images/df.svg new file mode 100644 index 00000000..f977bd86 --- /dev/null +++ b/docs/images/df.svg @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + +