From f63a2b6e93e7a744d3e14b0787842c7f9ae9bf13 Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Tue, 5 Nov 2019 19:45:53 +0300 Subject: [PATCH 01/41] Some serialization fixes --- build.gradle.kts | 17 +++++++----- .../hep/dataforge/context/ContextBuilder.kt | 2 ++ .../kotlin/hep/dataforge/data/MapAction.kt | 6 ++--- .../io/serialization/serializationUtils.kt | 26 ++++++++++++++----- .../hep/dataforge/output/html/HtmlOutput.kt | 2 +- .../hep/dataforge/workspace/TaskBuilder.kt | 3 ++- .../hep/dataforge/workspace/TaskModel.kt | 3 --- 7 files changed, 36 insertions(+), 23 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 04cce594..cd3e5d61 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,10 +1,12 @@ +import scientifik.ScientifikExtension + plugins { - id("scientifik.mpp") version "0.2.1" apply false - id("scientifik.jvm") version "0.2.1" apply false - id("scientifik.publish") version "0.2.1" apply false + id("scientifik.mpp") version "0.2.2" apply false + id("scientifik.jvm") version "0.2.2" apply false + id("scientifik.publish") version "0.2.2" apply false } -val dataforgeVersion by extra("0.1.4") +val dataforgeVersion by extra("0.1.5-dev-1") val bintrayRepo by extra("dataforge") val githubProject by extra("dataforge-core") @@ -15,7 +17,8 @@ allprojects { } subprojects { - if (name.startsWith("dataforge")) { - apply(plugin = "scientifik.publish") - } + apply(plugin = "scientifik.publish") + afterEvaluate { + extensions.findByType<ScientifikExtension>()?.apply { withDokka() } + } } \ No newline at end of file diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/ContextBuilder.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/ContextBuilder.kt index 58a03554..0aed9b7f 100644 --- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/ContextBuilder.kt +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/ContextBuilder.kt @@ -1,5 +1,6 @@ package hep.dataforge.context +import hep.dataforge.meta.DFBuilder import hep.dataforge.meta.MetaBuilder import hep.dataforge.meta.buildMeta import hep.dataforge.names.toName @@ -7,6 +8,7 @@ import hep.dataforge.names.toName /** * A convenience builder for context */ +@DFBuilder class ContextBuilder(var name: String = "@anonymous", val parent: Context = Global) { private val plugins = ArrayList<Plugin>() private var meta = MetaBuilder() diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/MapAction.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/MapAction.kt index 8c543927..89e887db 100644 --- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/MapAction.kt +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/MapAction.kt @@ -1,9 +1,6 @@ package hep.dataforge.data -import hep.dataforge.meta.Meta -import hep.dataforge.meta.MetaBuilder -import hep.dataforge.meta.builder -import hep.dataforge.meta.seal +import hep.dataforge.meta.* import hep.dataforge.names.Name import kotlin.reflect.KClass @@ -20,6 +17,7 @@ data class ActionEnv( /** * Action environment */ +@DFBuilder class MapActionBuilder<T, R>(var name: Name, var meta: MetaBuilder, val actionMeta: Meta) { lateinit var result: suspend ActionEnv.(T) -> R diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/serializationUtils.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/serializationUtils.kt index 09d17054..b32abb14 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/serializationUtils.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/serializationUtils.kt @@ -1,9 +1,7 @@ package hep.dataforge.io.serialization -import kotlinx.serialization.CompositeDecoder -import kotlinx.serialization.Decoder -import kotlinx.serialization.KSerializer -import kotlinx.serialization.SerialDescriptor +import hep.dataforge.meta.DFExperimental +import kotlinx.serialization.* import kotlinx.serialization.internal.* /** @@ -71,10 +69,24 @@ inline fun <reified T : Any> KSerializer<T>.descriptor( ): SerialDescriptor = SerialDescriptorBuilder(SerialClassDescImpl(name)).apply(block).build() -fun Decoder.decodeStructure( +@DFExperimental +inline fun <R> Decoder.decodeStructure( desc: SerialDescriptor, vararg typeParams: KSerializer<*> = emptyArray(), - block: CompositeDecoder.() -> Unit + crossinline block: CompositeDecoder.() -> R +): R { + val decoder = beginStructure(desc, *typeParams) + val res = decoder.block() + decoder.endStructure(desc) + return res +} + +inline fun Encoder.encodeStructure( + desc: SerialDescriptor, + vararg typeParams: KSerializer<*> = emptyArray(), + block: CompositeEncoder.() -> Unit ) { - beginStructure(desc, *typeParams).apply(block).endStructure(desc) + val encoder = beginStructure(desc, *typeParams) + encoder.block() + encoder.endStructure(desc) } \ No newline at end of file diff --git a/dataforge-output-html/src/commonMain/kotlin/hep/dataforge/output/html/HtmlOutput.kt b/dataforge-output-html/src/commonMain/kotlin/hep/dataforge/output/html/HtmlOutput.kt index b54b7eb7..3ff23403 100644 --- a/dataforge-output-html/src/commonMain/kotlin/hep/dataforge/output/html/HtmlOutput.kt +++ b/dataforge-output-html/src/commonMain/kotlin/hep/dataforge/output/html/HtmlOutput.kt @@ -47,7 +47,7 @@ class HtmlOutput<T : Any>(override val context: Context, private val consumer: T } /** - * A text or binary renderer based on [kotlinx.io.core.Output] + * A text or binary renderer based on [Output] */ @Type(HTML_CONVERTER_TYPE) interface HtmlBuilder<T : Any> { diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt index 80d89e24..3ff86325 100644 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt @@ -3,6 +3,7 @@ package hep.dataforge.workspace import hep.dataforge.context.Context import hep.dataforge.data.* import hep.dataforge.descriptors.NodeDescriptor +import hep.dataforge.meta.DFBuilder import hep.dataforge.meta.Meta import hep.dataforge.meta.get import hep.dataforge.meta.string @@ -13,7 +14,7 @@ import hep.dataforge.names.toName import kotlin.jvm.JvmName import kotlin.reflect.KClass -@TaskBuildScope +@DFBuilder class TaskBuilder<R : Any>(val name: Name, val type: KClass<out R>) { private var modelTransform: TaskModelBuilder.(Meta) -> Unit = { allData() } // private val additionalDependencies = HashSet<Dependency>() 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 b4ccb7ae..71a5c006 100644 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt @@ -58,9 +58,6 @@ fun TaskModel.buildInput(workspace: Workspace): DataTree<Any> { }.build() } -@DslMarker -annotation class TaskBuildScope - interface TaskDependencyContainer { val defaultMeta: Meta fun add(dependency: Dependency) From 5265c0e5abe2dd26a64fc90f91ff862eaa30b669 Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Tue, 5 Nov 2019 21:36:17 +0300 Subject: [PATCH 02/41] Implements Envelope parts An envelope consisting of other envelopes --- .../kotlin/hep/dataforge/io/Envelope.kt | 37 ++---------- .../hep/dataforge/io/EnvelopeBuilder.kt | 36 +++++++++++ .../kotlin/hep/dataforge/io/EnvelopeParts.kt | 60 +++++++++++++++++++ .../kotlin/hep/dataforge/io/IOPlugin.kt | 3 + .../hep/dataforge/io/EnvelopePartsTest.kt | 32 ++++++++++ 5 files changed, 135 insertions(+), 33 deletions(-) create mode 100644 dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt create mode 100644 dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt create mode 100644 dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopePartsTest.kt diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt index 80e07b56..61aeb4d2 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt @@ -1,11 +1,11 @@ package hep.dataforge.io -import hep.dataforge.meta.* +import hep.dataforge.meta.Laminate +import hep.dataforge.meta.Meta +import hep.dataforge.meta.get +import hep.dataforge.meta.string import hep.dataforge.names.asName import hep.dataforge.names.plus -import kotlinx.io.core.Output -import kotlinx.io.core.buildPacket -import kotlinx.io.core.readBytes interface Envelope { val meta: Meta @@ -83,32 +83,3 @@ fun Envelope.withMetaLayers(vararg layers: Meta): Envelope { } } -class EnvelopeBuilder { - private val metaBuilder = MetaBuilder() - var data: Binary? = null - - fun meta(block: MetaBuilder.() -> Unit) { - metaBuilder.apply(block) - } - - fun meta(meta: Meta) { - metaBuilder.update(meta) - } - - var type by metaBuilder.string(key = Envelope.ENVELOPE_TYPE_KEY) - var dataType by metaBuilder.string(key = Envelope.ENVELOPE_DATA_TYPE_KEY) - var dataID by metaBuilder.string(key = Envelope.ENVELOPE_DATA_ID_KEY) - var description by metaBuilder.string(key = Envelope.ENVELOPE_DESCRIPTION_KEY) - - /** - * Construct a binary and transform it into byte-array based buffer - */ - fun data(block: Output.() -> Unit) { - val bytes = buildPacket { - block() - } - data = ArrayBinary(bytes.readBytes()) - } - - internal fun build() = SimpleEnvelope(metaBuilder.seal(), data) -} \ No newline at end of file diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt new file mode 100644 index 00000000..b8d0b660 --- /dev/null +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt @@ -0,0 +1,36 @@ +package hep.dataforge.io + +import hep.dataforge.meta.* +import kotlinx.io.core.Output +import kotlinx.io.core.buildPacket +import kotlinx.io.core.readBytes + +class EnvelopeBuilder { + private val metaBuilder = MetaBuilder() + var data: Binary? = null + + fun meta(block: MetaBuilder.() -> Unit) { + metaBuilder.apply(block) + } + + fun meta(meta: Meta) { + metaBuilder.update(meta) + } + + var type by metaBuilder.string(key = Envelope.ENVELOPE_TYPE_KEY) + var dataType by metaBuilder.string(key = Envelope.ENVELOPE_DATA_TYPE_KEY) + var dataID by metaBuilder.string(key = Envelope.ENVELOPE_DATA_ID_KEY) + var description by metaBuilder.string(key = Envelope.ENVELOPE_DESCRIPTION_KEY) + + /** + * Construct a binary and transform it into byte-array based buffer + */ + fun data(block: Output.() -> Unit) { + val bytes = buildPacket { + block() + } + data = ArrayBinary(bytes.readBytes()) + } + + internal fun build() = SimpleEnvelope(metaBuilder.seal(), data) +} \ No newline at end of file diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt new file mode 100644 index 00000000..ca4efea4 --- /dev/null +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt @@ -0,0 +1,60 @@ +package hep.dataforge.io + +import hep.dataforge.context.Global +import hep.dataforge.io.EnvelopeParts.FORMAT_META_KEY +import hep.dataforge.io.EnvelopeParts.FORMAT_NAME_KEY +import hep.dataforge.io.EnvelopeParts.PARTS_DATA_TYPE +import hep.dataforge.io.EnvelopeParts.SIZE_KEY +import hep.dataforge.meta.* +import hep.dataforge.names.plus +import hep.dataforge.names.toName + +object EnvelopeParts { + val SIZE_KEY = Envelope.ENVELOPE_NODE_KEY + "parts" + "size" + val FORMAT_NAME_KEY = Envelope.ENVELOPE_NODE_KEY + "parts" + "format" + val FORMAT_META_KEY = Envelope.ENVELOPE_NODE_KEY + "parts" + "meta" + + const val PARTS_DATA_TYPE = "envelope.parts" +} + +fun EnvelopeBuilder.parts(formatFactory: EnvelopeFormatFactory, envelopes: Collection<Envelope>) { + dataType = PARTS_DATA_TYPE + meta { + SIZE_KEY put envelopes.size + FORMAT_NAME_KEY put formatFactory.name.toString() + } + val format = formatFactory() + data { + format.run { + envelopes.forEach { + writeObject(it) + } + } + } +} + +fun EnvelopeBuilder.parts(formatFactory: EnvelopeFormatFactory, builder: suspend SequenceScope<Envelope>.() -> Unit) = + parts(formatFactory, sequence(builder).toList()) + +fun Envelope.parts(io: IOPlugin = Global.plugins.fetch(IOPlugin)): Sequence<Envelope> { + return when (dataType) { + PARTS_DATA_TYPE -> { + val size = meta[SIZE_KEY].int ?: error("Unsized parts not supported yet") + val formatName = meta[FORMAT_NAME_KEY].string?.toName() + ?: error("Inferring parts format is not supported at the moment") + val formatMeta = meta[FORMAT_META_KEY].node ?: EmptyMeta + val format = io.envelopeFormat(formatName, formatMeta) + ?: error("Format $formatName is not resolved by $io") + return format.run { + data?.read { + sequence { + repeat(size) { + yield(readObject()) + } + } + } ?: emptySequence() + } + } + else -> emptySequence() + } +} diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt index 7e61924f..ae88e4f4 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt @@ -26,6 +26,9 @@ class IOPlugin(meta: Meta) : AbstractPlugin(meta) { context.content<EnvelopeFormatFactory>(ENVELOPE_FORMAT_TYPE).values } + fun envelopeFormat(name: Name, meta: Meta = EmptyMeta) = + envelopeFormatFactories.find { it.name == name }?.invoke(meta, context) + override fun provideTop(target: String): Map<Name, Any> { return when (target) { META_FORMAT_TYPE -> defaultMetaFormats.toMap() diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopePartsTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopePartsTest.kt new file mode 100644 index 00000000..5122680a --- /dev/null +++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopePartsTest.kt @@ -0,0 +1,32 @@ +package hep.dataforge.io + +import hep.dataforge.meta.get +import hep.dataforge.meta.int +import kotlinx.io.core.writeText +import kotlin.test.Test +import kotlin.test.assertEquals + +class EnvelopePartsTest { + val envelopes = (0..5).map { + Envelope { + meta { + "value" put it + } + data { + writeText("Hello World $it") + } + } + } + val partsEnvelope = Envelope { + parts(TaggedEnvelopeFormat, envelopes) + } + + @Test + fun testParts() { + val bytes = TaggedEnvelopeFormat.default.writeBytes(partsEnvelope) + val reconstructed = TaggedEnvelopeFormat.default.readBytes(bytes) + val parts = reconstructed.parts().toList() + assertEquals(2, parts[2].meta["value"].int) + } + +} \ No newline at end of file From ff59c14c173d575b4e193e42d06a73b82fff1ccb Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Tue, 5 Nov 2019 21:42:29 +0300 Subject: [PATCH 03/41] Minor fix to multipart envelope --- .../kotlin/hep/dataforge/io/EnvelopeParts.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt index ca4efea4..0a8a207d 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt @@ -17,6 +17,9 @@ object EnvelopeParts { const val PARTS_DATA_TYPE = "envelope.parts" } +/** + * Append multiple serialized envelopes to the data block. Previous data is erased if it was present + */ fun EnvelopeBuilder.parts(formatFactory: EnvelopeFormatFactory, envelopes: Collection<Envelope>) { dataType = PARTS_DATA_TYPE meta { @@ -36,7 +39,10 @@ fun EnvelopeBuilder.parts(formatFactory: EnvelopeFormatFactory, envelopes: Colle fun EnvelopeBuilder.parts(formatFactory: EnvelopeFormatFactory, builder: suspend SequenceScope<Envelope>.() -> Unit) = parts(formatFactory, sequence(builder).toList()) -fun Envelope.parts(io: IOPlugin = Global.plugins.fetch(IOPlugin)): Sequence<Envelope> { +/** + * If given envelope supports multipart data, return a sequence of those parts (could be empty). Otherwise return null. + */ +fun Envelope.parts(io: IOPlugin = Global.plugins.fetch(IOPlugin)): Sequence<Envelope>? { return when (dataType) { PARTS_DATA_TYPE -> { val size = meta[SIZE_KEY].int ?: error("Unsized parts not supported yet") @@ -55,6 +61,6 @@ fun Envelope.parts(io: IOPlugin = Global.plugins.fetch(IOPlugin)): Sequence<Enve } ?: emptySequence() } } - else -> emptySequence() + else -> null } } From 3e9cb3915cdd1e81bad42483fdb48d5af699f441 Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Sun, 10 Nov 2019 19:08:32 +0300 Subject: [PATCH 04/41] Meta and Evelope format factories now implement Meta and Envelope formats (default representation). --- .../kotlin/hep/dataforge/data/DataNode.kt | 20 ++-- .../io/yaml/FrontMatterEnvelopeFormat.kt | 17 ++- .../hep/dataforge/io/yaml/YamlMetaFormat.kt | 10 +- .../kotlin/hep/dataforge/io/Binary.kt | 21 ++-- .../kotlin/hep/dataforge/io/EnvelopeFormat.kt | 2 +- .../kotlin/hep/dataforge/io/JsonMetaFormat.kt | 12 ++- .../kotlin/hep/dataforge/io/MetaFormat.kt | 4 +- .../hep/dataforge/io/TaggedEnvelopeFormat.kt | 18 +++- .../hep/dataforge/io/TaglessEnvelopeFormat.kt | 23 +++- .../hep/dataforge/io/EnvelopeFormatTest.kt | 4 +- .../hep/dataforge/io/EnvelopePartsTest.kt | 6 +- .../kotlin/hep/dataforge/io/functionsJVM.kt | 29 +++++ .../kotlin/hep/dataforge/io/ioFormatsJVM.kt | 100 +++++++++++++----- .../kotlin/hep/dataforge/io/FileBinaryTest.kt | 4 +- .../dataforge/workspace/WorkspaceBuilder.kt | 2 +- .../hep/dataforge/workspace/fileData.kt | 24 +---- 16 files changed, 197 insertions(+), 99 deletions(-) create mode 100644 dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/functionsJVM.kt diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt index 12bb06ab..589e875b 100644 --- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt @@ -64,21 +64,13 @@ val <T : Any> DataItem<T>?.node: DataNode<T>? get() = (this as? DataItem.Node<T> val <T : Any> DataItem<T>?.data: Data<T>? get() = (this as? DataItem.Leaf<T>)?.value /** - * Start computation for all goals in data node + * Start computation for all goals in data node and return a job for the whole node */ -fun DataNode<*>.startAll(scope: CoroutineScope): Unit = items.values.forEach { - when (it) { - is DataItem.Node<*> -> it.value.startAll(scope) - is DataItem.Leaf<*> -> it.value.start(scope) - } -} - -fun DataNode<*>.joinAll(scope: CoroutineScope): Job = scope.launch { - startAll(scope) - items.forEach { - when (val value = it.value) { - is DataItem.Node -> value.value.joinAll(this).join() - is DataItem.Leaf -> value.value.await(scope) +fun DataNode<*>.launchAll(scope: CoroutineScope): Job = scope.launch { + items.values.forEach { + when (it) { + is DataItem.Node<*> -> it.value.launchAll(scope) + is DataItem.Leaf<*> -> it.value.start(scope) } } } diff --git a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt index db701625..023635e2 100644 --- a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt +++ b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt @@ -24,7 +24,7 @@ class FrontMatterEnvelopeFormat( val readMetaFormat = metaTypeRegex.matchEntire(line)?.groupValues?.first() - ?.let { io.metaFormat(it) } ?: YamlMetaFormat.default + ?.let { io.metaFormat(it) } ?: YamlMetaFormat val metaBlock = buildPacket { do { @@ -45,7 +45,7 @@ class FrontMatterEnvelopeFormat( val readMetaFormat = metaTypeRegex.matchEntire(line)?.groupValues?.first() - ?.let { io.metaFormat(it) } ?: YamlMetaFormat.default + ?.let { io.metaFormat(it) } ?: YamlMetaFormat val metaBlock = buildPacket { do { @@ -72,7 +72,7 @@ class FrontMatterEnvelopeFormat( private val metaTypeRegex = "---(\\w*)\\s*".toRegex() override fun invoke(meta: Meta, context: Context): EnvelopeFormat { - return FrontMatterEnvelopeFormat(context.io, meta) + return FrontMatterEnvelopeFormat(context.io, meta) } override fun peekFormat(io: IOPlugin, input: Input): EnvelopeFormat? { @@ -84,5 +84,16 @@ class FrontMatterEnvelopeFormat( } } + private val default by lazy { invoke() } + + override fun Input.readPartial(): PartialEnvelope = + default.run { readPartial() } + + override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) = + default.run { writeEnvelope(envelope, metaFormatFactory, formatMeta) } + + override fun Input.readObject(): Envelope = + default.run { readObject() } + } } \ No newline at end of file diff --git a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt index 24ea44ec..7130518d 100644 --- a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt +++ b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt @@ -45,12 +45,18 @@ class YamlMetaFormat(val meta: Meta) : MetaFormat { } companion object : MetaFormatFactory { - val default = YamlMetaFormat() - override fun invoke(meta: Meta, context: Context): MetaFormat = YamlMetaFormat(meta) override val name: Name = super.name + "yaml" override val key: Short = 0x594d //YM + + private val default = YamlMetaFormat() + + override fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor?) = + default.run { writeMeta(meta, descriptor) } + + override fun Input.readMeta(descriptor: NodeDescriptor?): Meta = + default.run { readMeta(descriptor) } } } \ No newline at end of file diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Binary.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Binary.kt index ca05de4d..bd1f2249 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Binary.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Binary.kt @@ -8,7 +8,7 @@ import kotlin.math.min */ interface Binary { /** - * The size of binary in bytes + * The size of binary in bytes. [ULong.MAX_VALUE] if size is not defined and input should be read until its end is reached */ val size: ULong @@ -18,6 +18,10 @@ interface Binary { * Some implementation may forbid this to be called twice. In this case second call will throw an exception. */ fun <R> read(block: Input.() -> R): R + + companion object { + val EMPTY = EmptyBinary + } } /** @@ -48,12 +52,11 @@ fun RandomAccessBinary.readPacket(from: UInt, size: UInt): ByteReadPacket = read @ExperimentalUnsignedTypes object EmptyBinary : RandomAccessBinary { - override val size: ULong = 0.toULong() + override val size: ULong = 0u override fun <R> read(from: UInt, size: UInt, block: Input.() -> R): R { error("The binary is empty") } - } @ExperimentalUnsignedTypes @@ -79,9 +82,9 @@ fun <T : Any> Binary.readWith(format: IOFormat<T>): T = format.run { } } -fun <T : Any> IOFormat<T>.writeBinary(obj: T): Binary { - val packet = buildPacket { - writeObject(obj) - } - return ArrayBinary(packet.readBytes()) -} \ No newline at end of file +//fun <T : Any> IOFormat<T>.writeBinary(obj: T): Binary { +// val packet = buildPacket { +// writeObject(obj) +// } +// return ArrayBinary(packet.readBytes()) +//} \ No newline at end of file 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 c52b9e1d..4f747ea2 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeFormat.kt @@ -31,7 +31,7 @@ interface EnvelopeFormat : IOFormat<Envelope> { } @Type(ENVELOPE_FORMAT_TYPE) -interface EnvelopeFormatFactory : IOFormatFactory<Envelope> { +interface EnvelopeFormatFactory : IOFormatFactory<Envelope>, EnvelopeFormat { override val name: Name get() = "envelope".asName() override val type: KClass<out Envelope> get() = Envelope::class diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt index a95cdec4..5c10505d 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt @@ -38,12 +38,18 @@ class JsonMetaFormat(private val json: Json = Json.indented) : MetaFormat { } companion object : MetaFormatFactory { - val default = JsonMetaFormat() - override fun invoke(meta: Meta, context: Context): MetaFormat = default override val name: Name = super.name + "json" override val key: Short = 0x4a53//"JS" + + private val default = JsonMetaFormat() + + override fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor?) = + default.run { writeMeta(meta,descriptor) } + + override fun Input.readMeta(descriptor: NodeDescriptor?): Meta = + default.run { readMeta(descriptor) } } } @@ -90,7 +96,7 @@ fun Meta.toJson(descriptor: NodeDescriptor? = null): JsonObject { fun JsonElement.toMeta(descriptor: NodeDescriptor? = null): Meta { return when (val item = toMetaItem(descriptor)) { is MetaItem.NodeItem<*> -> item.node - is MetaItem.ValueItem ->item.value.toMeta() + is MetaItem.ValueItem -> item.value.toMeta() } } diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/MetaFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/MetaFormat.kt index ca9a53a2..9d1af81a 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/MetaFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/MetaFormat.kt @@ -27,7 +27,7 @@ interface MetaFormat : IOFormat<Meta> { } @Type(META_FORMAT_TYPE) -interface MetaFormatFactory : IOFormatFactory<Meta> { +interface MetaFormatFactory : IOFormatFactory<Meta>, MetaFormat { override val name: Name get() = "meta".asName() override val type: KClass<out Meta> get() = Meta::class @@ -47,7 +47,7 @@ fun Meta.toString(format: MetaFormat): String = buildPacket { fun Meta.toString(formatFactory: MetaFormatFactory): String = toString(formatFactory()) -fun Meta.toBytes(format: MetaFormat = JsonMetaFormat.default): ByteReadPacket = buildPacket { +fun Meta.toBytes(format: MetaFormat = JsonMetaFormat): ByteReadPacket = buildPacket { format.run { writeObject(this@toBytes) } } 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 cce3eade..5f9164dc 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt @@ -39,7 +39,12 @@ class TaggedEnvelopeFormat( override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) { val metaFormat = metaFormatFactory.invoke(formatMeta, io.context) val metaBytes = metaFormat.writeBytes(envelope.meta) - val tag = Tag(metaFormatFactory.key, metaBytes.size.toUInt() + 2u, envelope.data?.size ?: 0.toULong()) + val actualSize: ULong = if (envelope.data == null) { + 0u + } else { + envelope.data?.size ?: ULong.MAX_VALUE + } + val tag = Tag(metaFormatFactory.key, metaBytes.size.toUInt() + 2u, actualSize) writePacket(tag.toBytes()) writeFully(metaBytes) writeText("\r\n") @@ -134,7 +139,16 @@ class TaggedEnvelopeFormat( } } - val default by lazy { invoke() } + private val default by lazy { invoke() } + + override fun Input.readPartial(): PartialEnvelope = + default.run { readPartial() } + + override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) = + default.run { writeEnvelope(envelope, metaFormatFactory, formatMeta) } + + override fun Input.readObject(): Envelope = + default.run { readObject() } } } \ No newline at end of file diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt index 14d871db..d3953a0c 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt @@ -27,7 +27,13 @@ class TaglessEnvelopeFormat( //printing all properties writeProperty(META_TYPE_PROPERTY, metaFormatFactory.type) //TODO add optional metaFormat properties - writeProperty(DATA_LENGTH_PROPERTY, envelope.data?.size ?: 0) + val actualSize: ULong = if (envelope.data == null) { + 0u + } else { + envelope.data?.size ?: ULong.MAX_VALUE + } + + writeProperty(DATA_LENGTH_PROPERTY, actualSize) //Printing meta if (!envelope.meta.isEmpty()) { @@ -66,7 +72,7 @@ class TaglessEnvelopeFormat( var meta: Meta = EmptyMeta if (line.startsWith(metaStart)) { - val metaFormat = properties[META_TYPE_PROPERTY]?.let { io.metaFormat(it) } ?: JsonMetaFormat.default + val metaFormat = properties[META_TYPE_PROPERTY]?.let { io.metaFormat(it) } ?: JsonMetaFormat val metaSize = properties.get(META_LENGTH_PROPERTY)?.toInt() meta = if (metaSize != null) { val metaPacket = buildPacket { @@ -121,7 +127,7 @@ class TaglessEnvelopeFormat( var meta: Meta = EmptyMeta if (line.startsWith(metaStart)) { - val metaFormat = properties[META_TYPE_PROPERTY]?.let { io.metaFormat(it) } ?: JsonMetaFormat.default + val metaFormat = properties[META_TYPE_PROPERTY]?.let { io.metaFormat(it) } ?: JsonMetaFormat val metaSize = properties.get(META_LENGTH_PROPERTY)?.toInt() meta = if (metaSize != null) { @@ -170,7 +176,16 @@ class TaglessEnvelopeFormat( return TaglessEnvelopeFormat(context.io, meta) } - val default by lazy { invoke() } + private val default by lazy { invoke() } + + override fun Input.readPartial(): PartialEnvelope = + default.run { readPartial() } + + override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) = + default.run { writeEnvelope(envelope, metaFormatFactory, formatMeta) } + + override fun Input.readObject(): Envelope = + default.run { readObject() } override fun peekFormat(io: IOPlugin, input: Input): EnvelopeFormat? { return try { diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopeFormatTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopeFormatTest.kt index 29e60f2f..37ee827d 100644 --- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopeFormatTest.kt +++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopeFormatTest.kt @@ -18,7 +18,7 @@ class EnvelopeFormatTest { @ExperimentalStdlibApi @Test fun testTaggedFormat(){ - TaggedEnvelopeFormat.default.run { + TaggedEnvelopeFormat.run { val bytes = writeBytes(envelope) println(bytes.decodeToString()) val res = readBytes(bytes) @@ -32,7 +32,7 @@ class EnvelopeFormatTest { @Test fun testTaglessFormat(){ - TaglessEnvelopeFormat.default.run { + TaglessEnvelopeFormat.run { val bytes = writeBytes(envelope) println(bytes.decodeToString()) val res = readBytes(bytes) diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopePartsTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopePartsTest.kt index 5122680a..c33b3179 100644 --- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopePartsTest.kt +++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopePartsTest.kt @@ -23,9 +23,9 @@ class EnvelopePartsTest { @Test fun testParts() { - val bytes = TaggedEnvelopeFormat.default.writeBytes(partsEnvelope) - val reconstructed = TaggedEnvelopeFormat.default.readBytes(bytes) - val parts = reconstructed.parts().toList() + val bytes = TaggedEnvelopeFormat.writeBytes(partsEnvelope) + val reconstructed = TaggedEnvelopeFormat.readBytes(bytes) + val parts = reconstructed.parts()?.toList() ?: emptyList() assertEquals(2, parts[2].meta["value"].int) } diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/functionsJVM.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/functionsJVM.kt new file mode 100644 index 00000000..fae986d7 --- /dev/null +++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/functionsJVM.kt @@ -0,0 +1,29 @@ +package hep.dataforge.io + +import hep.dataforge.io.functions.FunctionServer +import hep.dataforge.io.functions.function +import hep.dataforge.meta.Meta +import hep.dataforge.meta.buildMeta +import hep.dataforge.names.Name +import kotlin.reflect.KClass +import kotlin.reflect.full.isSuperclassOf + + +fun IOPlugin.resolveIOFormatName(type: KClass<*>): Name { + return ioFormats.entries.find { it.value.type.isSuperclassOf(type) }?.key + ?: error("Can't resolve IOFormat for type $type") +} + +inline fun <reified T : Any, reified R : Any> IOPlugin.generateFunctionMeta(functionName: String): Meta = buildMeta { + FunctionServer.FUNCTION_NAME_KEY put functionName + FunctionServer.INPUT_FORMAT_KEY put resolveIOFormatName(T::class).toString() + FunctionServer.OUTPUT_FORMAT_KEY put resolveIOFormatName(R::class).toString() +} + +inline fun <reified T : Any, reified R : Any> FunctionServer.function( + functionName: String +): (suspend (T) -> R) { + val plugin = context.plugins.get<IOPlugin>() ?: error("IO plugin not loaded") + val meta = plugin.generateFunctionMeta<T, R>(functionName) + return function(meta) +} \ No newline at end of file diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/ioFormatsJVM.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/ioFormatsJVM.kt index c926d07a..d0ce18b0 100644 --- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/ioFormatsJVM.kt +++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/ioFormatsJVM.kt @@ -1,51 +1,93 @@ package hep.dataforge.io import hep.dataforge.descriptors.NodeDescriptor -import hep.dataforge.io.functions.FunctionServer -import hep.dataforge.io.functions.FunctionServer.Companion.FUNCTION_NAME_KEY -import hep.dataforge.io.functions.FunctionServer.Companion.INPUT_FORMAT_KEY -import hep.dataforge.io.functions.FunctionServer.Companion.OUTPUT_FORMAT_KEY -import hep.dataforge.io.functions.function +import hep.dataforge.meta.DFExperimental +import hep.dataforge.meta.EmptyMeta import hep.dataforge.meta.Meta -import hep.dataforge.meta.buildMeta -import hep.dataforge.names.Name +import kotlinx.io.nio.asInput import kotlinx.io.nio.asOutput import java.nio.file.Files import java.nio.file.Path import java.nio.file.StandardOpenOption -import kotlin.reflect.KClass import kotlin.reflect.full.isSuperclassOf +import kotlin.streams.asSequence inline fun <reified T : Any> IOPlugin.resolveIOFormat(): IOFormat<T>? { return ioFormats.values.find { it.type.isSuperclassOf(T::class) } as IOFormat<T>? } -fun IOPlugin.resolveIOFormatName(type: KClass<*>): Name { - return ioFormats.entries.find { it.value.type.isSuperclassOf(type) }?.key - ?: error("Can't resolve IOFormat for type $type") +/** + * Read file containing meta using given [formatOverride] or file extension to infer meta type. + */ +fun IOPlugin.readMetaFile(path: Path, formatOverride: MetaFormat? = null, descriptor: NodeDescriptor? = null): Meta { + if (!Files.exists(path)) error("Meta file $path does not exist") + val extension = path.fileName.toString().substringAfterLast('.') + + val metaFormat = formatOverride ?: metaFormat(extension) ?: error("Can't resolve meta format $extension") + return metaFormat.run { + Files.newByteChannel(path, StandardOpenOption.READ).asInput().use { it.readMeta(descriptor) } + } } -inline fun <reified T : Any, reified R : Any> IOPlugin.generateFunctionMeta(functionName: String): Meta = buildMeta { - FUNCTION_NAME_KEY put functionName - INPUT_FORMAT_KEY put resolveIOFormatName(T::class).toString() - OUTPUT_FORMAT_KEY put resolveIOFormatName(R::class).toString() -} - -inline fun <reified T : Any, reified R : Any> FunctionServer.function( - functionName: String -): (suspend (T) -> R) { - val plugin = context.plugins.get<IOPlugin>() ?: error("IO plugin not loaded") - val meta = plugin.generateFunctionMeta<T, R>(functionName) - return function(meta) +fun IOPlugin.writeMetaFile( + path: Path, + metaFormat: MetaFormat = JsonMetaFormat, + descriptor: NodeDescriptor? = null +) { + metaFormat.run { + Files.newByteChannel(path, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW).asOutput().use { + it.writeMeta(meta, descriptor) + } + } } /** - * Write meta to file in a given [format] + * Read and envelope from file if the file exists, return null if file does not exist. */ -fun Meta.write(path: Path, format: MetaFormat, descriptor: NodeDescriptor? = null) { - format.run { - Files.newByteChannel(path, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW) - .asOutput() - .writeMeta(this@write, descriptor) +@DFExperimental +fun IOPlugin.readEnvelopeFromFile(path: Path, readNonEnvelopes: Boolean = false): Envelope? { + if (!Files.exists(path)) return null + + //read two-files directory + if (Files.isDirectory(path)) { + val metaFile = Files.list(path).asSequence() + .singleOrNull { it.fileName.toString().startsWith("meta") } + + val meta = if (metaFile == null) { + EmptyMeta + } else { + readMetaFile(metaFile) + } + + val dataFile = path.resolve("data") + + val data: Binary? = if (Files.exists(dataFile)) { + dataFile.asBinary() + } else { + null + } + + return SimpleEnvelope(meta, data) + } + + val binary = path.asBinary() + + val formats = envelopeFormatFactories.mapNotNull { factory -> + binary.read { + factory.peekFormat(this@readEnvelopeFromFile, this@read) + } + } + return when (formats.size) { + 0 -> if (readNonEnvelopes) { + SimpleEnvelope(Meta.empty, binary) + } else { + null + } + 1 -> formats.first().run { + binary.read { + readObject() + } + } + else -> error("Envelope format file recognition clash") } } \ No newline at end of file diff --git a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileBinaryTest.kt b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileBinaryTest.kt index 94403dcd..545601cd 100644 --- a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileBinaryTest.kt +++ b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileBinaryTest.kt @@ -39,7 +39,7 @@ class FileBinaryTest { } val binary = envelopeFromFile.data!! println(binary.toBytes().size) - assertEquals(binary.size.toInt(), binary.toBytes().size) + assertEquals(binary.size?.toInt(), binary.toBytes().size) } @@ -50,7 +50,7 @@ class FileBinaryTest { Global.io.writeEnvelopeFile(tmpPath, envelope) val binary = Global.io.readEnvelopeFile(tmpPath).data!! - assertEquals(binary.size.toInt(), binary.toBytes().size) + assertEquals(binary.size?.toInt(), binary.toBytes().size) } } \ No newline at end of file diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt index 2f717f78..b8f3ffa0 100644 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt @@ -12,7 +12,7 @@ import hep.dataforge.names.toName import kotlin.jvm.JvmName import kotlin.reflect.KClass -@TaskBuildScope +@DFBuilder interface WorkspaceBuilder { val parentContext: Context var context: Context diff --git a/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/fileData.kt b/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/fileData.kt index a483c78b..4fbfa72a 100644 --- a/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/fileData.kt +++ b/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/fileData.kt @@ -4,36 +4,16 @@ import hep.dataforge.data.Data import hep.dataforge.data.DataNode import hep.dataforge.data.DataTreeBuilder import hep.dataforge.data.datum -import hep.dataforge.descriptors.NodeDescriptor import hep.dataforge.io.* import hep.dataforge.meta.EmptyMeta -import hep.dataforge.meta.Meta import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.io.nio.asInput -import kotlinx.io.nio.asOutput import java.nio.file.Files import java.nio.file.Path import java.nio.file.StandardOpenOption import kotlin.reflect.KClass -/** - * Read meta from file in a given [MetaFormat] - */ -fun MetaFormat.readMetaFile(path: Path, descriptor: NodeDescriptor? = null): Meta { - return Files.newByteChannel(path, StandardOpenOption.READ) - .asInput() - .readMeta(descriptor) -} - -/** - * Write meta to file using given [MetaFormat] - */ -fun MetaFormat.writeMetaFile(path: Path, meta: Meta, descriptor: NodeDescriptor? = null) { - return Files.newByteChannel(path, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW) - .asOutput() - .writeMeta(meta, descriptor) -} /** * Read data with supported envelope format and binary format. If envelope format is null, then read binary directly from file. @@ -50,10 +30,10 @@ fun <T : Any> IOPlugin.readData( dataFormat: IOFormat<T>, envelopeFormatFactory: EnvelopeFormatFactory? = null, metaFile: Path = path.resolveSibling("${path.fileName}.meta"), - metaFileFormat: MetaFormat = JsonMetaFormat.default + metaFileFormat: MetaFormat = JsonMetaFormat ): Data<T> { val externalMeta = if (Files.exists(metaFile)) { - metaFileFormat.readMetaFile(metaFile) + readMetaFile(metaFile) } else { null } From 41d0cdb2b157b68ae9bfbdf7242e3ad22f0474f3 Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Wed, 13 Nov 2019 18:08:48 +0300 Subject: [PATCH 05/41] Fix large buffers IO. A lot of refactoring. --- build.gradle.kts | 2 +- .../kotlin/hep/dataforge/data/DataNode.kt | 27 ++++-- .../kotlin/hep/dataforge/data/dataCast.kt | 1 + .../dataforge/data/TypeFilteredDataNode.kt | 2 + .../kotlin/hep/dataforge/data/dataJVM.kt | 10 +-- .../kotlin/hep/dataforge/io/Envelope.kt | 1 + .../hep/dataforge/io/EnvelopeBuilder.kt | 1 + .../kotlin/hep/dataforge/io/EnvelopeParts.kt | 25 +++--- .../kotlin/hep/dataforge/io/IOFormat.kt | 29 +----- .../hep/dataforge/io/TaggedEnvelopeFormat.kt | 5 +- .../hep/dataforge/io/EnvelopePartsTest.kt | 6 +- .../kotlin/hep/dataforge/io/FileEnvelope.kt | 31 +------ .../io/{ioFormatsJVM.kt => fileIO.kt} | 56 ++++++++++-- .../dataforge/io/tcp/InputStreamAsInput.kt | 3 +- .../kotlin/hep/dataforge/io/FileBinaryTest.kt | 4 +- .../hep/dataforge/io/FileEnvelopeTest.kt | 2 +- .../hep/dataforge/workspace/fileData.kt | 90 ++++++++----------- 17 files changed, 147 insertions(+), 148 deletions(-) rename dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/{ioFormatsJVM.kt => fileIO.kt} (56%) diff --git a/build.gradle.kts b/build.gradle.kts index cd3e5d61..7f190853 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("scientifik.publish") version "0.2.2" apply false } -val dataforgeVersion by extra("0.1.5-dev-1") +val dataforgeVersion by extra("0.1.5-dev-2") val bintrayRepo by extra("dataforge") val githubProject by extra("dataforge-core") diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt index 589e875b..65c07676 100644 --- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt @@ -13,16 +13,22 @@ import kotlin.reflect.KClass sealed class DataItem<out T : Any> : MetaRepr { abstract val type: KClass<out T> + abstract val meta: Meta + class Node<out T : Any>(val value: DataNode<T>) : DataItem<T>() { override val type: KClass<out T> get() = value.type override fun toMeta(): Meta = value.toMeta() + + override val meta: Meta get() = value.meta } class Leaf<out T : Any>(val value: Data<T>) : DataItem<T>() { override val type: KClass<out T> get() = value.type override fun toMeta(): Meta = value.toMeta() + + override val meta: Meta get() = value.meta } } @@ -38,6 +44,8 @@ interface DataNode<out T : Any> : MetaRepr { val items: Map<NameToken, DataItem<T>> + val meta: Meta + override fun toMeta(): Meta = buildMeta { "type" put (type.simpleName ?: "undefined") "items" put { @@ -117,12 +125,9 @@ operator fun <T : Any> DataNode<T>.iterator(): Iterator<Pair<Name, DataItem<T>>> class DataTree<out T : Any> internal constructor( override val type: KClass<out T>, - override val items: Map<NameToken, DataItem<T>> -) : DataNode<T> { - override fun toString(): String { - return super.toString() - } -} + override val items: Map<NameToken, DataItem<T>>, + override val meta: Meta +) : DataNode<T> private sealed class DataTreeBuilderItem<out T : Any> { class Node<T : Any>(val tree: DataTreeBuilder<T>) : DataTreeBuilderItem<T>() @@ -136,6 +141,8 @@ private sealed class DataTreeBuilderItem<out T : Any> { class DataTreeBuilder<T : Any>(val type: KClass<out T>) { private val map = HashMap<NameToken, DataTreeBuilderItem<T>>() + private var meta = MetaBuilder() + operator fun set(token: NameToken, node: DataTreeBuilder<out T>) { if (map.containsKey(token)) error("Tree entry with name $token is not empty") map[token] = DataTreeBuilderItem.Node(node) @@ -203,13 +210,19 @@ class DataTreeBuilder<T : Any>(val type: KClass<out T>) { infix fun String.put(block: DataTreeBuilder<T>.() -> Unit) = set(toName(), DataTreeBuilder(type).apply(block)) + /** + * Update data with given node data and meta with node meta. + */ fun update(node: DataNode<T>) { node.dataSequence().forEach { //TODO check if the place is occupied this[it.first] = it.second } + meta.update(node.meta) } + fun meta(block: MetaBuilder.() -> Unit) = meta.apply(block) + fun build(): DataTree<T> { val resMap = map.mapValues { (_, value) -> when (value) { @@ -217,7 +230,7 @@ class DataTreeBuilder<T : Any>(val type: KClass<out T>) { is DataTreeBuilderItem.Node -> DataItem.Node(value.tree.build()) } } - return DataTree(type, resMap) + return DataTree(type, resMap, meta.seal()) } } diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataCast.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataCast.kt index 556b77fc..0b9a4910 100644 --- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataCast.kt +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataCast.kt @@ -52,6 +52,7 @@ inline fun <reified R : Any> Data<*>.cast(): Data<R> = cast(R::class) @Suppress("UNCHECKED_CAST") fun <R : Any> DataNode<*>.cast(type: KClass<out R>): DataNode<R> { return object : DataNode<R> { + override val meta: Meta get() = this@cast.meta override val type: KClass<out R> = type override val items: Map<NameToken, DataItem<R>> get() = this@cast.items as Map<NameToken, DataItem<R>> } diff --git a/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/TypeFilteredDataNode.kt b/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/TypeFilteredDataNode.kt index d24de964..331f3b0e 100644 --- a/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/TypeFilteredDataNode.kt +++ b/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/TypeFilteredDataNode.kt @@ -1,5 +1,6 @@ package hep.dataforge.data +import hep.dataforge.meta.Meta import hep.dataforge.names.NameToken import kotlin.reflect.KClass @@ -8,6 +9,7 @@ import kotlin.reflect.KClass * A zero-copy data node wrapper that returns only children with appropriate type. */ class TypeFilteredDataNode<out T : Any>(val origin: DataNode<*>, override val type: KClass<out T>) : DataNode<T> { + override val meta: Meta get() = origin.meta override val items: Map<NameToken, DataItem<T>> by lazy { origin.items.mapNotNull { (key, item) -> when (item) { diff --git a/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/dataJVM.kt b/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/dataJVM.kt index 5b5507b2..29d048ed 100644 --- a/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/dataJVM.kt +++ b/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/dataJVM.kt @@ -30,12 +30,10 @@ fun <R : Any> Data<*>.filterIsInstance(type: KClass<out R>): Data<R>? = * but could contain empty nodes */ fun <R : Any> DataNode<*>.filterIsInstance(type: KClass<out R>): DataNode<R> { - return if (canCast(type)) { - cast(type) - } else if (this is TypeFilteredDataNode) { - origin.filterIsInstance(type) - } else { - TypeFilteredDataNode(this, type) + return when { + canCast(type) -> cast(type) + this is TypeFilteredDataNode -> origin.filterIsInstance(type) + else -> TypeFilteredDataNode(this, type) } } diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt index 61aeb4d2..7cb918df 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt @@ -21,6 +21,7 @@ interface Envelope { val ENVELOPE_DATA_TYPE_KEY = ENVELOPE_NODE_KEY + "dataType" val ENVELOPE_DATA_ID_KEY = ENVELOPE_NODE_KEY + "dataID" val ENVELOPE_DESCRIPTION_KEY = ENVELOPE_NODE_KEY + "description" + val ENVELOPE_NAME_KEY = ENVELOPE_NODE_KEY + "name" //const val ENVELOPE_TIME_KEY = "@envelope.time" /** diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt index b8d0b660..354b4586 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt @@ -21,6 +21,7 @@ class EnvelopeBuilder { var dataType by metaBuilder.string(key = Envelope.ENVELOPE_DATA_TYPE_KEY) var dataID by metaBuilder.string(key = Envelope.ENVELOPE_DATA_ID_KEY) var description by metaBuilder.string(key = Envelope.ENVELOPE_DESCRIPTION_KEY) + var name by metaBuilder.string(key = Envelope.ENVELOPE_NAME_KEY) /** * Construct a binary and transform it into byte-array based buffer diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt index 0a8a207d..eb1dd696 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt @@ -3,30 +3,31 @@ package hep.dataforge.io import hep.dataforge.context.Global import hep.dataforge.io.EnvelopeParts.FORMAT_META_KEY import hep.dataforge.io.EnvelopeParts.FORMAT_NAME_KEY -import hep.dataforge.io.EnvelopeParts.PARTS_DATA_TYPE +import hep.dataforge.io.EnvelopeParts.MULTIPART_DATA_TYPE import hep.dataforge.io.EnvelopeParts.SIZE_KEY import hep.dataforge.meta.* +import hep.dataforge.names.asName import hep.dataforge.names.plus import hep.dataforge.names.toName object EnvelopeParts { - val SIZE_KEY = Envelope.ENVELOPE_NODE_KEY + "parts" + "size" - val FORMAT_NAME_KEY = Envelope.ENVELOPE_NODE_KEY + "parts" + "format" - val FORMAT_META_KEY = Envelope.ENVELOPE_NODE_KEY + "parts" + "meta" + val MULTIPART_KEY = "multipart".asName() + val SIZE_KEY = Envelope.ENVELOPE_NODE_KEY + MULTIPART_KEY + "size" + val FORMAT_NAME_KEY = Envelope.ENVELOPE_NODE_KEY + MULTIPART_KEY + "format" + val FORMAT_META_KEY = Envelope.ENVELOPE_NODE_KEY + MULTIPART_KEY + "meta" - const val PARTS_DATA_TYPE = "envelope.parts" + const val MULTIPART_DATA_TYPE = "envelope.multipart" } /** * Append multiple serialized envelopes to the data block. Previous data is erased if it was present */ -fun EnvelopeBuilder.parts(formatFactory: EnvelopeFormatFactory, envelopes: Collection<Envelope>) { - dataType = PARTS_DATA_TYPE +fun EnvelopeBuilder.multipart(format: EnvelopeFormatFactory, envelopes: Collection<Envelope>) { + dataType = MULTIPART_DATA_TYPE meta { SIZE_KEY put envelopes.size - FORMAT_NAME_KEY put formatFactory.name.toString() + FORMAT_NAME_KEY put format.name.toString() } - val format = formatFactory() data { format.run { envelopes.forEach { @@ -36,15 +37,15 @@ fun EnvelopeBuilder.parts(formatFactory: EnvelopeFormatFactory, envelopes: Colle } } -fun EnvelopeBuilder.parts(formatFactory: EnvelopeFormatFactory, builder: suspend SequenceScope<Envelope>.() -> Unit) = - parts(formatFactory, sequence(builder).toList()) +fun EnvelopeBuilder.multipart(formatFactory: EnvelopeFormatFactory, builder: suspend SequenceScope<Envelope>.() -> Unit) = + multipart(formatFactory, sequence(builder).toList()) /** * If given envelope supports multipart data, return a sequence of those parts (could be empty). Otherwise return null. */ fun Envelope.parts(io: IOPlugin = Global.plugins.fetch(IOPlugin)): Sequence<Envelope>? { return when (dataType) { - PARTS_DATA_TYPE -> { + MULTIPART_DATA_TYPE -> { val size = meta[SIZE_KEY].int ?: error("Unsized parts not supported yet") val formatName = meta[FORMAT_NAME_KEY].string?.toName() ?: error("Inferring parts format is not supported at the moment") diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt index 093ffbc8..bd1e54f4 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt @@ -16,7 +16,6 @@ import kotlinx.serialization.ImplicitReflectionSerializer import kotlinx.serialization.KSerializer import kotlinx.serialization.cbor.Cbor import kotlinx.serialization.serializer -import kotlin.math.min import kotlin.reflect.KClass /** @@ -80,33 +79,9 @@ inline fun buildPacketWithoutPool(headerSizeHint: Int = 0, block: BytePacketBuil } fun <T : Any> IOFormat<T>.writePacket(obj: T): ByteReadPacket = buildPacket { writeObject(obj) } +//TODO Double buffer copy. fix all that with IO-2 fun <T : Any> IOFormat<T>.writeBytes(obj: T): ByteArray = buildPacket { writeObject(obj) }.readBytes() -fun <T : Any> IOFormat<T>.readBytes(array: ByteArray): T { - //= ByteReadPacket(array).readThis() - val byteArrayInput: Input = object : AbstractInput( - IoBuffer.Pool.borrow(), - remaining = array.size.toLong(), - pool = IoBuffer.Pool - ) { - var written = 0 - override fun closeSource() { - // do nothing - } - - override fun fill(): IoBuffer? { - if (array.size - written <= 0) return null - - return IoBuffer.Pool.fill { - reserveEndGap(IoBuffer.ReservedSize) - val toWrite = min(capacity, array.size - written) - writeFully(array, written, toWrite) - written += toWrite - } - } - - } - return byteArrayInput.readObject() -} +fun <T : Any> IOFormat<T>.readBytes(array: ByteArray): T = buildPacket { writeFully(array) }.readObject() object DoubleIOFormat : IOFormat<Double>, IOFormatFactory<Double> { override fun invoke(meta: Meta, context: Context): IOFormat<Double> = this 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 5f9164dc..a95b7bfb 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt @@ -64,7 +64,10 @@ class TaggedEnvelopeFormat( val metaFormat = io.metaFormat(tag.metaFormatKey) ?: error("Meta format with key ${tag.metaFormatKey} not found") - val metaPacket = ByteReadPacket(readBytes(tag.metaSize.toInt())) + val metaBytes = readBytes(tag.metaSize.toInt()) + val metaPacket = buildPacket { + writeFully(metaBytes) + } val dataBytes = readBytes(tag.dataSize.toInt()) val meta = metaFormat.run { metaPacket.readObject() } diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopePartsTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopePartsTest.kt index c33b3179..d123d632 100644 --- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopePartsTest.kt +++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopePartsTest.kt @@ -14,11 +14,14 @@ class EnvelopePartsTest { } data { writeText("Hello World $it") + repeat(200){ + writeInt(it) + } } } } val partsEnvelope = Envelope { - parts(TaggedEnvelopeFormat, envelopes) + multipart(TaggedEnvelopeFormat, envelopes) } @Test @@ -27,6 +30,7 @@ class EnvelopePartsTest { val reconstructed = TaggedEnvelopeFormat.readBytes(bytes) val parts = reconstructed.parts()?.toList() ?: emptyList() assertEquals(2, parts[2].meta["value"].int) + println(reconstructed.data!!.size) } } \ No newline at end of file diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/FileEnvelope.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/FileEnvelope.kt index 3187cd54..5f21e6ae 100644 --- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/FileEnvelope.kt +++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/FileEnvelope.kt @@ -1,9 +1,7 @@ package hep.dataforge.io -import hep.dataforge.meta.EmptyMeta import hep.dataforge.meta.Meta import kotlinx.io.nio.asInput -import kotlinx.io.nio.asOutput import java.nio.file.Files import java.nio.file.Path import java.nio.file.StandardOpenOption @@ -15,7 +13,7 @@ class FileEnvelope internal constructor(val path: Path, val format: EnvelopeForm init { val input = Files.newByteChannel(path, StandardOpenOption.READ).asInput() - partialEnvelope = format.run { input.use { it.readPartial()} } + partialEnvelope = format.run { input.use { it.readPartial() } } } override val meta: Meta get() = partialEnvelope.meta @@ -23,30 +21,3 @@ class FileEnvelope internal constructor(val path: Path, val format: EnvelopeForm override val data: Binary? = FileBinary(path, partialEnvelope.dataOffset, partialEnvelope.dataSize) } -fun IOPlugin.readEnvelopeFile( - path: Path, - formatFactory: EnvelopeFormatFactory = TaggedEnvelopeFormat, - formatMeta: Meta = EmptyMeta -): FileEnvelope { - val format = formatFactory(formatMeta, context) - return FileEnvelope(path, format) -} - -fun IOPlugin.writeEnvelopeFile( - path: Path, - envelope: Envelope, - formatFactory: EnvelopeFormatFactory = TaggedEnvelopeFormat, - formatMeta: Meta = EmptyMeta -) { - val output = Files.newByteChannel( - path, - StandardOpenOption.WRITE, - StandardOpenOption.CREATE, - StandardOpenOption.TRUNCATE_EXISTING - ).asOutput() - - with(formatFactory(formatMeta, context)) { - output.writeObject(envelope) - } -} - diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/ioFormatsJVM.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt similarity index 56% rename from dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/ioFormatsJVM.kt rename to dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt index d0ce18b0..14e4c077 100644 --- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/ioFormatsJVM.kt +++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt @@ -18,24 +18,41 @@ inline fun <reified T : Any> IOPlugin.resolveIOFormat(): IOFormat<T>? { /** * Read file containing meta using given [formatOverride] or file extension to infer meta type. + * If [path] is a directory search for file starting with `meta` in it */ fun IOPlugin.readMetaFile(path: Path, formatOverride: MetaFormat? = null, descriptor: NodeDescriptor? = null): Meta { if (!Files.exists(path)) error("Meta file $path does not exist") - val extension = path.fileName.toString().substringAfterLast('.') + + val actualPath: Path = if (Files.isDirectory(path)) { + Files.list(path).asSequence().find { it.fileName.startsWith("meta") } + ?: error("The directory $path does not contain meta file") + } else { + path + } + val extension = actualPath.fileName.toString().substringAfterLast('.') val metaFormat = formatOverride ?: metaFormat(extension) ?: error("Can't resolve meta format $extension") return metaFormat.run { - Files.newByteChannel(path, StandardOpenOption.READ).asInput().use { it.readMeta(descriptor) } + Files.newByteChannel(actualPath, StandardOpenOption.READ).asInput().use { it.readMeta(descriptor) } } } +/** + * Write meta to file using [metaFormat]. If [path] is a directory, write a file with name equals name of [metaFormat]. + * Like "meta.json" + */ fun IOPlugin.writeMetaFile( path: Path, - metaFormat: MetaFormat = JsonMetaFormat, + metaFormat: MetaFormatFactory = JsonMetaFormat, descriptor: NodeDescriptor? = null ) { + val actualPath = if (Files.isDirectory(path)) { + path.resolve(metaFormat.name.toString()) + } else { + path + } metaFormat.run { - Files.newByteChannel(path, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW).asOutput().use { + Files.newByteChannel(actualPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW).asOutput().use { it.writeMeta(meta, descriptor) } } @@ -43,9 +60,19 @@ fun IOPlugin.writeMetaFile( /** * Read and envelope from file if the file exists, return null if file does not exist. + * + * If file is directory, then expect two files inside: + * * **meta.<format name>** for meta + * * **data** for data + * + * If the file is envelope read it using [EnvelopeFormatFactory.peekFormat] functionality to infer format. + * + * If the file is not an envelope and [readNonEnvelopes] is true, return an Envelope without meta, using file as binary. + * + * Return null otherwise. */ @DFExperimental -fun IOPlugin.readEnvelopeFromFile(path: Path, readNonEnvelopes: Boolean = false): Envelope? { +fun IOPlugin.readEnvelopeFile(path: Path, readNonEnvelopes: Boolean = false): Envelope? { if (!Files.exists(path)) return null //read two-files directory @@ -74,7 +101,7 @@ fun IOPlugin.readEnvelopeFromFile(path: Path, readNonEnvelopes: Boolean = false) val formats = envelopeFormatFactories.mapNotNull { factory -> binary.read { - factory.peekFormat(this@readEnvelopeFromFile, this@read) + factory.peekFormat(this@readEnvelopeFile, this@read) } } return when (formats.size) { @@ -90,4 +117,21 @@ fun IOPlugin.readEnvelopeFromFile(path: Path, readNonEnvelopes: Boolean = false) } else -> error("Envelope format file recognition clash") } +} + +fun IOPlugin.writeEnvelopeFile( + path: Path, + envelope: Envelope, + format: EnvelopeFormat = TaggedEnvelopeFormat +) { + val output = Files.newByteChannel( + path, + StandardOpenOption.WRITE, + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING + ).asOutput() + + with(format) { + output.writeObject(envelope) + } } \ No newline at end of file diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/InputStreamAsInput.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/InputStreamAsInput.kt index 1c711be0..eb743625 100644 --- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/InputStreamAsInput.kt +++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/InputStreamAsInput.kt @@ -3,7 +3,6 @@ package hep.dataforge.io.tcp import kotlinx.io.core.AbstractInput import kotlinx.io.core.Input import kotlinx.io.core.IoBuffer -import kotlinx.io.core.IoBuffer.Companion.NoPool import kotlinx.io.core.writePacket import kotlinx.io.streams.readPacketAtMost import java.io.InputStream @@ -13,7 +12,7 @@ import java.io.InputStream */ internal class InputStreamAsInput( private val stream: InputStream -) : AbstractInput(pool = NoPool) { +) : AbstractInput(pool = IoBuffer.Pool) { override fun fill(): IoBuffer? { diff --git a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileBinaryTest.kt b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileBinaryTest.kt index 545601cd..d8c7c67a 100644 --- a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileBinaryTest.kt +++ b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileBinaryTest.kt @@ -49,8 +49,8 @@ class FileBinaryTest { val tmpPath = Files.createTempFile("dataforge_test", ".df") Global.io.writeEnvelopeFile(tmpPath, envelope) - val binary = Global.io.readEnvelopeFile(tmpPath).data!! - assertEquals(binary.size?.toInt(), binary.toBytes().size) + val binary = Global.io.readEnvelopeFile(tmpPath)?.data!! + assertEquals(binary.size.toInt(), binary.toBytes().size) } } \ No newline at end of file diff --git a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt index ba7f7cc5..f4847cfd 100644 --- a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt +++ b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt @@ -25,7 +25,7 @@ class FileEnvelopeTest { val tmpPath = Files.createTempFile("dataforge_test", ".df") Global.io.writeEnvelopeFile(tmpPath,envelope) println(tmpPath.toUri()) - val restored: Envelope = Global.io.readEnvelopeFile(tmpPath) + val restored: Envelope = Global.io.readEnvelopeFile(tmpPath)!! assertTrue { envelope.contentEquals(restored) } } } \ No newline at end of file diff --git a/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/fileData.kt b/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/fileData.kt index 4fbfa72a..b5ecb519 100644 --- a/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/fileData.kt +++ b/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/fileData.kt @@ -1,17 +1,15 @@ package hep.dataforge.workspace -import hep.dataforge.data.Data -import hep.dataforge.data.DataNode -import hep.dataforge.data.DataTreeBuilder -import hep.dataforge.data.datum -import hep.dataforge.io.* -import hep.dataforge.meta.EmptyMeta -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import kotlinx.io.nio.asInput +import hep.dataforge.data.* +import hep.dataforge.io.Envelope +import hep.dataforge.io.IOFormat +import hep.dataforge.io.IOPlugin +import hep.dataforge.io.readEnvelopeFile +import hep.dataforge.meta.Meta +import hep.dataforge.meta.get +import hep.dataforge.meta.string import java.nio.file.Files import java.nio.file.Path -import java.nio.file.StandardOpenOption import kotlin.reflect.KClass @@ -20,54 +18,30 @@ import kotlin.reflect.KClass * The operation is blocking since it must read meta header. The reading of envelope body is lazy * @param type explicit type of data read * @param dataFormat binary format - * @param envelopeFormatFactory the format of envelope. If null, file is read directly + * @param envelopeFormat the format of envelope. If null, file is read directly * @param metaFile the relative file for optional meta override * @param metaFileFormat the meta format for override */ -fun <T : Any> IOPlugin.readData( +fun <T : Any> IOPlugin.readDataFile( path: Path, type: KClass<out T>, - dataFormat: IOFormat<T>, - envelopeFormatFactory: EnvelopeFormatFactory? = null, - metaFile: Path = path.resolveSibling("${path.fileName}.meta"), - metaFileFormat: MetaFormat = JsonMetaFormat + formatResolver: (Meta) -> IOFormat<T> ): Data<T> { - val externalMeta = if (Files.exists(metaFile)) { - readMetaFile(metaFile) - } else { - null - } - return if (envelopeFormatFactory == null) { - Data(type, externalMeta ?: EmptyMeta) { - withContext(Dispatchers.IO) { - dataFormat.run { - Files.newByteChannel(path, StandardOpenOption.READ) - .asInput() - .readObject() - } - } - } - } else { - readEnvelopeFile(path, envelopeFormatFactory).let { - if (externalMeta == null) { - it - } else { - it.withMetaLayers(externalMeta) - } - }.toData(type, dataFormat) - } + val envelope = readEnvelopeFile(path, true) ?: error("Can't read data from $path") + val format = formatResolver(envelope.meta) + return envelope.toData(type, format) } //TODO wants multi-receiver fun <T : Any> DataTreeBuilder<T>.file( plugin: IOPlugin, path: Path, - dataFormat: IOFormat<T>, - envelopeFormatFactory: EnvelopeFormatFactory? = null + formatResolver: (Meta) -> IOFormat<T> ) { plugin.run { - val data = readData(path, type, dataFormat, envelopeFormatFactory) - val name = path.fileName.toString().replace(".df", "") + val data = readDataFile(path, type, formatResolver) + val name = data.meta[Envelope.ENVELOPE_NAME_KEY].string + ?: path.fileName.toString().replace(".df", "") datum(name, data) } } @@ -75,23 +49,35 @@ fun <T : Any> DataTreeBuilder<T>.file( /** * Read the directory as a data node */ -fun <T : Any> IOPlugin.readDataNode( +fun <T : Any> IOPlugin.readDataDirectory( path: Path, type: KClass<out T>, - dataFormat: IOFormat<T>, - envelopeFormatFactory: EnvelopeFormatFactory? = null + formatResolver: (Meta) -> IOFormat<T> ): DataNode<T> { if (!Files.isDirectory(path)) error("Provided path $this is not a directory") return DataNode(type) { Files.list(path).forEach { path -> if (!path.fileName.toString().endsWith(".meta")) { - file(this@readDataNode,path, dataFormat, envelopeFormatFactory) + file(this@readDataDirectory, path, formatResolver) } } } } -//suspend fun <T : Any> Path.writeData( -// data: Data<T>, -// format: IOFormat<T>, -// ) \ No newline at end of file +fun <T : Any> DataTreeBuilder<T>.directory( + plugin: IOPlugin, + path: Path, + formatResolver: (Meta) -> IOFormat<T> +) { + plugin.run { + val data = readDataDirectory(path, type, formatResolver) + val name = data.meta[Envelope.ENVELOPE_NAME_KEY].string + ?: path.fileName.toString().replace(".df", "") + node(name, data) + } +} + + + + + From d10bd40763605d733e94ac35234e2e4403e5a108 Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Wed, 13 Nov 2019 18:24:33 +0300 Subject: [PATCH 06/41] Remove use of direct Input to ByteArray conversions due to bugs in kotlinx.io. --- build.gradle.kts | 2 +- .../src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt | 4 +++- .../kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt | 6 +++--- .../kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt | 6 +++--- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 7f190853..430fcda4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("scientifik.publish") version "0.2.2" apply false } -val dataforgeVersion by extra("0.1.5-dev-2") +val dataforgeVersion by extra("0.1.5-dev-3") val bintrayRepo by extra("dataforge") val githubProject by extra("dataforge-core") diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt index bd1e54f4..e5497365 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt @@ -79,8 +79,10 @@ inline fun buildPacketWithoutPool(headerSizeHint: Int = 0, block: BytePacketBuil } fun <T : Any> IOFormat<T>.writePacket(obj: T): ByteReadPacket = buildPacket { writeObject(obj) } -//TODO Double buffer copy. fix all that with IO-2 + +@Deprecated("Not to be used outside tests due to double buffer write") fun <T : Any> IOFormat<T>.writeBytes(obj: T): ByteArray = buildPacket { writeObject(obj) }.readBytes() +@Deprecated("Not to be used outside tests due to double buffer write") fun <T : Any> IOFormat<T>.readBytes(array: ByteArray): T = buildPacket { writeFully(array) }.readObject() object DoubleIOFormat : IOFormat<Double>, IOFormatFactory<Double> { 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 a95b7bfb..a461d257 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt @@ -38,15 +38,15 @@ class TaggedEnvelopeFormat( override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) { val metaFormat = metaFormatFactory.invoke(formatMeta, io.context) - val metaBytes = metaFormat.writeBytes(envelope.meta) + val metaBytes = metaFormat.writePacket(envelope.meta) val actualSize: ULong = if (envelope.data == null) { 0u } else { envelope.data?.size ?: ULong.MAX_VALUE } - val tag = Tag(metaFormatFactory.key, metaBytes.size.toUInt() + 2u, actualSize) + val tag = Tag(metaFormatFactory.key, metaBytes.remaining.toUInt() + 2u, actualSize) writePacket(tag.toBytes()) - writeFully(metaBytes) + writePacket(metaBytes) writeText("\r\n") envelope.data?.read { copyTo(this@writeEnvelope) } flush() diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt index d3953a0c..5c4b7dae 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt @@ -37,10 +37,10 @@ class TaglessEnvelopeFormat( //Printing meta if (!envelope.meta.isEmpty()) { - val metaBytes = metaFormat.writeBytes(envelope.meta) - writeProperty(META_LENGTH_PROPERTY, metaBytes.size) + val metaBytes = metaFormat.writePacket(envelope.meta) + writeProperty(META_LENGTH_PROPERTY, metaBytes.remaining) writeText(metaStart + "\r\n") - writeFully(metaBytes) + writePacket(metaBytes) writeText("\r\n") } From 4370a66164d728a2293f2345afedf6bf304cd920 Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Wed, 13 Nov 2019 21:12:19 +0300 Subject: [PATCH 07/41] Fixed TaglessEnvelopeFormat resolution --- .../kotlin/hep/dataforge/io/EnvelopeParts.kt | 35 ++++++++++++++- .../kotlin/hep/dataforge/io/IOPlugin.kt | 2 +- .../hep/dataforge/io/TaglessEnvelopeFormat.kt | 3 +- .../jvmMain/kotlin/hep/dataforge/io/fileIO.kt | 45 ++++++++++++------- .../hep/dataforge/io/FileEnvelopeTest.kt | 23 +++++++--- 5 files changed, 82 insertions(+), 26 deletions(-) diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt index eb1dd696..d1b86195 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt @@ -3,6 +3,7 @@ package hep.dataforge.io import hep.dataforge.context.Global import hep.dataforge.io.EnvelopeParts.FORMAT_META_KEY import hep.dataforge.io.EnvelopeParts.FORMAT_NAME_KEY +import hep.dataforge.io.EnvelopeParts.INDEX_KEY import hep.dataforge.io.EnvelopeParts.MULTIPART_DATA_TYPE import hep.dataforge.io.EnvelopeParts.SIZE_KEY import hep.dataforge.meta.* @@ -13,6 +14,7 @@ import hep.dataforge.names.toName object EnvelopeParts { val MULTIPART_KEY = "multipart".asName() val SIZE_KEY = Envelope.ENVELOPE_NODE_KEY + MULTIPART_KEY + "size" + val INDEX_KEY = Envelope.ENVELOPE_NODE_KEY + MULTIPART_KEY + "index" val FORMAT_NAME_KEY = Envelope.ENVELOPE_NODE_KEY + MULTIPART_KEY + "format" val FORMAT_META_KEY = Envelope.ENVELOPE_NODE_KEY + MULTIPART_KEY + "meta" @@ -37,8 +39,37 @@ fun EnvelopeBuilder.multipart(format: EnvelopeFormatFactory, envelopes: Collecti } } -fun EnvelopeBuilder.multipart(formatFactory: EnvelopeFormatFactory, builder: suspend SequenceScope<Envelope>.() -> Unit) = - multipart(formatFactory, sequence(builder).toList()) +/** + * Create a multipart partition in the envelope adding additional name-index mapping in meta + */ +@DFExperimental +fun EnvelopeBuilder.multipart(format: EnvelopeFormatFactory, envelopes: Map<String, Envelope>) { + dataType = MULTIPART_DATA_TYPE + meta { + SIZE_KEY put envelopes.size + FORMAT_NAME_KEY put format.name.toString() + } + data { + format.run { + var counter = 0 + envelopes.forEach {(key, envelope)-> + writeObject(envelope) + meta{ + append(INDEX_KEY, buildMeta { + "key" put key + "index" put counter + }) + } + counter++ + } + } + } +} + +fun EnvelopeBuilder.multipart( + formatFactory: EnvelopeFormatFactory, + builder: suspend SequenceScope<Envelope>.() -> Unit +) = multipart(formatFactory, sequence(builder).toList()) /** * If given envelope supports multipart data, return a sequence of those parts (could be empty). Otherwise return null. diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt index ae88e4f4..a463a053 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt @@ -52,7 +52,7 @@ class IOPlugin(meta: Meta) : AbstractPlugin(meta) { companion object : PluginFactory<IOPlugin> { val defaultMetaFormats: List<MetaFormatFactory> = listOf(JsonMetaFormat, BinaryMetaFormat) - val defaultEnvelopeFormats = listOf(TaggedEnvelopeFormat) + val defaultEnvelopeFormats = listOf(TaggedEnvelopeFormat, TaglessEnvelopeFormat) override val tag: PluginTag = PluginTag("io", group = PluginTag.DATAFORGE_GROUP) diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt index 5c4b7dae..1cc62a2b 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt @@ -49,6 +49,7 @@ class TaglessEnvelopeFormat( writeText(dataStart + "\r\n") writeFully(data.toBytes()) } + flush() } override fun Input.readObject(): Envelope { @@ -191,7 +192,7 @@ class TaglessEnvelopeFormat( return try { val buffer = ByteArray(TAGLESS_ENVELOPE_HEADER.length) input.readFully(buffer) - return if (buffer.toString() == TAGLESS_ENVELOPE_HEADER) { + return if (String(buffer) == TAGLESS_ENVELOPE_HEADER) { TaglessEnvelopeFormat(io) } else { null diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt index 14e4c077..eff9e705 100644 --- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt +++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt @@ -58,6 +58,24 @@ fun IOPlugin.writeMetaFile( } } +/** + * Return inferred [EnvelopeFormat] if only one format could read given file. If no format accepts file, return null. If + * multiple formats accepts file, throw an error. + */ +fun IOPlugin.peekBinaryFormat(binary: Binary): EnvelopeFormat? { + val formats = envelopeFormatFactories.mapNotNull { factory -> + binary.read { + factory.peekFormat(this@peekBinaryFormat, this@read) + } + } + + return when (formats.size) { + 0 -> null + 1 -> formats.first() + else -> error("Envelope format binary recognition clash") + } +} + /** * Read and envelope from file if the file exists, return null if file does not exist. * @@ -72,7 +90,11 @@ fun IOPlugin.writeMetaFile( * Return null otherwise. */ @DFExperimental -fun IOPlugin.readEnvelopeFile(path: Path, readNonEnvelopes: Boolean = false): Envelope? { +fun IOPlugin.readEnvelopeFile( + path: Path, + readNonEnvelopes: Boolean = false, + formatPeeker: IOPlugin.(Binary) -> EnvelopeFormat? = IOPlugin::peekBinaryFormat +): Envelope? { if (!Files.exists(path)) return null //read two-files directory @@ -99,24 +121,13 @@ fun IOPlugin.readEnvelopeFile(path: Path, readNonEnvelopes: Boolean = false): En val binary = path.asBinary() - val formats = envelopeFormatFactories.mapNotNull { factory -> + return formatPeeker(binary)?.run { binary.read { - factory.peekFormat(this@readEnvelopeFile, this@read) + readObject() } - } - return when (formats.size) { - 0 -> if (readNonEnvelopes) { - SimpleEnvelope(Meta.empty, binary) - } else { - null - } - 1 -> formats.first().run { - binary.read { - readObject() - } - } - else -> error("Envelope format file recognition clash") - } + } ?: if (readNonEnvelopes) { // if no format accepts file, read it as binary + SimpleEnvelope(Meta.empty, binary) + } else null } fun IOPlugin.writeEnvelopeFile( diff --git a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt index f4847cfd..b8bfafaa 100644 --- a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt +++ b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt @@ -22,10 +22,23 @@ class FileEnvelopeTest { @Test fun testFileWriteRead() { - val tmpPath = Files.createTempFile("dataforge_test", ".df") - Global.io.writeEnvelopeFile(tmpPath,envelope) - println(tmpPath.toUri()) - val restored: Envelope = Global.io.readEnvelopeFile(tmpPath)!! - assertTrue { envelope.contentEquals(restored) } + Global.io.run { + val tmpPath = Files.createTempFile("dataforge_test", ".df") + writeEnvelopeFile(tmpPath, envelope) + println(tmpPath.toUri()) + val restored: Envelope = readEnvelopeFile(tmpPath)!! + assertTrue { envelope.contentEquals(restored) } + } + } + + @Test + fun testFileWriteReadTagless() { + Global.io.run { + val tmpPath = Files.createTempFile("dataforge_test_tagless", ".df") + writeEnvelopeFile(tmpPath, envelope, format = TaglessEnvelopeFormat) + println(tmpPath.toUri()) + val restored: Envelope = readEnvelopeFile(tmpPath)!! + assertTrue { envelope.contentEquals(restored) } + } } } \ No newline at end of file From 45a5b6fe28318b951dfe598219d2750e27702c1e Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Sun, 17 Nov 2019 22:15:29 +0300 Subject: [PATCH 08/41] Working on zip and directory storage for data. Update to build 0.2.4 --- .github/workflows/gradle.yml | 2 +- build.gradle.kts | 6 +- .../kotlin/hep/dataforge/data/DataNode.kt | 38 ++-- .../kotlin/hep/dataforge/data/dataCast.kt | 4 +- .../dataforge/data/TypeFilteredDataNode.kt | 4 +- .../kotlin/hep/dataforge/data/dataJVM.kt | 4 +- .../dataforge-io-yaml/build.gradle.kts | 2 - .../dataforge/io/yaml/YamlMetaFormatTest.kt | 2 +- .../kotlin/hep/dataforge/io/Binary.kt | 8 +- .../kotlin/hep/dataforge/io/Envelope.kt | 1 - .../kotlin/hep/dataforge/io/EnvelopeFormat.kt | 8 +- .../kotlin/hep/dataforge/io/IOPlugin.kt | 2 +- .../jvmMain/kotlin/hep/dataforge/io/fileIO.kt | 101 ++++++++--- .../hep/dataforge/io/FileEnvelopeTest.kt | 2 +- .../dataforge/io/tcp/EnvelopeServerTest.kt | 10 +- dataforge-scripting/build.gradle.kts | 2 - .../hep/dataforge/scripting/BuildersKtTest.kt | 2 +- .../hep/dataforge/workspace/dataUtils.kt | 14 -- .../hep/dataforge/workspace/envelopeData.kt | 33 ++++ .../hep/dataforge/workspace/fileData.kt | 162 ++++++++++++++---- .../workspace/DataPropagationTest.kt | 2 +- .../hep/dataforge/workspace/FileDataTest.kt | 72 ++++++++ .../workspace/SimpleWorkspaceTest.kt | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 55616 -> 58702 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 29 ++-- 26 files changed, 383 insertions(+), 131 deletions(-) delete mode 100644 dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/dataUtils.kt create mode 100644 dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt create mode 100644 dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/FileDataTest.kt mode change 100644 => 100755 gradlew diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 85ea102d..adc74adf 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -1,4 +1,4 @@ -name: Java CI +name: Gradle build on: [push] diff --git a/build.gradle.kts b/build.gradle.kts index 430fcda4..cf2f4387 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,9 +1,9 @@ import scientifik.ScientifikExtension plugins { - id("scientifik.mpp") version "0.2.2" apply false - id("scientifik.jvm") version "0.2.2" apply false - id("scientifik.publish") version "0.2.2" apply false + id("scientifik.mpp") version "0.2.4" apply false + id("scientifik.jvm") version "0.2.4" apply false + id("scientifik.publish") version "0.2.4" apply false } val dataforgeVersion by extra("0.1.5-dev-3") diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt index 65c07676..a673f0b7 100644 --- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt @@ -15,20 +15,20 @@ sealed class DataItem<out T : Any> : MetaRepr { abstract val meta: Meta - class Node<out T : Any>(val value: DataNode<T>) : DataItem<T>() { - override val type: KClass<out T> get() = value.type + class Node<out T : Any>(val node: DataNode<T>) : DataItem<T>() { + override val type: KClass<out T> get() = node.type - override fun toMeta(): Meta = value.toMeta() + override fun toMeta(): Meta = node.toMeta() - override val meta: Meta get() = value.meta + override val meta: Meta get() = node.meta } - class Leaf<out T : Any>(val value: Data<T>) : DataItem<T>() { - override val type: KClass<out T> get() = value.type + class Leaf<out T : Any>(val data: Data<T>) : DataItem<T>() { + override val type: KClass<out T> get() = data.type - override fun toMeta(): Meta = value.toMeta() + override fun toMeta(): Meta = data.toMeta() - override val meta: Meta get() = value.meta + override val meta: Meta get() = data.meta } } @@ -68,8 +68,8 @@ interface DataNode<out T : Any> : MetaRepr { } } -val <T : Any> DataItem<T>?.node: DataNode<T>? get() = (this as? DataItem.Node<T>)?.value -val <T : Any> DataItem<T>?.data: Data<T>? get() = (this as? DataItem.Leaf<T>)?.value +val <T : Any> DataItem<T>?.node: DataNode<T>? get() = (this as? DataItem.Node<T>)?.node +val <T : Any> DataItem<T>?.data: Data<T>? get() = (this as? DataItem.Leaf<T>)?.data /** * Start computation for all goals in data node and return a job for the whole node @@ -77,8 +77,8 @@ val <T : Any> DataItem<T>?.data: Data<T>? get() = (this as? DataItem.Leaf<T>)?.v fun DataNode<*>.launchAll(scope: CoroutineScope): Job = scope.launch { items.values.forEach { when (it) { - is DataItem.Node<*> -> it.value.launchAll(scope) - is DataItem.Leaf<*> -> it.value.start(scope) + is DataItem.Node<*> -> it.node.launchAll(scope) + is DataItem.Leaf<*> -> it.data.start(scope) } } } @@ -98,7 +98,7 @@ fun <T : Any> DataNode<T>.asSequence(): Sequence<Pair<Name, DataItem<T>>> = sequ items.forEach { (head, item) -> yield(head.asName() to item) if (item is DataItem.Node) { - val subSequence = item.value.asSequence() + val subSequence = item.node.asSequence() .map { (name, data) -> (head.asName() + name) to data } yieldAll(subSequence) } @@ -111,9 +111,9 @@ fun <T : Any> DataNode<T>.asSequence(): Sequence<Pair<Name, DataItem<T>>> = sequ fun <T : Any> DataNode<T>.dataSequence(): Sequence<Pair<Name, Data<T>>> = sequence { items.forEach { (head, item) -> when (item) { - is DataItem.Leaf -> yield(head.asName() to item.value) + is DataItem.Leaf -> yield(head.asName() to item.data) is DataItem.Node -> { - val subSequence = item.value.dataSequence() + val subSequence = item.node.dataSequence() .map { (name, data) -> (head.asName() + name) to data } yieldAll(subSequence) } @@ -188,8 +188,8 @@ class DataTreeBuilder<T : Any>(val type: KClass<out T>) { operator fun set(name: Name, node: DataNode<T>) = set(name, node.builder()) operator fun set(name: Name, item: DataItem<T>) = when (item) { - is DataItem.Node<T> -> set(name, item.value.builder()) - is DataItem.Leaf<T> -> set(name, item.value) + is DataItem.Node<T> -> set(name, item.node.builder()) + is DataItem.Leaf<T> -> set(name, item.data) } /** @@ -223,6 +223,10 @@ class DataTreeBuilder<T : Any>(val type: KClass<out T>) { fun meta(block: MetaBuilder.() -> Unit) = meta.apply(block) + fun meta(meta: Meta) { + this.meta = meta.builder() + } + fun build(): DataTree<T> { val resMap = map.mapValues { (_, value) -> when (value) { diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataCast.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataCast.kt index 0b9a4910..2bf8adde 100644 --- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataCast.kt +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataCast.kt @@ -28,8 +28,8 @@ expect fun <R : Any> DataNode<*>.canCast(type: KClass<out R>): Boolean expect fun <R : Any> Data<*>.canCast(type: KClass<out R>): Boolean fun <R : Any> DataItem<*>.canCast(type: KClass<out R>): Boolean = when (this) { - is DataItem.Node -> value.canCast(type) - is DataItem.Leaf -> value.canCast(type) + is DataItem.Node -> node.canCast(type) + is DataItem.Leaf -> data.canCast(type) } /** diff --git a/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/TypeFilteredDataNode.kt b/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/TypeFilteredDataNode.kt index 331f3b0e..3590679c 100644 --- a/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/TypeFilteredDataNode.kt +++ b/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/TypeFilteredDataNode.kt @@ -14,12 +14,12 @@ class TypeFilteredDataNode<out T : Any>(val origin: DataNode<*>, override val ty origin.items.mapNotNull { (key, item) -> when (item) { is DataItem.Leaf -> { - (item.value.filterIsInstance(type))?.let { + (item.data.filterIsInstance(type))?.let { key to DataItem.Leaf(it) } } is DataItem.Node -> { - key to DataItem.Node(item.value.filterIsInstance(type)) + key to DataItem.Node(item.node.filterIsInstance(type)) } } }.associate { it } diff --git a/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/dataJVM.kt b/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/dataJVM.kt index 29d048ed..f354c2f7 100644 --- a/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/dataJVM.kt +++ b/dataforge-data/src/jvmMain/kotlin/hep/dataforge/data/dataJVM.kt @@ -42,8 +42,8 @@ fun <R : Any> DataNode<*>.filterIsInstance(type: KClass<out R>): DataNode<R> { */ fun <R : Any> DataItem<*>?.filterIsInstance(type: KClass<out R>): DataItem<R>? = when (this) { null -> null - is DataItem.Node -> DataItem.Node(this.value.filterIsInstance(type)) - is DataItem.Leaf -> this.value.filterIsInstance(type)?.let { DataItem.Leaf(it) } + is DataItem.Node -> DataItem.Node(this.node.filterIsInstance(type)) + is DataItem.Leaf -> this.data.filterIsInstance(type)?.let { DataItem.Leaf(it) } } inline fun <reified R : Any> DataItem<*>?.filterIsInstance(): DataItem<R>? = this@filterIsInstance.filterIsInstance(R::class) \ No newline at end of file diff --git a/dataforge-io/dataforge-io-yaml/build.gradle.kts b/dataforge-io/dataforge-io-yaml/build.gradle.kts index 74ba43cf..d287d9ac 100644 --- a/dataforge-io/dataforge-io-yaml/build.gradle.kts +++ b/dataforge-io/dataforge-io-yaml/build.gradle.kts @@ -7,6 +7,4 @@ description = "YAML meta IO" dependencies { api(project(":dataforge-io")) api("org.yaml:snakeyaml:1.25") - testImplementation(kotlin("test")) - testImplementation(kotlin("test-junit")) } diff --git a/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt b/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt index 414162f7..5c5b3c18 100644 --- a/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt +++ b/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt @@ -6,7 +6,7 @@ import hep.dataforge.meta.Meta import hep.dataforge.meta.buildMeta import hep.dataforge.meta.get import hep.dataforge.meta.seal -import org.junit.Test +import org.junit.jupiter.api.Test import kotlin.test.assertEquals diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Binary.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Binary.kt index bd1f2249..b671928b 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Binary.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Binary.kt @@ -10,7 +10,7 @@ interface Binary { /** * The size of binary in bytes. [ULong.MAX_VALUE] if size is not defined and input should be read until its end is reached */ - val size: ULong + val size: ULong get() = ULong.MAX_VALUE /** * Read continuous [Input] from this binary stating from the beginning. @@ -41,7 +41,11 @@ interface RandomAccessBinary : Binary { } fun Binary.toBytes(): ByteArray = read { - this.readBytes() + readBytes() +} + +fun Binary.contentToString(): String = read { + readText() } @ExperimentalUnsignedTypes diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt index 7cb918df..abf8a504 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt @@ -83,4 +83,3 @@ fun Envelope.withMetaLayers(vararg layers: Meta): Envelope { else -> ProxyEnvelope(this, *layers) } } - 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 4f747ea2..49a25919 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeFormat.kt @@ -23,11 +23,15 @@ interface EnvelopeFormat : IOFormat<Envelope> { fun Input.readPartial(): PartialEnvelope - fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta = EmptyMeta) + fun Output.writeEnvelope( + envelope: Envelope, + metaFormatFactory: MetaFormatFactory = defaultMetaFormat, + formatMeta: Meta = EmptyMeta + ) override fun Input.readObject(): Envelope - override fun Output.writeObject(obj: Envelope): Unit = writeEnvelope(obj, defaultMetaFormat) + override fun Output.writeObject(obj: Envelope): Unit = writeEnvelope(obj) } @Type(ENVELOPE_FORMAT_TYPE) diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt index a463a053..eb975029 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt @@ -20,7 +20,7 @@ class IOPlugin(meta: Meta) : AbstractPlugin(meta) { metaFormatFactories.find { it.key == key }?.invoke(meta) fun metaFormat(name: String, meta: Meta = EmptyMeta): MetaFormat? = - metaFormatFactories.find { it.name.toString() == name }?.invoke(meta) + metaFormatFactories.find { it.name.last().toString() == name }?.invoke(meta) val envelopeFormatFactories by lazy { context.content<EnvelopeFormatFactory>(ENVELOPE_FORMAT_TYPE).values diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt index eff9e705..9203d306 100644 --- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt +++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt @@ -4,6 +4,9 @@ import hep.dataforge.descriptors.NodeDescriptor import hep.dataforge.meta.DFExperimental import hep.dataforge.meta.EmptyMeta import hep.dataforge.meta.Meta +import hep.dataforge.meta.isEmpty +import kotlinx.io.core.Output +import kotlinx.io.core.copyTo import kotlinx.io.nio.asInput import kotlinx.io.nio.asOutput import java.nio.file.Files @@ -12,10 +15,15 @@ import java.nio.file.StandardOpenOption import kotlin.reflect.full.isSuperclassOf import kotlin.streams.asSequence +/** + * Resolve IOFormat based on type + */ +@DFExperimental inline fun <reified T : Any> IOPlugin.resolveIOFormat(): IOFormat<T>? { return ioFormats.values.find { it.type.isSuperclassOf(T::class) } as IOFormat<T>? } + /** * Read file containing meta using given [formatOverride] or file extension to infer meta type. * If [path] is a directory search for file starting with `meta` in it @@ -43,11 +51,12 @@ fun IOPlugin.readMetaFile(path: Path, formatOverride: MetaFormat? = null, descri */ fun IOPlugin.writeMetaFile( path: Path, + meta: Meta, metaFormat: MetaFormatFactory = JsonMetaFormat, descriptor: NodeDescriptor? = null ) { val actualPath = if (Files.isDirectory(path)) { - path.resolve(metaFormat.name.toString()) + path.resolve("@" + metaFormat.name.toString()) } else { path } @@ -62,7 +71,8 @@ fun IOPlugin.writeMetaFile( * Return inferred [EnvelopeFormat] if only one format could read given file. If no format accepts file, return null. If * multiple formats accepts file, throw an error. */ -fun IOPlugin.peekBinaryFormat(binary: Binary): EnvelopeFormat? { +fun IOPlugin.peekBinaryFormat(path: Path): EnvelopeFormat? { + val binary = path.asBinary() val formats = envelopeFormatFactories.mapNotNull { factory -> binary.read { factory.peekFormat(this@peekBinaryFormat, this@read) @@ -76,6 +86,9 @@ fun IOPlugin.peekBinaryFormat(binary: Binary): EnvelopeFormat? { } } +val IOPlugin.Companion.META_FILE_NAME: String get() = "@meta" +val IOPlugin.Companion.DATA_FILE_NAME: String get() = "@data" + /** * Read and envelope from file if the file exists, return null if file does not exist. * @@ -93,14 +106,14 @@ fun IOPlugin.peekBinaryFormat(binary: Binary): EnvelopeFormat? { fun IOPlugin.readEnvelopeFile( path: Path, readNonEnvelopes: Boolean = false, - formatPeeker: IOPlugin.(Binary) -> EnvelopeFormat? = IOPlugin::peekBinaryFormat + formatPeeker: IOPlugin.(Path) -> EnvelopeFormat? = IOPlugin::peekBinaryFormat ): Envelope? { if (!Files.exists(path)) return null //read two-files directory if (Files.isDirectory(path)) { val metaFile = Files.list(path).asSequence() - .singleOrNull { it.fileName.toString().startsWith("meta") } + .singleOrNull { it.fileName.toString().startsWith(IOPlugin.META_FILE_NAME) } val meta = if (metaFile == null) { EmptyMeta @@ -108,7 +121,7 @@ fun IOPlugin.readEnvelopeFile( readMetaFile(metaFile) } - val dataFile = path.resolve("data") + val dataFile = path.resolve(IOPlugin.DATA_FILE_NAME) val data: Binary? = if (Files.exists(dataFile)) { dataFile.asBinary() @@ -119,30 +132,76 @@ fun IOPlugin.readEnvelopeFile( return SimpleEnvelope(meta, data) } - val binary = path.asBinary() - - return formatPeeker(binary)?.run { - binary.read { - readObject() - } + return formatPeeker(path)?.let { format -> + FileEnvelope(path, format) } ?: if (readNonEnvelopes) { // if no format accepts file, read it as binary - SimpleEnvelope(Meta.empty, binary) + SimpleEnvelope(Meta.empty, path.asBinary()) } else null } +private fun Path.useOutput(consumer: Output.() -> Unit) { + //TODO forbid rewrite? + Files.newByteChannel( + this, + StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING + ).asOutput().use { + it.consumer() + it.flush() + } +} + +/** + * Write a binary into file. Throws an error if file already exists + */ +fun <T : Any> IOFormat<T>.writeToFile(path: Path, obj: T) { + path.useOutput { + writeObject(obj) + flush() + } +} + +/** + * Write envelope file to given [path] using [envelopeFormat] and optional [metaFormat] + */ +@DFExperimental fun IOPlugin.writeEnvelopeFile( path: Path, envelope: Envelope, - format: EnvelopeFormat = TaggedEnvelopeFormat + envelopeFormat: EnvelopeFormat = TaggedEnvelopeFormat, + metaFormat: MetaFormatFactory? = null ) { - val output = Files.newByteChannel( - path, - StandardOpenOption.WRITE, - StandardOpenOption.CREATE, - StandardOpenOption.TRUNCATE_EXISTING - ).asOutput() + path.useOutput { + with(envelopeFormat) { + writeEnvelope(envelope, metaFormat ?: envelopeFormat.defaultMetaFormat) + } + } +} - with(format) { - output.writeObject(envelope) +/** + * Write separate meta and data files to given directory [path] + */ +@DFExperimental +fun IOPlugin.writeEnvelopeDirectory( + path: Path, + envelope: Envelope, + metaFormat: MetaFormatFactory = JsonMetaFormat +) { + if (!Files.exists(path)) { + Files.createDirectories(path) + } + if (!Files.isDirectory(path)) { + error("Can't write envelope directory to file") + } + if (!envelope.meta.isEmpty()) { + writeMetaFile(path, envelope.meta, metaFormat) + } + val dataFile = path.resolve(IOPlugin.DATA_FILE_NAME) + dataFile.useOutput { + envelope.data?.read { + val copied = copyTo(this@useOutput) + if (envelope.data?.size != ULong.MAX_VALUE && copied != envelope.data?.size?.toLong()) { + error("The number of copied bytes does not equal data size") + } + } } } \ No newline at end of file diff --git a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt index b8bfafaa..585aced9 100644 --- a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt +++ b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt @@ -35,7 +35,7 @@ class FileEnvelopeTest { fun testFileWriteReadTagless() { Global.io.run { val tmpPath = Files.createTempFile("dataforge_test_tagless", ".df") - writeEnvelopeFile(tmpPath, envelope, format = TaglessEnvelopeFormat) + writeEnvelopeFile(tmpPath, envelope, envelopeFormat = TaglessEnvelopeFormat) println(tmpPath.toUri()) val restored: Envelope = readEnvelopeFile(tmpPath)!! assertTrue { envelope.contentEquals(restored) } diff --git a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/tcp/EnvelopeServerTest.kt b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/tcp/EnvelopeServerTest.kt index 37c35efc..64067dec 100644 --- a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/tcp/EnvelopeServerTest.kt +++ b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/tcp/EnvelopeServerTest.kt @@ -7,9 +7,9 @@ import hep.dataforge.io.TaggedEnvelopeFormat import hep.dataforge.io.writeBytes import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.runBlocking -import org.junit.AfterClass -import org.junit.BeforeClass -import org.junit.Test +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeAll +import kotlin.test.Test import kotlin.test.assertEquals import kotlin.time.ExperimentalTime @@ -30,13 +30,13 @@ class EnvelopeServerTest { @JvmStatic val echoEnvelopeServer = EnvelopeServer(Global, 7778, EchoResponder, GlobalScope) - @BeforeClass + @BeforeAll @JvmStatic fun start() { echoEnvelopeServer.start() } - @AfterClass + @AfterAll @JvmStatic fun close() { echoEnvelopeServer.stop() diff --git a/dataforge-scripting/build.gradle.kts b/dataforge-scripting/build.gradle.kts index 757f0c33..c848c1b1 100644 --- a/dataforge-scripting/build.gradle.kts +++ b/dataforge-scripting/build.gradle.kts @@ -19,8 +19,6 @@ kotlin { } val jvmTest by getting { dependencies { - implementation(kotlin("test")) - implementation(kotlin("test-junit")) implementation("ch.qos.logback:logback-classic:1.2.3") } } diff --git a/dataforge-scripting/src/jvmTest/kotlin/hep/dataforge/scripting/BuildersKtTest.kt b/dataforge-scripting/src/jvmTest/kotlin/hep/dataforge/scripting/BuildersKtTest.kt index 5a9ba56d..6dd61105 100644 --- a/dataforge-scripting/src/jvmTest/kotlin/hep/dataforge/scripting/BuildersKtTest.kt +++ b/dataforge-scripting/src/jvmTest/kotlin/hep/dataforge/scripting/BuildersKtTest.kt @@ -6,7 +6,7 @@ import hep.dataforge.meta.int import hep.dataforge.workspace.SimpleWorkspaceBuilder import hep.dataforge.workspace.context import hep.dataforge.workspace.target -import org.junit.Test +import kotlin.test.Test import kotlin.test.assertEquals diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/dataUtils.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/dataUtils.kt deleted file mode 100644 index f6d27774..00000000 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/dataUtils.kt +++ /dev/null @@ -1,14 +0,0 @@ -package hep.dataforge.workspace - -import hep.dataforge.data.Data -import hep.dataforge.io.Envelope -import hep.dataforge.io.IOFormat -import hep.dataforge.io.readWith -import kotlin.reflect.KClass - -/** - * Convert an [Envelope] to a data via given format. The actual parsing is done lazily. - */ -fun <T : Any> Envelope.toData(type: KClass<out T>, format: IOFormat<T>): Data<T> = Data(type, meta) { - data?.readWith(format) ?: error("Can't convert envelope without data to Data") -} \ No newline at end of file diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt new file mode 100644 index 00000000..111bba76 --- /dev/null +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt @@ -0,0 +1,33 @@ +package hep.dataforge.workspace + +import hep.dataforge.data.Data +import hep.dataforge.data.await +import hep.dataforge.io.* +import kotlinx.coroutines.coroutineScope +import kotlinx.io.core.Input +import kotlinx.io.core.buildPacket +import kotlin.reflect.KClass + +/** + * Convert an [Envelope] to a data via given format. The actual parsing is done lazily. + */ +fun <T : Any> Envelope.toData(type: KClass<out T>, format: IOFormat<T>): Data<T> = Data(type, meta) { + data?.readWith(format) ?: error("Can't convert envelope without data to Data") +} + +suspend fun <T : Any> Data<T>.toEnvelope(format: IOFormat<T>): Envelope { + val obj = coroutineScope { + await(this) + } + val binary = object : Binary { + override fun <R> read(block: Input.() -> R): R { + //TODO optimize away additional copy by creating inputs that reads directly from output + val packet = buildPacket { + format.run { writeObject(obj) } + } + return packet.block() + } + + } + return SimpleEnvelope(meta, binary) +} \ No newline at end of file diff --git a/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/fileData.kt b/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/fileData.kt index b5ecb519..2b6d5454 100644 --- a/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/fileData.kt +++ b/dataforge-workspace/src/jvmMain/kotlin/hep/dataforge/workspace/fileData.kt @@ -1,17 +1,27 @@ package hep.dataforge.workspace +//import jdk.nio.zipfs.ZipFileSystemProvider import hep.dataforge.data.* -import hep.dataforge.io.Envelope -import hep.dataforge.io.IOFormat -import hep.dataforge.io.IOPlugin -import hep.dataforge.io.readEnvelopeFile -import hep.dataforge.meta.Meta -import hep.dataforge.meta.get -import hep.dataforge.meta.string +import hep.dataforge.io.* +import hep.dataforge.meta.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.nio.file.FileSystem import java.nio.file.Files import java.nio.file.Path +import java.nio.file.StandardCopyOption +import java.nio.file.spi.FileSystemProvider import kotlin.reflect.KClass +typealias FileFormatResolver<T> = (Path, Meta) -> IOFormat<T> + +//private val zipFSProvider = ZipFileSystemProvider() + +private fun newZFS(path: Path): FileSystem { + val fsProvider = FileSystemProvider.installedProviders().find { it.scheme == "jar" } + ?: error("Zip file system provider not found") + return fsProvider.newFileSystem(path, mapOf("create" to "true")) +} /** * Read data with supported envelope format and binary format. If envelope format is null, then read binary directly from file. @@ -22,62 +32,152 @@ import kotlin.reflect.KClass * @param metaFile the relative file for optional meta override * @param metaFileFormat the meta format for override */ +@DFExperimental fun <T : Any> IOPlugin.readDataFile( path: Path, type: KClass<out T>, - formatResolver: (Meta) -> IOFormat<T> + formatResolver: FileFormatResolver<T> ): Data<T> { val envelope = readEnvelopeFile(path, true) ?: error("Can't read data from $path") - val format = formatResolver(envelope.meta) + val format = formatResolver(path, envelope.meta) return envelope.toData(type, format) } -//TODO wants multi-receiver +@DFExperimental +inline fun <reified T : Any> IOPlugin.readDataFile(path: Path): Data<T> = + readDataFile(path, T::class) { _, _ -> + resolveIOFormat<T>() ?: error("Can't resolve IO format for ${T::class}") + } + +/** + * Add file/directory-based data tree item + */ +@DFExperimental fun <T : Any> DataTreeBuilder<T>.file( plugin: IOPlugin, path: Path, - formatResolver: (Meta) -> IOFormat<T> + formatResolver: FileFormatResolver<T> ) { - plugin.run { - val data = readDataFile(path, type, formatResolver) - val name = data.meta[Envelope.ENVELOPE_NAME_KEY].string - ?: path.fileName.toString().replace(".df", "") - datum(name, data) + //If path is a single file or a special directory, read it as single datum + if (!Files.isDirectory(path) || Files.list(path).allMatch { it.fileName.toString().startsWith("@") }) { + plugin.run { + val data = readDataFile(path, type, formatResolver) + val name = data.meta[Envelope.ENVELOPE_NAME_KEY].string + ?: path.fileName.toString().replace(".df", "") + datum(name, data) + } + } else { + //otherwise, read as directory + plugin.run { + val data = readDataDirectory(path, type, formatResolver) + val name = data.meta[Envelope.ENVELOPE_NAME_KEY].string + ?: path.fileName.toString().replace(".df", "") + node(name, data) + } } } /** - * Read the directory as a data node + * Read the directory as a data node. If [path] is a zip archive, read it as directory */ +@DFExperimental fun <T : Any> IOPlugin.readDataDirectory( path: Path, type: KClass<out T>, - formatResolver: (Meta) -> IOFormat<T> + formatResolver: FileFormatResolver<T> ): DataNode<T> { - if (!Files.isDirectory(path)) error("Provided path $this is not a directory") + //read zipped data node + if (path.fileName != null && path.fileName.toString().endsWith(".zip")) { + //Using explicit Zip file system to avoid bizarre compatibility bugs + val fs = newZFS(path) + return readDataDirectory(fs.rootDirectories.first(), type, formatResolver) + } + if (!Files.isDirectory(path)) error("Provided path $path is not a directory") return DataNode(type) { Files.list(path).forEach { path -> - if (!path.fileName.toString().endsWith(".meta")) { + val fileName = path.fileName.toString() + if (fileName.startsWith(IOPlugin.META_FILE_NAME)) { + meta(readMetaFile(path)) + } else if (!fileName.startsWith("@")) { file(this@readDataDirectory, path, formatResolver) } } } } -fun <T : Any> DataTreeBuilder<T>.directory( - plugin: IOPlugin, +@DFExperimental +inline fun <reified T : Any> IOPlugin.readDataDirectory(path: Path): DataNode<T> = + readDataDirectory(path, T::class) { _, _ -> + resolveIOFormat<T>() ?: error("Can't resolve IO format for ${T::class}") + } + +/** + * Write data tree to existing directory or create a new one using default [java.nio.file.FileSystem] provider + */ +@DFExperimental +suspend fun <T : Any> IOPlugin.writeDataDirectory( path: Path, - formatResolver: (Meta) -> IOFormat<T> + node: DataNode<T>, + format: IOFormat<T>, + envelopeFormat: EnvelopeFormat? = null, + metaFormat: MetaFormatFactory? = null ) { - plugin.run { - val data = readDataDirectory(path, type, formatResolver) - val name = data.meta[Envelope.ENVELOPE_NAME_KEY].string - ?: path.fileName.toString().replace(".df", "") - node(name, data) + withContext(Dispatchers.IO) { + if (!Files.exists(path)) { + Files.createDirectories(path) + } else if (!Files.isDirectory(path)) { + error("Can't write a node into file") + } + node.items.forEach { (token, item) -> + val childPath = path.resolve(token.toString()) + when (item) { + is DataItem.Node -> { + writeDataDirectory(childPath, item.node, format, envelopeFormat) + } + is DataItem.Leaf -> { + val envelope = item.data.toEnvelope(format) + if (envelopeFormat != null) { + writeEnvelopeFile(childPath, envelope, envelopeFormat, metaFormat) + } else { + writeEnvelopeDirectory(childPath, envelope, metaFormat ?: JsonMetaFormat) + } + } + } + } + if (!node.meta.isEmpty()) { + writeMetaFile(path, node.meta, metaFormat ?: JsonMetaFormat) + } } } - - - +suspend fun <T : Any> IOPlugin.writeZip( + path: Path, + node: DataNode<T>, + format: IOFormat<T>, + envelopeFormat: EnvelopeFormat? = null, + metaFormat: MetaFormatFactory? = null +) { + withContext(Dispatchers.IO) { + val actualFile = if (path.toString().endsWith(".zip")) { + path + } else { + path.resolveSibling(path.fileName.toString() + ".zip") + } + if (Files.exists(actualFile) && Files.size(path) == 0.toLong()) { + Files.delete(path) + } + //Files.createFile(actualFile) + newZFS(actualFile).use { zipfs -> + val internalTargetPath = zipfs.getPath("/") + Files.createDirectories(internalTargetPath) + val tmp = Files.createTempDirectory("df_zip") + writeDataDirectory(tmp, node, format, envelopeFormat, metaFormat) + Files.list(tmp).forEach { sourcePath -> + val targetPath = sourcePath.fileName.toString() + val internalTargetPath = internalTargetPath.resolve(targetPath) + Files.copy(sourcePath, internalTargetPath, StandardCopyOption.REPLACE_EXISTING) + } + } + } +} diff --git a/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/DataPropagationTest.kt b/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/DataPropagationTest.kt index c449ffc3..083d3f57 100644 --- a/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/DataPropagationTest.kt +++ b/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/DataPropagationTest.kt @@ -6,8 +6,8 @@ import hep.dataforge.context.PluginTag import hep.dataforge.data.* import hep.dataforge.meta.Meta import hep.dataforge.names.asName -import org.junit.Test import kotlin.reflect.KClass +import kotlin.test.Test import kotlin.test.assertEquals diff --git a/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/FileDataTest.kt b/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/FileDataTest.kt new file mode 100644 index 00000000..b73a4d59 --- /dev/null +++ b/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/FileDataTest.kt @@ -0,0 +1,72 @@ +package hep.dataforge.workspace + +import hep.dataforge.context.Global +import hep.dataforge.data.* +import hep.dataforge.io.IOFormat +import hep.dataforge.io.io +import hep.dataforge.meta.DFExperimental +import kotlinx.coroutines.runBlocking +import kotlinx.io.core.Input +import kotlinx.io.core.Output +import kotlinx.io.core.readText +import kotlinx.io.core.writeText +import java.nio.file.Files +import kotlin.test.Ignore +import kotlin.test.Test +import kotlin.test.assertEquals + +class FileDataTest { + val dataNode = DataNode<String> { + node("dir") { + static("a", "Some string") { + "content" put "Some string" + } + } + static("b", "root data") + meta { + "content" put "This is root meta node" + } + } + + object StringIOFormat : IOFormat<String> { + override fun Output.writeObject(obj: String) { + writeText(obj) + } + + override fun Input.readObject(): String { + return readText() + } + + } + + @Test + @DFExperimental + fun testDataWriteRead() { + Global.io.run { + val dir = Files.createTempDirectory("df_data_node") + runBlocking { + writeDataDirectory(dir, dataNode, StringIOFormat) + } + println(dir.toUri().toString()) + val reconstructed = readDataDirectory(dir, String::class) { _, _ -> StringIOFormat } + assertEquals(dataNode["dir.a"]?.meta, reconstructed["dir.a"]?.meta) + assertEquals(dataNode["b"]?.data?.get(), reconstructed["b"]?.data?.get()) + } + } + + + @Test + @Ignore + fun testZipWriteRead() { + Global.io.run { + val zip = Files.createTempFile("df_data_node", ".zip") + runBlocking { + writeZip(zip, dataNode, StringIOFormat) + } + println(zip.toUri().toString()) + val reconstructed = readDataDirectory(zip, String::class) { _, _ -> StringIOFormat } + assertEquals(dataNode["dir.a"]?.meta, reconstructed["dir.a"]?.meta) + assertEquals(dataNode["b"]?.data?.get(), reconstructed["b"]?.data?.get()) + } + } +} \ No newline at end of file diff --git a/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/SimpleWorkspaceTest.kt b/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/SimpleWorkspaceTest.kt index 3a40e783..a4df6a4b 100644 --- a/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/SimpleWorkspaceTest.kt +++ b/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/SimpleWorkspaceTest.kt @@ -7,7 +7,7 @@ import hep.dataforge.meta.builder import hep.dataforge.meta.get import hep.dataforge.meta.int import hep.dataforge.names.plus -import org.junit.Test +import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 5c2d1cf016b3885f6930543d57b744ea8c220a1a..cc4fdc293d0e50b0ad9b65c16e7ddd1db2f6025b 100644 GIT binary patch delta 16535 zcmZ9zbyyr-lRgZCTOhc*ySoH;cXxO94DJ#b+}+(FxVr{|dypU*+~LbU`|kes`Fj57 zyY8yfeVy*U&eSRCZ-Sbgglb@bL`kJuD(i%VfWU)-fM5ZAqre6!L1F?a*_h28Ox@k% z)ux=5zF-P1b$GIsh22W}rhGA$wY4AMj)Kul`ohep<{7-Ia88yvi6?!4@QO*mP1?8% z^+-G1h=Bla=)vYr;y%0F`7k?YyaR;riRpp3>1dAn4tcrPo2W>F8o&vIoo8FT<sPh8 z9mu>(bX<wvfoEU59Ty1{vYfMEF)#ak96dc-x@;+^=;u^`6bw#fn<TbwqQx8<b9H)A z-I?T6U!Y--rm!r=@x4SG+tsjJ@?7M%Y|r<g{X!j$≶)6+k93DRaCapIbRgIazWl zZD`$k>b?GlmSb7V<kXlp6_>9@<6RmZzUyg~x=I4k!GQX(!lDs)h5@qh6<w)x3l+S( zVjZS%2nRRPHVE6EvVzP($SFdC>p<nQyG9~2xbJ`q1|DXWkK$X$KVnol{p&l5sVEER zOby;6T@HJp+_r*Pk9zFQd>kwH=O@3LDKNm1i;WQ8o$Fl=C^mx!!2RpT&LbaQ5~-gj zk}V-#Uq1+j(|;TD?e?fpp}ORH^Fq!uFQ{?+R=-AAXl>dQHNRxA%eOvJm2_4jRrfpH z5-aw5XpBp(8nzoT7~-#u+*s{L@q<(~8X0g_k%xjtgn)pDhk$?(g|LNWtR{hhfS~+K zG5zN~69PBXF|=_%h}_p27^B$eqeB|SWFatETD2Oq;%Vn$m>?Zn)|n^BYMi`It%~RE z{?zseJ_NVFBivK1vbQd!dzAq}2e$&>Wo6B}`={5MckUhxc|L^S-q?bQA7!N=FxZWT zU=VP`Gg4To%<=zBf<;qVDNMDbkkc&;M*Z23z5%huy5rEWEer-UUAsxdlvL`%T?_}| z(AC(<JIKrld8h(bqm2S6MUhNZS0`hONk1{0%8q`8B#kHvvxK#r<m}94YjMj=#|tU< zQ1@2ictoFIKz@(Y+(q8d+&XMdT_kTmZj}tBHC>*xAH|wk8S#%l@lNw>O44BZp257X zHvrr{{odBrG<b2#H{~|@sb|`zE7Oy6WAC0<ynwL|87lhhH`7Hz8aHQi#w>rE6ZV); zj8iGg2`q{Cm5o=D;JE|EG^sx`O)a|Vsgst~3Ake^OY!6;?G&szhN9ov0-!PbvBcU5 zGRjaV&=KpDs4zqyN`T#AmhHfP#k*wGhXF?Dga*x|Bj<T95+_o6^UydsmgaTKRIjNI zmCN?Vx~29B71wr{p?kP-H)b6(BsgVSBD7cV%XjIjw8mr_AGHw}1B<9UYbR9Vm|PKU zU+%17Zbxpq6z7@z;|;88V{(r}WTWl0r&3r8OdvuLl@5p|2eo+Jv6dF(UxBO07(l#g zGtB+6U&APqkwXWw?1{!644C!ivC8?mv1eAf2J;i;y*i(<_r*hh6=I6drGeyQ+u>`& zHV~0hpwX|JkNK!dAqe;o8Ea%7b%IeQD~k(41Q0J{%pt1LS1<!Yl(9C40w<YUy(iDg zG>Ggcq3F<yb~yr+YU%Yt)Hc*KVSvWF@-W7G_P<N~7C9m`kBWK`dk0L23~KQ?V>OT= z5A|Vo_JTwHTm_Y#V?{dbMum`oDTd}5=vi-t>w&h{Z8|8w&TVt0^eE-i3>R&hl&SM_ zmq)Meerq`|97S(0OKH~x2bnWXD<<toG`ZT8k7l3oYl1ZLlQhDw(k6chPyp2qmAq=t z!D=^tGL-r>9`-`tCM{=<s7JDEoSVAJk8PrP@vpi29Ngp4H&g3Z2UXsL77tg75y6(1 z7VC_SI{Pq}{K#ED+g44!|7crtAaC)zn(Hn}$y2(PJY%WI_O99JE@iS40w=dsBvn-S zvauk;>8}{PSRq_%t`k~5fPh}{h3YIkjBTGneZ+JF+OuXd^<)_ZuX5$u&ZP+pP<<fw z=isQ*h;!?g^t1Bf@`CqJ8jMeyP{ci84b3nS__35BWRaIZrqIQdl9T7ql+SRNiNb?z zYvLaT-Wa_GtA9SAf^(cXU*HE&WPncODHLkbv)&bmoiR#gw3;4{1wjAl5BQDZcY42~ zcW?szpn0$jX*Z~Zg3s7TyDetRe8A?LFShha;M{V>2g_}pc)~MKJVi9<{(FJ?Nr^j) z=vL&X+rs>>ym1r>$ddJHuRN}3R53kb3p*4jpEpZzzA*8+3P^Zm_{$%#!r=GQC(O@C zx6Lk~7MUL^QcV)@DgnE*4-XV`3c`9c<x|H5WV_jlBpNOdo~efO_9Mc+g+)ntDRpMx zZI|Us++I{J?^_o=o%jY|G;X^1{DHl%q|m&rH4h?wJ3Ap*8-Cw}-Pa_{z<VPwEKYpu z`+|Q*6gW=YRf=UB^%+-%H&>&QcG>RRmvV%AHUPa?0%()8%asP!noiK|7#1;^qznQT z0~b;d`W|`=o_E4xvzJ%-6v|@%kGFdG2L#9-_6miL%AA`Q8UkV!?(cf~&k72JLx7X8 zv@-Q{@Bp3R5(7&$x6}zVF+a8(xRIt{)nsT>+Jf4+pyjHxT1sjigKcbRQ&rGv`O^=% z9loFMTS2`MJnyO-KNl${u=ILJh5e4pedY`0;4eN1B{>+214bTnrh^ygc0ClRkGF-6 z^KM>p6MJ-DjzMz}f}!mS!&hQLdM<J?qIB%k(Mz7Z=D>YMBZn`5<C3H9Xe3;_epV@D z3zTjP@|bjrW2*vTYB$`}{i0wtsC48w4hh0~18iY&>Ft}T)22E31R0j608`P&({6Sv z+~0D8pDl^uBMt<J2=CJj#turSdbRH*OOx9ff?ZP2-eHUx*Ns}6e5D>G_h6A3r60>3 ze}0-}HvlSJitaX&`j_DjiW^0DaQ|}DHmI7NLj)$z@t4@n`b%CaxbCFQaar<aG&3OQ z4(;RD^A9*B;nn2GpM4u*z!LpreZTK{C{m<qi(Z;liv}*x!^=8w(oMnDOMW^FNGdn{ zTvD%a$X;B`P7|6g#||NWFk}^Bv*1o=^9{#|F9TRSnZTYwFkm@MONQwjcNMNTiZHRf zWdp+!8<PGjr6A8>%#KMbFrP8;UV*=UXv2t~N7${I78|hP9xX|r*{0)ZBS-A2?pnEp z5{%<w9ctweyB{&h=%aZ(v7T({L7~(K-Qt3l&eUSo7tY<H&l~g!X>38c<{72i%oG5F zBn@<(E_yi9g#uyMnN0S#v~<f^ZUu7>L6&+}+@3~P5v<;rEzy3qM((!S^E7A$!`9*Z zfXHq{x|C#{_u}V_a3rgg{+P${gr=ns+3nmp7N*<vd`G6S3oV(q>3$9I`A)xCG=A&A zk)vJy%fy1XNE<$2gK24($*r7zv|jZX)Cs&uID;Ff>s4pn&mdgKDt8oUo#5NiSA)&e zJ4iE)n<|_?dQ#*Q@65>|bKEX#^E_AO@K|ufg}Vxmu;OF$c;lKXEaaj*j#yz`L)}N4 z7`o+@_lsZgv4de;{vM}N<&38%r!Vzbcm11k4Keo+>iUiF?hz3GnEb7mTyS3bsTfEg z{lk+$yF=lE(k<$qGn=dX;d3Di>#8R3#qeA{5c+~3qq1%VjOdZv{)bd<QpNqy9CvB) zmCMZSl<Gj}ha{=4ZOpPMF6GaYrjN9S-s1CKni7)vND@~>5jroreFdBBbJ#1)<d`i| zzj1uE??#?=iTC_1J~iP#Zoe+m)-MBTt<9~fn*Cw!*p@O0<}sd$2q~=g!rqBL$<87K zVo@(75cNbxpp~g3<W@aGX2ISmf6lf2sXg!WQ3oI)4bftXr%{o<wx<8h@*~6870{RT zrp$jO!2SqD@hD&FmORdd{=`XD1sW*<DOZnL@GMPLm1L|>lyIhM5VZs&!Pcn5PR2S# z=^0_9q~0cs$>}}R&gvTxD)MaWj`V7B0z1~8qhjtKm}`Y~#bXcn!m-JZ7H@n7E8l%j zuSN6NIX__j?Xk_ZA`0VxOyNX<7f$G+m_p4e*zNKonge<-rut`Usij{fL)mOusi|$U zG_o_^vj(A89K0u3WqcXp5zrI^AV?;CtmPSO5tiQ?Io$v79p?$~+?+i;<U<heSBC2{ z>NYf5nDND9A+<MVjq2f_`T36x!d~jh^m8@Z?h}ju4kvm3O(j0abk0TzWKcC^HK=<o z^!dprh;}U`0#CcIQH+mz-*J2Ai>Xjmwo|s55SQS$L9~oncx`VWnLO|nBSK6Iu<e92 zm{OJG;Da)877Xr6Zr2m@t1oiGmFVvMYE&@Ix`SpHd9o$5GUoOblf9-h{5Q>erhlQz zwuQ>taA1U{x7}WC)8#rZke-dv7{a2#t2m)1`e*N@kb5${9SJvk21PuQAlo!osvVYo z*AA*9nWA8WYM6BTBaiE#Wsp*ug2Ni;mUP#+IfgQB%!hX-a;LhvHF~Uiw$=FPa8M+Q zbNf%N{comPbCObF8bT2$?fkH+i>L&@2A|M|ni2YeC028z<6$xMKt<;E(nAaKQ|x;N z<R=8Gdj`KjB=z-S#2$QtXZim5Q0N{Ae&3cwWY%WBpsQ4F*rr?Mzy5azKqwy<0`8}+ zB6;L%)N5K1fBHx8urv+da2&{Onrt!90Kfm)js6%7<pgvdG%KEbk|=weSc;G_@GA4n zS35e6lYP$Mjg0%mx%fR>C(5?n?3KK3q!h)jC#br?MSQ5~ROH_ujB;*1$-pNF2n=Ef z2(thDLBRw6dm~q?i{N9R?fIT)<*Qs=K4PwazZ%VvU@pCaFOWbq6^$`<wt6kIqQl;F zz^l9n;=H$!%JenY*A4ozg>8cv-V*)=9!(~wffqAT0h85(jmhvt3`g!XYq7_pu(SpG zuFo4gz9bs{%})Pe%lop^TI8cg`F#@A=oJtIti85@I0G|4O1So9HM3OjX)lBAVSCYo zNc!rGzKXlPl|}C$?p8lKLiJ$;h3}y3K7d;xwj+16he&AiL^Os-U>abIdB9_^y`TH# zUS%N|z%vlSK_Z${z_JJto+}*4ZW3T+L?1i2$?x40Lis=+@)hM>3k9gH=m>P)CjkH- zrC&k8K<=vx2<|=O02Ls95dJH}J5x|O_z!h2Mn7;@BsJ_0{iHX_YkJdxzuluV*J~nv zZ+(RJ4=@zh^dfdJ9r~Aijm&+v5&I~Xpsfz4n0#e6%-Bk+Wn>UEAW9~lP78vslB;y~ zo1df|t7RsgDAXTT3*RqV<8tcwsXu_45jEVD7L)kuEBJ1qbUd)Eq-P496DbYJ-}BPO zXUZH{e_^Y0XEjZv=quW?TQ;N5JIKV6)dCoj75Gnk5ClN3>>=6re8pbedzbQtGSq7K zGS2*5X<qD^mp-GO<FxR}9<FxED@5MOP64Z4>Xa)F(uorON)mI(=YL`){fdAVXTtXR z?E>gtZZ#A~Wd{?Dh9T=cl@_C|pv$1#asILv1iP+hRKnFAZ)$A5PGi!~sPoXGhR()w z1HEsJtC>BKv>V0f6kr-PbMwil)~(80oiUwtVp(1yoW=XY642$zO00%CSjbM9Hw3~O zN{JssnF<C46q{pPO?kW=P!6>CFubzZ++sSh(;EyKsbeW~AV%|fD3h|W2=o>_m1xEg zS9<OcN^#Kus||UX^(&1n`v;$RjvY+T$IE9bV5rOM-4}Pf=xueF@}?5TCt5De4ld4{ zK5){Nt!LPy<_p*<V|TMjBiiY@EBbcIj$NGmiN$OgRwshnN07E>JqIRzw!}X(6J|KG z9-ip9vJlnYdhKBhdc%p#m2DlLL6OW&Dmg0wd4-HxE=9wreebMg&URh&AI%XfWxo<% zTTs<x(=6|@QDzrdIHujO-%PQm>B>FK5HKq1$D>O=WW_LG?Cz<oS)4Xtx8U@?6YOzF zDB-AaD22(>Si#~<dj+0Obb)l!RlUy<n6*bb11)X2l5_MqOSZm!5_jUTh$YiU>CA<- zK36RlA;PKAM?0TEf|`sPMp={ELiS6~jYefrI5~=W(mM~EG%)G7oz1DPkV-D58=U=? z>)PhLkx#h7)KFO|W~(XoErM-q##xTUbMp#Qy`e0Q<sy!{q83QeC-rq|VOr9N;v*|B zQCUdbx>L5)aN+Vq_D}m#bjQA)?x<!}3Hd<+yHwvSKTY+dmyO273exl>QHbUF?>&b> zuiSSvN~gM<Z%<BV<5XNFnIKF*veYu30pER(PDCwE%@j117DyVtqTMd)54HXgOLuvy zERoa+3L8&)btT{?h}cRM&aBeGZ&g)jbe0()^F7(z6wz5@D4Wq@S8sejC;Wz&X0;Xi zar0VymPjt;0-JfaD0~b|ZDq3QNwYzNwM%=9vSvDw>ti(Eo02wSosQnU^i4_LYr-&X zlj%ECr}SkjnA@NUOeSbPL2Np;qvFuYi~>C?<15|-ngY6(2gpwBR7V7+ou@-#=Z&~y zTY=GwE0CR+Y?}`Y2%9L2=FKk9Kk2whbTRSKtBU(Eo~D|o-O}0bFtL?!)y-4o=6d9Q z7EjP$WN{eyMfL53F13MF0~4>;#Cp(@U?a5=Dk7)h(39O}LY9vzi0nbvO%Il_(^ztc zo<&!Fb{9w`PplGJJ58Y0Y|0hqQouVl$XSONKyQmDFJ-CVayp#XYeVVBx|wep9f3+D zvQ4n!gOP{IyZ6JFhNun1$$o%*lY%g3Dz~Z_9-BdMR0b9$Y6rtlQ4^6&(&yc~I1iGo zS2$+!`m^OQ(Z#hke@*Su;D1+v+}2_`&#Q9~ECl**<nzoWyGH9j^5(T4)?8obP%>ts zd5);~Z&Y$GY?ngLCZ{N{FS|F49GF0g>0B3-AW>=bKBO%sbO|~TDgQ#DKcRzT5vLtZ zWi;OezJA%rP0L9~x_OMzPuKp!DXOE&(q^0^(}FqzqPTc*_~}(nO*F_?Tt8Q13Buex zQUspuM`!1e-_IhP9V}qyyG&Z-F{fq3c!dvJ4C3rxKB7k_S`SX75X@T8(5SbVQYx%t zCeZ}=>{c)@#SZrel(*pUOSWPr);$ex1I((16?Lz_*$JZrUmPO^*<fuWe$mbc=4KS* zRx0=t1EF$eC^qoXe>zQjI829Sb6a_x0)g36Wod$piD+W<oL&~jr}!QRYW=O@Jkc95 zf!aZMW`U&}kX!Wur0L?KO>sTlnct7G#;>kCev7^LwzYL1n5)bF?A1y8or;AjG?4Vs zK2_1BkfMEqdD_ww5ie=v<XL{J+hu{)n$mf~_!xy?LD=qsys*<`?n|7Xrds`D-4=*` z6Kz9F+~v-r{4v~_<ilV5r;lbW%}GwWeVEUC&0wYFag@NWD&R)l-qV9eH*bXt$zt}q z4D6|9{*6i6&%@3yTY7uFnon-V(#pr0Q&sHRQ}ww|-|;j#jGeCT<##B$FUsx=*M53b z$@g$iGqCn($Qx)cF!s*%4IftieqjUVMNj8y{KDBa3Rk3(Ptzt<?}KH%tnyeZsb&%p z6H=4A+T4qb&z?CaD(AFZx`uh^9EwbJ*a`R8v@FDp6QwNTc&&;bXt)wc_m9A+jOz+v zt83hIl`c#icq-Hwotg7Phz-xI9dvZ_Vmza@(292@bxc$|k>5MCpL{TrJNy8)DLx%r z&#XmHhq&O>tyfXJP99TItlVcYe}t>+7)ER@@>LM71QqZ1`tB|JYxf2mld0LT>F-6% zeyR4r9(H^slfuHPIK=E@zN~FH{!t|KOAR})zUFHy*C<1tU_SpC{;DonK{@?!$0AMw zqR!8h>aWX7Iuqh|o*UgBjVYgi;jd%BrR`F;(n*&~{V|a&Ipx($01mxGRR|IcbIlmP z1euEoX;?Gwm@nW97Ig!xY>C_-Pyn#uTqwTanQ~9CqF3(rCSY#@6-gNCFn3U#kmN{T zBmjJ^yR}JP>$vm{rzJz0(;RC|E5l}}IEU*P@5--R^aH<9j{#jsy{Za$t3Y>SgXPRv z;RB~xVJzrmmnWs^K859zwNcl<L3nKxT<;6}K9IIzi>qytT<E9t!OxHCp>pP!@*T!= zH3q9AcVI0dzC(PYg^8upVyP@yF}vlvreE4JcV%YNtUSF)J>trpjeRiIK)>b>1L-Z~ z8qrLt3(X&N`hx3e{5>B)rBO4QH1qTo$6pUv9(}qulWyoh<n#)162ru8ot-T4z)vVN zS*g`iM*-7QTZ)|zE??`@2xgt>o-`6k#*}Rg?;d5l!v%IGJJVBekDVFlZ#etwfuSd$ z3Xf;KI`WL6Yo!llE#z5~U!+((O6HoJhjXT$<?WqRhq+QwWX4NC+pz9|Rx*l60<mpE zr}JAT`$2iD^2%+eb>fO`RrQ`??n9(ZzA(6UZEYcxWBQe2mmB|vYmQa4ZmP(5j#W<F zZclv0pZNA#TE<P@KR8!@TRWOxAUfn#eBGzhF+eo%+n23b;Ix)@ys<WI`?jKU!`Je- zfXyz%Q0;CL=yY!gG`NP|aI-;usvAOnayzm4o?xZs<o}6)|BRuYz)na5cmV@D5_#s6 zS?Gtivp5$(55y<ny*zk^bR#rdrC@9wVyvWKym!K0Q>EsOVNR2R9-EI9hUJfdBpie1 z;2+S%rpd?wDNNCI6O~^fUyj}IhT^bE<bLXnQ4K&bf8^f>K2pCtST6P|u6xV85Zl)8 z)-;%p$lE5`W&eJBp#O@P$Pul71x<IX^ZvQ6Hy6cXAM)}Y@7|Z|f-lkA4ZA=QuRFGz ztHlOgWJ;H*rz0YNXs%id%eXO&mmvks`U$-X9d$eYp`W7a+qFP|g%A24OAQS+AVRP% zVjWk*Y(F*>@DB$#CHR5BXT2W|`4%q@<Wp<tjsneY8H$D=+>Q`xK?n>|wQyh-ru% z;F9*X++b7s7>P`1b*d!UX&Go%wd01Fbqya{(PjIF+=k43+@Q(3Ih*hJ+8HXc@ziXN z?`_1~T50UeYrJxQc4aE%p)?{r{=}HaQ1NI1sp-uFY*#S1Zn>BO_oAIU6xI=X2_eY; zyfm!YTG`#=SQX-p_YZkEYADZy-yE_2<i8f@4hBUIM%s)GAum%PAD%VFbfRF4Us9*c z4BT9zJDf8Qj5-CRJXwB&Hz8S4d9*@V*4&XsQEw?a9HwT!iWNzWOD<3o(I&R$z5mtl zVPg*le)$9!8Y!4BO&K|~u(JF4Zu{cbO971!r<7TFyJ83NOkw#F&UHk9LEI-ly^tP0 zLJILYoCWVpfyC<Ew;S(>Znfy|O9G+61G@;}+V$V1Fck0m*{EBUU+@`*D>9RUFH^nE zxL%5K-x@%Mu5rs-V|pakt$o3FZ@3HwBWJ==Koc%L;Q<HV@gB(h{b~70y8!(3%mLR? zUS<C{T_9xsUQOaE7%7J?>T5UV*_fw+?+qy~5L?@(IK~C3%Bpg^*dCPoO`VD;`<Lr) zrX-np4W~?Mh{L%Ony0elPAcAUj#C>j<(SQx=cYuEzJ3Kx9<4t<zdP@8T7CLdP2CGK zFrB_kl_ZYD0w5x>k#9;6m~nFNpj+xdr`sp_liiuQ<<b3_v^|P~C5^rm_8L*IwI|RZ zXFwiCpih*lid7j-d1>%+_icThV{&~Licp|OR9`4yfb0$o7fGOyYqHYE!+r8=2#3HT za~SrGY&Pzj2)9k!Ff74qEn!^Ss%G4@ji+fZlCY9MetCHQZu}9bn<y~G5Nx|S8ArGC z>92F~ctoQFG_oEwBkwH;L_&wCv)vIBgz2qdfj0G8Nawv#o%MPpxBlw(p1krpHS7RR z`$Yz*{t)EqY)fb@e5dgyY7_+b{ntJi^k)LUc@;Md3x&@Cb6@Lk)++)X0)qU%_rc6) zKpo!zOmD1@_ogvM5agnY7>-T0o`XBf9(~x5m>8QQIw@HgbV=^{r);ujj<a*dU_TYd zTIrs%RKI*3$<kE^v4zo>FZMmo3tF|(LT4oR>XL!ZRy=E4jC5@IbMLd>Z`&`u4=;+d zZ^wm^kTruMN2XAWPRX0y-w3j^F?kZ=fY>Eegh`(Vqr!^WElPad;-uRn!Q_|5(+n(o zN2QyD$48&=5V{qlc#LLea&KI4j0TFoTXv(@n<Z;c`Jh}P?6oR9ccB^0HEoj~#QR{> zcXtv#>@z7mYUTCT5~_Ch5VCcLW-p*!9{lp2^ugI?GXGX9vn#aOtv&c6<^zN$0mAQv zk_E^}VF*tXkeJ%iPzGp>@^7*%A&5}#9iS`8J%)W5`Mj)Ss-wD$I}hSHji7EQIB4*b zh(FN^J0^gc%%m<W&*9#})=9};dp22LU~2x!U6!FR6MgH|j<IB^fZaMR?kDJ2tw5u) zVY+rDv#DhsfsNO=Y@yan5oolN1yq)M-817768~M*dgix?5G<#hS(tStgZeqc)eN3r z+|O=AF8EhzOG`GLG#7H&MoD&8v*d1F)*3ugu8Linz!?>ZUD<N0Kg?^bR$O;)gE&uX zorjr9^Fu`_VdUhePg6$AE53P#w>NY!DPBvIR}ooqwwyh7X`mXLGVvE#bf9EqQCS;r zN6ckX>nGa<Q-pjEOXYe4N*lhlrxHp9FZ2mI!Y4Ci9MR$tb-4|1ClUPR<TczWgk|Ct zhs1#1O4~+_Y%VkWG<=Ft$U!|hp^{cCb4Ax0i%2gC4){cmM?_`*3xcFiMN|Wt;dKIO z)1CEQQC{kW0&Qr2UWujSKLNd~WYZ-F%PDh`!4SFvmVR=Bw!u^GvmO*(?%)P&%dH!~ z+m1?p+w32Xxw7A^AuOdi9XGO0CG;kuYwE&;+!9mUinESO3>>mD;=VL*#o=qk6#S^< z6W3B0EXNXzVuRUm1%)WC)|epi%nijOwwYyzXtmI-1|v^QYL}W2eg{IQVTya`>+zUn z)tUgTF$Ke#F@I9q>kL@?^g`upf?27t0ur+4Zq{+Yk}$@D=~w|U#;IT~7~?TMn4Nwe zD#4;%eIJd1b~d^_0mR<eKM2rS$yFD$kHTNSNd1EN(p9ayhf4J-Gmw~hM)O!1mQVM{ z_qA|W@N4C+wvWQV!6UFWWT)YMhg>Pcb_sdL)N7E$ce5!mselG7fY7H6hI>^V06l_2 zL=IRa3;-En6<Q3A!&Qn?l}g#|XA^dE1$JXaN}LE`U0ht2nM{;-^#Qwmdw&q~221)E zZU1<dD@rTdsEe&>dxYhlAO32lVz6Zyjq6Ws4w2e@mRDFXm<KkO#!h;f@~bRl8rgVr z(i^1Z-Xz<r=B1|@8PFAS+u2tg9M$zlB&W7n#OJ8y2XGls9*=QZr%#sOUw<j`P0yN- zX?A($Hd&)iO&ZNYxAY!W!#*0ubz^jv-CfU8v+V2mntoQ<MmTTaUg-pLmtuV^*5rO> zGReM}&?fI0F%D$2<E$z@DbkbG)(n`{R@q}aQps$#%hq)qa<D_<d*bBV)|}SoPb>9} zHP4JZ&oif!F0S4zU-Np0X^d4mnt$TtO0vGQTj}<l%|aq;tO)xgrj~3CyXWglSE3RW z%EnBKIc-VxLwQFVeMhsWO{v{gaSy7n?a?tsU?t1i(*}5o>#cLufwTf}v1Z9w>nG~1 zV2ueg9Vu7TpDJ_A`fhu{7wOO~lbh|OL(9$8{WoeF-oHm0M*Bdw^PqFv#3(lv5LM^z z)f}5)Ele!-tg%;JHL){?B~g?V@k1lsE5$B*$K!hrBu@imygQpofyWcGCQ*-H@(1yx z|Kd#8Pd{Lr<pb4fa^j;roi85@fzH6_#|z#?Oc9i4G9#A~f|jsh$<c4gUW#spa(RM1 zDvhOdC8Tj-Xa@R6I%t(Xs*R=4@rzT%!~0uCH-A5#1)ieBuL9tJeW&sTh)GgaprPR# zu7u$b?&NR$vE6E<m$BV?B)Czu3M9BOv>JlQTL_?P+MbnN=rC%{Fw+mM1$@~ra9t4I z!&xVy1ImDP3ZY*8&n7~a*ScZPXT%b^us5?}mn71iJnHNj#+^Y~$k+)>-_x}M@eH_Q z?(Xn35{fdhp;`P0VyRtxt%sno6UikEmn)Za#NM#*!lJ+0=F_xX3(LG?fM2+mHbsIh z4X1$8Y=YGYQ{@UaSCMbJs%8LfD_Mqm@{m#FI_e_is-78poq$y!?A#UE`9q1}MtZXk zfI)9_>lm>GdN7!yL&*d)+t;I~;MlT)N~feGA|));Lt!qfrpUzw&>BedE|8f@I9|XU z>bD{-vhFbMl;UegpuF3b_9f{AKKho?Vh@<g&{N4G;K;0!G1eBtL>^vU4nG*2LnM4H zEd&#WdK_U<Y0ng2P2CW&-`F;no5LUL`6k$9M#!Te78OcPmVC?h$D7TQr_S{wQZ;lr z><?F1dFx3b>PsLe0cH0X!VX2)^+DJl0fa3Ygq?DPtwi)*5{hXd*^00D7iI`f*k?f3 z*wu(njYNj~q+YSm_sL~Wrp3~mi9-8?ej^mCG_%FVg29kinD?>3{h*E@eM1G35QXP- zQ=WUY5M?!`yJRnsiMlZ(d>GlqueV8#kW!x5FI@Ysw@Y>XQ61@S_99orI1jrJy5~bn zMd&R3qRDQ=D0PPrwosTw5BE+K$`!!B@%bmfy)3-!$yZpUqa7J9KC!`F7{)ZTR5X9s z+DIzSHzc_Ccz9J&3T_buevQV|Mdr&=B627E5I5e?yK*_J`u)!q%B)lo>tyLhW2WsS z5qp<ZCPg5r)Rs2LZDXF=`!DY#{|?B*)FWL60RGli!9qYl{&&$0kQp8*>*VfX>fj)5 zV`*;x-_iNhlr7~Y72MJMW={qNqFo8eUg*pwl#&B+j3Qi$=mqFoGb@B`qDfQCu7sA{ zXA<9`aBB2;Y9qfr63c)&+qKb*V9PcC*^Rv82Vv(q+mF|`E2MrzVmz5*$|13c!6IZ- zi>{Jl#xYAMyqXgope3uF@Q(Y)l$0SWvLn&;!=@Yl3ep%>;_0BU_huPOn<N?-zjj=b z!QgVaM(#wwC|>LIiXQeR6(?-dlLs{{utZJyF`F3`@R`*ClesEZAEnPqlDY;}SVS1R z7fby*m$Rzak^8=49GrF#{d4BI4!m=1sNHF|x>@VCljIu!RISg?TnR06R3B_G;@vS7 zSzb~moI}WGpY{~><J6>T-U}ATdZ{$w71ey4?WMTKO%C4|h;X1fykFoJNyujJ_)Xbo zz|6sjU5A`rGd$)-&_E7(76{RmIErVZ8N&Sxn=2w3YVBCrtCz`ctAVe$gWcrt62v4M z6`kE-X$JojsE{$9#mZ`9hOW-Pf_qedGCqv!GzI=<T5@qQb6MCR9&z<y{)1YRP25={ z!l)bCZ4q+DZShk)-sA+)Ntjw2Y`j&_6~-x%)-&u|vzlRfMw<63t(2{kRJDsZ8}gb3 zi&*p>X<ROagcEwxKt_YOZt|!py@-k)Z@8A8pyahY5D8R_Zh|qtO3ChkDDI#~W=)Pa z$_81H8OFf{nxQ_CRu*Qy$&vD9(SJs~;PJS?XgNeMl=1S(y#Qy$4=mj(ld(O<#Ut;2 z(v0`fnwC$x_#vx3H$;?!X+;$|?9Jvas$kVcqT}pJ{NNV_(#F_vzVOk1C5oOJ)}_jG zcJwM|&M>4-xbG}5`%Gc?a0-${Tdx5A`@3y^MQbR*gn;<M1OY+%9|s!l|0R+FG?te| zXqvkxz^ec<<g*}XG3>zv=n^q_bYw^bG$>79N|uRn#;X~E<ciuhNmpA#m5mV3+888F zRoQ;pb-kZe%T=wl`ZyPLty<MuoI4-gJRF&nsE|P8uM<6gw*3F*kDl_~=f3+M#loSY zFDSfED<8WgZ~}))@R$|rI4PPI4rMvijG(^K@2VmudVTJDt;l~BlsX!f<CvX0;G>;^ z7EwMtcx{QLkp<O*Eksa`>BNi+z#1et&!=CR)jC#{i#vvuQNf&ebg5QdgB-7%dD2h5 z)N|MBd~<0(`4*>Bt+pZf$H!iLdIv4pd-|1+uf^~L2Y_R-B_CP&%7-JuM&um7$RE|n zYQXBmEH_uOi!5_Taz=Z9Q}C0C<*A6;FSf#7Bb)TLTJr8O4f+&>b^+a5QY&=bMtgcB z`M(eN@m6=ssk&9O>R(Phg%$Ufu!O~ld7e%!R$f~|co+=+lxq$K!tgxmq^C>S9?@+c zmV0j2xB$oJtgo?c2ftROCPn3QU(=FEmnO<`%*`<cq$Q#x@#W1>(?~Se3Ol9tDni?7 zKRSqT#TsTm<v`w&<ulCiV7`~-lLy3b9wY2MAp+(1oL?r;)|(#%noP23{e$y`_mO_x ziTSQG6L+cX#bWkm$kxV<Dl!o=JFlFz>(r}m(E?HJuR4gW5gBWB+I$R`*E!O(R%#5@ zJ1w@>CpDL?YmB<R_|%n_GuO3hIcp@d<?IzGFdg;!IU5A2UyF|Xm~!R=0}wzwg!Xoe zC}j(pY{iu=6Z80kezw(xtt}@~wy9jvsl5?8_MC)E_!*;opS743XQ!B%vevqB$)&g> z!+|#v<uKJ8whcBZ+gc<_!{YjPmW2(y9lb`0W7>AAGs(3-qQyr{ae{KaO=<DjaoBQ= zz*K&Loz&9U{=faGy?tv$t?>=8Vty}2k6Uf&RGX>^qE-JKJmaFE{4*iizD5{wJj#3N z@Pfbia)x5aaaUT{F~PZ`8mjj_Qk+0s5dkR9A>McrQrWg7-l*0X-BBd$o@e`8^{<DC zvbqiLs5Jh<Pch9oZbh;@v*>A0FPfY!tF}}#lf%(Y{n->BAA337N`XFrE~5JR6UU5j zQ7X-yet0g{ny>A+4AOFOvz=ov*$?tR4OA{g?c+@ygFE5+th)K|L)~})WyX^k%POGy zZAaD}H}$8zdh|SpmQ`y>G<0*v>kgxQRxvC8Q#q5*Ukvc=77xm595Bm|%N{D?+9(yk z%dPNMcvfI1B~EU{AI;p%qAiY2kq=zz=98mkZO{r7FS4z}dQ=H@Y^~2s46WEm)`&pm zy(!GDY};Y2EqJar>nvwQMp&KPO=;k-cYJ{mDuhMZ%xHv{V@q<=O5%DRF{ZZAEfg}S zNz}$Cb72ELtfrd%c3qZ4Nt3b9J;kLxR9I{S!bmvx*!~NEaF#!+9C+W;bX>2_b3)!@ zh*Vv}TG1N=;Zbewti+J?c_$La(4~5uB!?h+Y9;G=?qKalaoQjeG(%@iCN+Rt6uXe8 zyYW4;Sbm7vKf<z#vNBm4o^C0?zpYUUDC@GZ9D|DJU=3;sl2inl!O{@-zhEVsp|dEK z4!DbMBe=R&Osp3>*3jfLY#;UXSz_@%&u}sUym2#81N68lVy$uATR($xx+y;+ZsfS+ zEH=DDvll<aBxs&~GyHbn*IEQ*k70_JK#I$gWuJ#t^JPR|D1w*9l3=623qv%91V!w( z7ShhLp8TfI=z_Ex*<a3rVEHbJdX{XD0{0T65=~Rn45PL`qrHRdiA>Z_+_u0b3vr3q z1BF9VWF1*><uk@RbsHp|m?h=Ej*u4I*n&w{BW6WeJ^*!^LN<_W)<h39H&`rcKtyP| zX3x-+!1yeb`_nK@=MvWN6V9)5jlAizzV9fqy=r?zh{iB2D`R@=9Bw#EEr79{BgVXV zZk<`ny$LLAhG(v+E>M|r{_KxKpC6^OBOh}Csmt7kS$K=n=SgO5GJ65LWhE|~RE9LA zxHF%nkP>rMt%y?hxgN%W-3b{kYTZW&^~vUYt%cTCS51#8#X12s6WrB~T64@dmgz8K zabeR@_}?tJ%%9n+W0&9Y874MNldAg55i;fG7TxLJQs2uKDQ+v|`pQKrZh3_Y7hyaK z<#q}k={;4-<<hU0S06K~v~)e<<#3j$=pAW(eC%jH!M%#7dS5dG!|PMiNh$+~jO(va z0)*_wSwvruO}&aId9G$u$kfHd0%op9sAvN;-r06SI_7d_;LTiu>H-*c%C4Py4Sxwd zDp?R8BTDRj*VrBsQGIgimHy@LThIAW86fgU?FrHkWV<ArRxvOwM#f7eB22Z4wsbIH zGmTuN!_}(7Ss}Rr6t&uQiI-*1#;%dcYZ2wZc90-O{q4w`GITM3XWMmeTfycRrr-Lc zfLz_3D4=n9_(o5%>z|<{P=hwnbFfN|9T&ibpz-zFcg(LczapPVmtrXF8I6{ZO|w>n zP8tw%NKE@LtezVuMSkU1zTzrO&YYE=AS~-=3gOy&=;1s30Pg;bKzLeswIOo3kil43 z51m=p66(<XSuCm^+aiO2=75qpvvgg&5*hp%A!{ZpS_)OkmGPw<T0agKJL^L9WP#a# z?SxUb$X(Bk7#2nD4Fh?ku#Idbbc(=2R)vF9j~xbJxZ*YOYe0$uJ2+5*;xmr&YUwJc zDr1K3HmquHDiG%8;AugF8AZ;KPW|3xJ^gNizH#~*aTOXzztpG?Ar;8X_SiA03<e>J zlwL2r#!dF^TC2j|9<Gl>6t>C_YCiG#ssB2DN~iB5Rc0BqzKsYA2D;N`#py*a81Jo$ z7)<;?ny++*P!4pbjKCk`a-JnjH5T&;o|>ZX8|>410%{IC!XK+8(CxZtY`D{ZL;xA$ zzS7Lt_oT?B`_cE!eplg*LZE8cmPxu}UeoxhK0X@gyIcm=r~k<ND7Yq}Qa`#=W{}0e zOdCVrgJsZB`AL4R7Vg+kTHb`hJsQ_kP?94;_u4l32h1_<-uT!;D<_!xF%49Gtf&8o z|ERkb0Y2tu*tb@TrtbWD(0Iq`O9cFAd{;dcdT*a3iNg69A}O2RsFzWHqLB6cz#>UJ zJqy<CscKKk#N%<7i~DGN+{Z{2E>qTcPpSVqmjD68vmqM)GCFD9hXOSvMS19Axg6hf zk{!Bw{aLveknL@H0Kl4@syTr0$9E-B$ZZyEpx+Z!@i$BSOAU+rWGBbw&-Sf-<f^yw zrG_fHbUmPf94AqgYwlAk=Zi>8g$sWa_9j%-(UCzgV5~Z9H|c!VW3<uWC7@IwB}dpy zKmUvEogcHtYk|CRxx>q3xUO?GQLEc5R<XAH*AynN*Y2kI5DF}l;r1+Dy-$14@IB04 zz;k>^#7{vXX|M}^HoQZ7qb9#UGy81z8-?!LA0$_%eq&x(EXY)|H|>weX(z)&xD2Uu z8{ug2{@PN<2ba<KOQ>C_6DBob^=kin<%B~UE0cfp%we^+ho~>``4&d?YOmFe{2{Y3 zg;0*x=(8=`Rq$`emRZ0VQYA@q{2S95E%0j>cRpF`6GDO+(VKUU05QM*AOZ2Ybz=)K zcQ8<v(F*XaHzlWG$W>;Qu^&93wxMYoO-m199v+e8I*Y?9w2-u7ZFRlTi2Af}w!b_l zc14C)-#?J%W^HP$xvFb>b>zdC!|EA*vz;m?FiBBDjPq%0+CFue)oD&~fHl(e5!fZU zJ-8suZULRA?~J5N+ol@Nb4EImc2;kBU%H|~+MS;&c2!!*k5^=i0&(st-5WfNEnZ;X zi5)MgdK}?sDUHc%(4+Gt#GHV+$Kg8fK3CFWM}`4|qD0Ja$dM4=9oPNy#m}qchA8r! zr^cGz*O<CkSQb`urF3=B95LmqHqbuVSG(+zjKd%<bY_wV3iQNuxBb%6D2+3Ip<M-N z7LG`yM=fE#07Uj&d?U!w0p$v5(=Y(bCZtDzqH;tp=jlVTb!b1AOFmpv*&6#N<1`z! z2vPJ5{;W}eMqN>17HZmS?F5l?7;2}cI#6)OHoCuvmf8F56r(t;>@%200F6GcP=FzW zL`bXJGbeub&dShGz#KI>6Za%B-Ea96z)8I^Ps?$5UU)M2@OJzC9%5@uF2|BiRl+zS zq$edug*g%A&(G)$Z)bew{xu#5ljnYTJ@~tQNm2{QW*G7n*M_C^PthCk_ADG6&$DcJ zZi?Zm<a<{Rr1)v2lG?*UKX9-pumltP4~4VogfM=~&-Fd_mg4bPFLEtdTV5hKJn9AB z$+0DiE<%oQZso3!I?ARz3F@{yi7Li3*+Vx|4>-f{&q-DyPqLzY6&0bd^%5K<U0_K3 zu*gEo%b@R}Zj*wGC=54d0Ve$$l0#>RP}@P}9Tg=YHvyaB;uLRZ5+Gl>*qE3Lb3_dl zXI7c$^=Vqp)Wz1K8*@?hDZb2M;nQv4Gi1l3E%zImmYb;~*+mJ7X!FAS4SyH028J#2 zRuB!#R@AanO*eu)SjhQo=-6yJF%!v6>ax6lk{Mr9`-g0CwW0f#c;vizFS~M`z!@yQ zIy%^6KBM!};NfoT4-f}Vu+D&%&&&H^V}yva4p}du{;b3#b3f~B>JFwG&bjPVyi#Cy z=5FTs=xdfr8qxS=LG&eo?Uyfj>^-3g)hM*=oRwbLiQe8KBr5#0#?$*v(@k*^MUG*s zikul)knv~+KGgB$Oq}6^tQuhn<=7cR1t3}_`|%RR6o_Rleqii+1(EqNWKg=k!D|N6 zJQJ%LcWnWm2g8<>uqwaf3X%;^T-bbn)yC;3Tx(X|Em?2TJVNk#D3%i#eo6VnDZ}%# zR}Y-B(QWLB(K-^(7Mw8E;VEpUcA-1wr25I%aAK42`_J(&Arbqcg;xPl)C?N$bSUS) zK%agqnAH#v_y8rqVjY9(hHgRB9E1Xb)-f-p^cC({KhMi6Un;>y)0kwbn?aTPz3O#P z8p)FVS^aJzivH*lrGZfvX3sro$Y!?_tck<fI7$4tuJ{rXn1h^1XfQNn+}dRPJY>ux z70r$aORx?t;L(+(ui$Y&x}rxAaTug>$VM0ISy?1&Jy6dotuvC1Mv6e8P8?I?WVb?` z6T#}tGEKT5)G-aGp%hwPasorcNM}=)V{(%U-JZjHfwA93%W>9WM6IEsY&JfakIOSJ zIg8)9p9wMD_p-P%WZ!rG`LV~g0!#0)4?u8P02y_&7u5h^=D<#w7yj-O<xVc4iwI%p zBw2uC4y1qkF{uEB7AZ0WvH-=jaM7QzF&s>QB#hJUZrvH={xrLh17RaF{e+d2OSbYY z3*9AgW~5b8Wz%#UK-fk4Iw)J#sZsK%vv(awe(pV;dD*sN{kdnkx@9tGxecHn`$29& z*p{jn+$?5iGyA>F+bHktL+9RK)&y)RRfM77f%&KoECV-gQ5kMm$isya5rE0HTS_4q z7*bum1uWV2mj<<*+*Gedp=(wti9K>RPYN2k$`0O&`K3q844a((t<*e-D-JEMSD5#_ z(&KY=2-sV_B9RF7U3-Cvp7z-5-!X1V=OrTyon5hMKYU5buKBfR)gFb*0eNr`Y0Dmq zKv^$6ql6aZ9qr2!OT(6;x>%(;&_k7y-kR)ka=+HVO0}uDGhD8k_K|<cH9&3_U%(8| zoAs%xQnCvJqrP@q(cGc!>?&%wFJI}R;O`cklo*lxj=`|yGhttzyB=IFvx&q{QEQL+ zvYvTr98=HFwaw4f72F6TD4YOCxSA~l;0sZ|=p!jDF#wsQj6K5&p{Nl1ssZ8K1|TXI z?uP*cg(38u0bs`<__+GSHs~I&3mdi@;pls69^4&LnzTN|Pd!5Bxh0lbwCS<O*wxz1 z(ZzZQB@!<^ah8RtFY<V~3ib2#d)e||Ug+Oo&42bc*DpFVHHf5*_M2<D6w^u)17CAt z?hPzPe=eHTljheGygaK3k2-gX$z%E%7GfbTA`%t3;$o0?a74>Qtpt~NnV>oB6!3t! zL^-x8%cOqUyx86ZYV3%jXiD<=!Esq_i4i{#|IG6UIM&(kg<dd@@HK|qG;WP!8$9}^ zAzmtan}*d)-jw*QL^=jq_qsmq5LowO3P0g@6H~*Q9Z_dnYQL~*mg9wi$n*s}F&9C? zD_LJ@((usrV(mx_z87XuSBPA(W&eds=iM{;Y;8}4kz@nMtl6x4u0*7qf|W!ASRx`C zElMkM_;+odvGB{@Zp6$;zaJ8;=@PE&|Kb*?6pHzbl=z0J&~#`lJ%RBd33OmiYAKG* z`c;FvCL^pqo5|!h8wN(R>Sr_?Q}Ceq740^1jUMVp^dm&Yr!sa{j1bSW=ZK$fTb4Q| zKS)0U9nzV`F*U<(OA+eg#14fv@%*w^kJ}L>ntz807HYzg%Zm`-4)TEg<Z=22@#J;8 z;ri~NOwfRDLh%e|wNpxV3uHMwdoayGHR=ACN4O=sz8l-UrSl1tVk?;XvXk&`@J#sH zWlMlG=G2A!mMSA=cFxr>MaiG~{;8L^hFJLn+MDIEebIka9DOIDrP13&`lWkA^rP(y zkZRk3Uj%RsC9~gVP?&VhhoX8SKD<UouSvTY-%@3<YhcA%GcO&c2i)t+SM$>1>AsW& z>5$Q@Z-H~l=j0rc_@!4w;}TCnhkR~CqtJCv;;!K5s#rOd{^c1@WBJe+`I_t6K<|g| z5Jzj{O0`1Ag_=oC+1;xyv@bTus0F0eoY8PrIj>K)@`ppS-nwby<sSoPZr6$Jlax55 z*>F=kX)R%Lx{)QEz;*8^w@&F3GGU*io054f9jY`f#8{WX7e7SH`qmK}`LF^-F=I+e zm0h_FJVcOYK#B4SnXuKY9IOkSU*WaPS1+sDb!cvTMz6*V)5eDrZ2#441A{aL9i!?J zcOyp{N@qQW`dX|<T9;5Ll7~F=L(t57Nv8G?e=>F;D~GVWx`96t-x`T*FDDHN@0w*i zYP{jfBLwQiZ6>xhBo>Xg6`%9Xugh-Xq1=8%)cpaaQ4{O!NH$o@E40Gn!dpe88|K3Z z_Y;Dstv!p6^ZjUEiKh>UW&^n|U;lqC(3Ru7Al3<7!hbc){%xWCpQ9w00t%Ewf%Ugf z8Xpw1iU#t9MMM67%6RyHlz&^pKx`8@g#T(9`yZ>n=aOI-g#R)8zddB2%1JcBe>y+@ z<_#47cAIhjYY^P0{|q7nWlf+F{;T5uUxqGd|1pFIl}%xTo+j`CE+qd;-QZ&X*Ns3r zllTA=(tqd;Jkq}uJ;0jguSfs_PYMGV=>I}Skiir^0H5<8quePH!hcm){Og|3T>lsW znNdNnQ)q<$H~aB7ko><#NpP0Xe+=P~|8Fh?v^S1T_^;UW|Bm^u2WI-^KcnD464R^z zam|0kcsb;MrcyqQ5BQ_~4<$T<0+Le11-(tv1739hLkR&iP5*)UT124w8G3-F)juM5 zMgm}B`yU7gQk&%ke0KwZt*JopbA+Io*-rohcaVw=!(WjeVBrqpoD%?m+(E8$h5%x( zzb8D9gFPh(Wu6`|=LcGdBm|MV;D8+dik1QYi03w_f3;|!rFneFk-vo}L?EOEZU9o) zUnK>|YJm-K|KCu_4<B?qjs`kfzz5_;{iCH?6a;*W{Rf@|5&WZVT*L>QCH_N!7nK1y z$so}sTfj@^Kg`^cB;Yv*B$`DB68Z53@R1J+{$UP4E&hi=T^0Z!m;QxZ|6C|(86N;& z@mFL4Z7%Zz9;*Jif^xxUP|y+@$Y2E@AYc0rmAxVZ2ygfc$w6>GSphqPAhLdPkp5qI zKKU0i|D7uuXzC|E0Bsg@{L>0>I0sT*wFI;;fX+wB{_7c{QT^*JA}oT0$7rxs<YE3M U#ULQK|Neget<zCj{4eSM0|LcRF8}}l delta 13615 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≈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 O=|=?tu^Rs0)%hQKQa~60 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 7c4388a9..6ce793f2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 index 83f2acfd..2fe81a7d --- a/gradlew +++ b/gradlew @@ -154,19 +154,19 @@ if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi @@ -175,14 +175,9 @@ save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=$(save "$@") +APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - exec "$JAVACMD" "$@" From 2e643287ef7c1f7d44ccf2b01b4348b8b7d25b43 Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Sat, 30 Nov 2019 23:36:06 +0300 Subject: [PATCH 09/41] Moving to io-2 --- build.gradle.kts | 10 +- dataforge-io/build.gradle.kts | 19 ++-- .../io/yaml/FrontMatterEnvelopeFormat.kt | 13 +-- .../hep/dataforge/io/yaml/YamlMetaFormat.kt | 12 +-- .../kotlin/hep/dataforge/io/Binary.kt | 94 ------------------ .../hep/dataforge/io/BinaryMetaFormat.kt | 15 ++- .../kotlin/hep/dataforge/io/Envelope.kt | 1 + .../hep/dataforge/io/EnvelopeBuilder.kt | 8 +- .../kotlin/hep/dataforge/io/EnvelopeFormat.kt | 4 +- .../kotlin/hep/dataforge/io/EnvelopeParts.kt | 32 +++++-- .../kotlin/hep/dataforge/io/IOFormat.kt | 46 ++------- .../kotlin/hep/dataforge/io/IOPlugin.kt | 2 +- .../kotlin/hep/dataforge/io/JsonMetaFormat.kt | 20 ++-- .../kotlin/hep/dataforge/io/MetaFormat.kt | 25 ++--- .../hep/dataforge/io/TaggedEnvelopeFormat.kt | 73 +++++++------- .../hep/dataforge/io/TaglessEnvelopeFormat.kt | 96 +++++++++++-------- .../io/serialization/MetaSerializer.kt | 1 + .../io/serialization/serializationUtils.kt | 1 + .../hep/dataforge/io/EnvelopeFormatTest.kt | 18 ++-- .../kotlin/hep/dataforge/io/MetaFormatTest.kt | 10 ++ .../hep/dataforge/io/MetaSerializerTest.kt | 4 +- ...{EnvelopePartsTest.kt => MultipartTest.kt} | 19 ++-- .../kotlin/hep/dataforge/io/FileBinary.kt | 31 ------ .../kotlin/hep/dataforge/io/FileEnvelope.kt | 15 ++- .../jvmMain/kotlin/hep/dataforge/io/fileIO.kt | 37 +++---- .../hep/dataforge/io/tcp/EnvelopeClient.kt | 9 +- .../hep/dataforge/io/tcp/EnvelopeServer.kt | 7 +- .../dataforge/io/tcp/InputStreamAsInput.kt | 32 ------- .../kotlin/hep/dataforge/io/tcp/streams.kt | 62 ++++++++++++ .../kotlin/hep/dataforge/io/FileBinaryTest.kt | 16 ++-- .../hep/dataforge/io/FileEnvelopeTest.kt | 1 + .../dataforge/io/tcp/EnvelopeServerTest.kt | 15 +-- .../kotlin/hep/dataforge/output/TextOutput.kt | 8 +- .../hep/dataforge/workspace/envelopeData.kt | 9 +- .../hep/dataforge/workspace/FileDataTest.kt | 8 +- settings.gradle.kts | 4 +- 36 files changed, 351 insertions(+), 426 deletions(-) delete mode 100644 dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Binary.kt rename dataforge-io/src/commonTest/kotlin/hep/dataforge/io/{EnvelopePartsTest.kt => MultipartTest.kt} (54%) delete mode 100644 dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/FileBinary.kt delete mode 100644 dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/InputStreamAsInput.kt create mode 100644 dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/streams.kt diff --git a/build.gradle.kts b/build.gradle.kts index cf2f4387..9e432336 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,9 +1,9 @@ import scientifik.ScientifikExtension plugins { - id("scientifik.mpp") version "0.2.4" apply false - id("scientifik.jvm") version "0.2.4" apply false - id("scientifik.publish") version "0.2.4" apply false + id("scientifik.mpp") version "0.2.5" apply false + id("scientifik.jvm") version "0.2.5" apply false + id("scientifik.publish") version "0.2.5" apply false } val dataforgeVersion by extra("0.1.5-dev-3") @@ -14,6 +14,10 @@ val githubProject by extra("dataforge-core") allprojects { group = "hep.dataforge" version = dataforgeVersion + + repositories { + mavenLocal() + } } subprojects { diff --git a/dataforge-io/build.gradle.kts b/dataforge-io/build.gradle.kts index 083e9d53..bb27ceff 100644 --- a/dataforge-io/build.gradle.kts +++ b/dataforge-io/build.gradle.kts @@ -4,27 +4,30 @@ plugins { description = "IO module" -scientifik{ +scientifik { withSerialization() - withIO() + //withIO() } +val ioVersion by rootProject.extra("0.2.0-npm-dev-2") kotlin { sourceSets { - commonMain{ + commonMain { dependencies { api(project(":dataforge-context")) + api("org.jetbrains.kotlinx:kotlinx-io:$ioVersion") + //api("org.jetbrains.kotlinx:kotlinx-io-metadata:$ioVersion") } } - jvmMain{ + jvmMain { dependencies { - + //api("org.jetbrains.kotlinx:kotlinx-io-jvm:$ioVersion") } } - jsMain{ - dependencies{ - api(npm("text-encoding")) + jsMain { + dependencies { + //api("org.jetbrains.kotlinx:kotlinx-io-js:$ioVersion") } } } diff --git a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt index 023635e2..be6e1d75 100644 --- a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt +++ b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt @@ -5,7 +5,8 @@ import hep.dataforge.io.* import hep.dataforge.meta.DFExperimental import hep.dataforge.meta.EmptyMeta import hep.dataforge.meta.Meta -import kotlinx.io.core.* +import kotlinx.io.Input +import kotlinx.io.Output import kotlinx.serialization.toUtf8Bytes @DFExperimental @@ -18,7 +19,7 @@ class FrontMatterEnvelopeFormat( var line: String = "" var offset = 0u do { - line = readUTF8Line() ?: error("Input does not contain front matter separator") + line = readUtf8Line() ?: error("Input does not contain front matter separator") offset += line.toUtf8Bytes().size.toUInt() } while (!line.startsWith(SEPARATOR)) @@ -28,7 +29,7 @@ class FrontMatterEnvelopeFormat( val metaBlock = buildPacket { do { - line = readUTF8Line() ?: error("Input does not contain closing front matter separator") + line = readUtf8Line() ?: error("Input does not contain closing front matter separator") appendln(line) offset += line.toUtf8Bytes().size.toUInt() } while (!line.startsWith(SEPARATOR)) @@ -40,7 +41,7 @@ class FrontMatterEnvelopeFormat( override fun Input.readObject(): Envelope { var line: String = "" do { - line = readUTF8Line() ?: error("Input does not contain front matter separator") + line = readUtf8Line() ?: error("Input does not contain front matter separator") } while (!line.startsWith(SEPARATOR)) val readMetaFormat = @@ -49,7 +50,7 @@ class FrontMatterEnvelopeFormat( val metaBlock = buildPacket { do { - appendln(readUTF8Line() ?: error("Input does not contain closing front matter separator")) + appendln(readUtf8Line() ?: error("Input does not contain closing front matter separator")) } while (!line.startsWith(SEPARATOR)) } val meta = readMetaFormat.fromBytes(metaBlock) @@ -76,7 +77,7 @@ class FrontMatterEnvelopeFormat( } override fun peekFormat(io: IOPlugin, input: Input): EnvelopeFormat? { - val line = input.readUTF8Line(3, 30) + val line = input.readUtf8Line(3, 30) return if (line != null && line.startsWith("---")) { invoke() } else { diff --git a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt index 7130518d..5e31a69e 100644 --- a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt +++ b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt @@ -8,12 +8,10 @@ import hep.dataforge.meta.DFExperimental import hep.dataforge.meta.Meta import hep.dataforge.meta.toMap import hep.dataforge.meta.toMeta -import hep.dataforge.names.Name -import hep.dataforge.names.plus -import kotlinx.io.core.Input -import kotlinx.io.core.Output -import kotlinx.io.core.readUByte -import kotlinx.io.core.writeText +import kotlinx.io.Input +import kotlinx.io.Output +import kotlinx.io.readUByte +import kotlinx.io.writeText import org.yaml.snakeyaml.Yaml import java.io.InputStream @@ -47,7 +45,7 @@ class YamlMetaFormat(val meta: Meta) : MetaFormat { companion object : MetaFormatFactory { override fun invoke(meta: Meta, context: Context): MetaFormat = YamlMetaFormat(meta) - override val name: Name = super.name + "yaml" + override val shortName = "yaml" override val key: Short = 0x594d //YM diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Binary.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Binary.kt deleted file mode 100644 index b671928b..00000000 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Binary.kt +++ /dev/null @@ -1,94 +0,0 @@ -package hep.dataforge.io - -import kotlinx.io.core.* -import kotlin.math.min - -/** - * A source of binary data - */ -interface Binary { - /** - * The size of binary in bytes. [ULong.MAX_VALUE] if size is not defined and input should be read until its end is reached - */ - val size: ULong get() = ULong.MAX_VALUE - - /** - * Read continuous [Input] from this binary stating from the beginning. - * The input is automatically closed on scope close. - * Some implementation may forbid this to be called twice. In this case second call will throw an exception. - */ - fun <R> read(block: Input.() -> R): R - - companion object { - val EMPTY = EmptyBinary - } -} - -/** - * A [Binary] with addition random access functionality. It by default allows multiple [read] operations. - */ -@ExperimentalUnsignedTypes -interface RandomAccessBinary : Binary { - /** - * Read at most [size] of bytes starting at [from] offset from the beginning of the binary. - * This method could be called multiple times simultaneously. - * - * If size - */ - fun <R> read(from: UInt, size: UInt = UInt.MAX_VALUE, block: Input.() -> R): R - - override fun <R> read(block: Input.() -> R): R = read(0.toUInt(), UInt.MAX_VALUE, block) -} - -fun Binary.toBytes(): ByteArray = read { - readBytes() -} - -fun Binary.contentToString(): String = read { - readText() -} - -@ExperimentalUnsignedTypes -fun RandomAccessBinary.readPacket(from: UInt, size: UInt): ByteReadPacket = read(from, size) { - buildPacket { copyTo(this) } -} - -@ExperimentalUnsignedTypes -object EmptyBinary : RandomAccessBinary { - - override val size: ULong = 0u - - override fun <R> read(from: UInt, size: UInt, block: Input.() -> R): R { - error("The binary is empty") - } -} - -@ExperimentalUnsignedTypes -inline class ArrayBinary(val array: ByteArray) : RandomAccessBinary { - override val size: ULong get() = array.size.toULong() - - override fun <R> read(from: UInt, size: UInt, block: Input.() -> R): R { - val theSize = min(size, array.size.toUInt() - from) - return buildPacket { - writeFully(array, from.toInt(), theSize.toInt()) - }.block() - } -} - -fun ByteArray.asBinary() = ArrayBinary(this) - -/** - * Read given binary as object using given format - */ -fun <T : Any> Binary.readWith(format: IOFormat<T>): T = format.run { - read { - readObject() - } -} - -//fun <T : Any> IOFormat<T>.writeBinary(obj: T): Binary { -// val packet = buildPacket { -// writeObject(obj) -// } -// return ArrayBinary(packet.readBytes()) -//} \ No newline at end of file diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/BinaryMetaFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/BinaryMetaFormat.kt index ba9886d8..1182bfbc 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/BinaryMetaFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/BinaryMetaFormat.kt @@ -3,16 +3,13 @@ package hep.dataforge.io import hep.dataforge.context.Context import hep.dataforge.descriptors.NodeDescriptor import hep.dataforge.meta.* -import hep.dataforge.names.Name -import hep.dataforge.names.plus import hep.dataforge.values.* -import kotlinx.io.core.Input -import kotlinx.io.core.Output -import kotlinx.io.core.readText -import kotlinx.io.core.writeText +import kotlinx.io.* +import kotlinx.io.text.readUtf8String +import kotlinx.io.text.writeUtf8String object BinaryMetaFormat : MetaFormat, MetaFormatFactory { - override val name: Name = super.name + "bin" + override val shortName: String = "bin" override val key: Short = 0x4249//BI override fun invoke(meta: Meta, context: Context): MetaFormat = this @@ -25,7 +22,7 @@ object BinaryMetaFormat : MetaFormat, MetaFormatFactory { private fun Output.writeString(str: String) { writeInt(str.length) - writeText(str) + writeUtf8String(str) } fun Output.writeValue(value: Value) { @@ -93,7 +90,7 @@ object BinaryMetaFormat : MetaFormat, MetaFormatFactory { private fun Input.readString(): String { val length = readInt() - return readText(max = length) + return readUtf8String(length) } @Suppress("UNCHECKED_CAST") diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt index abf8a504..adffdbf7 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt @@ -6,6 +6,7 @@ import hep.dataforge.meta.get import hep.dataforge.meta.string import hep.dataforge.names.asName import hep.dataforge.names.plus +import kotlinx.io.Binary interface Envelope { val meta: Meta diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt index 354b4586..a0b21b64 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt @@ -1,9 +1,7 @@ package hep.dataforge.io import hep.dataforge.meta.* -import kotlinx.io.core.Output -import kotlinx.io.core.buildPacket -import kotlinx.io.core.readBytes +import kotlinx.io.* class EnvelopeBuilder { private val metaBuilder = MetaBuilder() @@ -27,10 +25,10 @@ class EnvelopeBuilder { * Construct a binary and transform it into byte-array based buffer */ fun data(block: Output.() -> Unit) { - val bytes = buildPacket { + val bytes = buildBytes { block() } - data = ArrayBinary(bytes.readBytes()) + data = ArrayBinary(bytes.toByteArray()) } internal fun build() = SimpleEnvelope(metaBuilder.seal(), data) 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 49a25919..bf5b85f5 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeFormat.kt @@ -7,8 +7,8 @@ import hep.dataforge.meta.Meta import hep.dataforge.names.Name import hep.dataforge.names.asName import hep.dataforge.provider.Type -import kotlinx.io.core.Input -import kotlinx.io.core.Output +import kotlinx.io.Input +import kotlinx.io.Output import kotlin.reflect.KClass /** diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt index d1b86195..d7d981c4 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt @@ -24,16 +24,24 @@ object EnvelopeParts { /** * Append multiple serialized envelopes to the data block. Previous data is erased if it was present */ -fun EnvelopeBuilder.multipart(format: EnvelopeFormatFactory, envelopes: Collection<Envelope>) { +@DFExperimental +fun EnvelopeBuilder.multipart( + envelopes: Collection<Envelope>, + format: EnvelopeFormatFactory, + formatMeta: Meta = EmptyMeta +) { dataType = MULTIPART_DATA_TYPE meta { SIZE_KEY put envelopes.size FORMAT_NAME_KEY put format.name.toString() + if (!formatMeta.isEmpty()) { + FORMAT_META_KEY put formatMeta + } } data { - format.run { + format(formatMeta).run { envelopes.forEach { - writeObject(it) + writeEnvelope(it) } } } @@ -43,18 +51,25 @@ fun EnvelopeBuilder.multipart(format: EnvelopeFormatFactory, envelopes: Collecti * Create a multipart partition in the envelope adding additional name-index mapping in meta */ @DFExperimental -fun EnvelopeBuilder.multipart(format: EnvelopeFormatFactory, envelopes: Map<String, Envelope>) { +fun EnvelopeBuilder.multipart( + envelopes: Map<String, Envelope>, + format: EnvelopeFormatFactory, + formatMeta: Meta = EmptyMeta +) { dataType = MULTIPART_DATA_TYPE meta { SIZE_KEY put envelopes.size FORMAT_NAME_KEY put format.name.toString() + if (!formatMeta.isEmpty()) { + FORMAT_META_KEY put formatMeta + } } data { format.run { var counter = 0 - envelopes.forEach {(key, envelope)-> + envelopes.forEach { (key, envelope) -> writeObject(envelope) - meta{ + meta { append(INDEX_KEY, buildMeta { "key" put key "index" put counter @@ -66,14 +81,17 @@ fun EnvelopeBuilder.multipart(format: EnvelopeFormatFactory, envelopes: Map<Stri } } +@DFExperimental fun EnvelopeBuilder.multipart( formatFactory: EnvelopeFormatFactory, + formatMeta: Meta = EmptyMeta, builder: suspend SequenceScope<Envelope>.() -> Unit -) = multipart(formatFactory, sequence(builder).toList()) +) = multipart(sequence(builder).toList(), formatFactory, formatMeta) /** * If given envelope supports multipart data, return a sequence of those parts (could be empty). Otherwise return null. */ +@DFExperimental fun Envelope.parts(io: IOPlugin = Global.plugins.fetch(IOPlugin)): Sequence<Envelope>? { return when (dataType) { MULTIPART_DATA_TYPE -> { diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt index e5497365..44f68738 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOFormat.kt @@ -10,12 +10,9 @@ import hep.dataforge.names.Name import hep.dataforge.names.asName import hep.dataforge.provider.Type import hep.dataforge.values.Value -import kotlinx.io.core.* +import kotlinx.io.* +import kotlinx.io.buffer.Buffer import kotlinx.io.pool.ObjectPool -import kotlinx.serialization.ImplicitReflectionSerializer -import kotlinx.serialization.KSerializer -import kotlinx.serialization.cbor.Cbor -import kotlinx.serialization.serializer import kotlin.reflect.KClass /** @@ -49,7 +46,7 @@ class ListIOFormat<T : Any>(val format: IOFormat<T>) : IOFormat<List<T>> { val <T : Any> IOFormat<T>.list get() = ListIOFormat(this) -fun ObjectPool<IoBuffer>.fill(block: IoBuffer.() -> Unit): IoBuffer { +fun ObjectPool<Buffer>.fill(block: Buffer.() -> Unit): Buffer { val buffer = borrow() return try { buffer.apply(block) @@ -71,19 +68,11 @@ interface IOFormatFactory<T : Any> : Factory<IOFormat<T>>, Named { } } -@Deprecated("To be removed in io-2") -inline fun buildPacketWithoutPool(headerSizeHint: Int = 0, block: BytePacketBuilder.() -> Unit): ByteReadPacket { - val builder = BytePacketBuilder(headerSizeHint, IoBuffer.NoPool) - block(builder) - return builder.build() -} +fun <T : Any> IOFormat<T>.writeBytes(obj: T): Bytes = buildBytes { writeObject(obj) } -fun <T : Any> IOFormat<T>.writePacket(obj: T): ByteReadPacket = buildPacket { writeObject(obj) } -@Deprecated("Not to be used outside tests due to double buffer write") -fun <T : Any> IOFormat<T>.writeBytes(obj: T): ByteArray = buildPacket { writeObject(obj) }.readBytes() -@Deprecated("Not to be used outside tests due to double buffer write") -fun <T : Any> IOFormat<T>.readBytes(array: ByteArray): T = buildPacket { writeFully(array) }.readObject() +fun <T : Any> IOFormat<T>.writeByteArray(obj: T): ByteArray = buildBytes { writeObject(obj) }.toByteArray() +fun <T : Any> IOFormat<T>.readByteArray(array: ByteArray): T = array.asBinary().read { readObject() } object DoubleIOFormat : IOFormat<Double>, IOFormatFactory<Double> { override fun invoke(meta: Meta, context: Context): IOFormat<Double> = this @@ -117,25 +106,10 @@ object ValueIOFormat : IOFormat<Value>, IOFormatFactory<Value> { } /** - * Experimental + * Read given binary as object using given format */ -@ImplicitReflectionSerializer -class SerializerIOFormat<T : Any>( - type: KClass<T>, - val serializer: KSerializer<T> = type.serializer() -) : IOFormat<T> { - - //override val name: Name = type.simpleName?.toName() ?: EmptyName - - - override fun Output.writeObject(obj: T) { - val bytes = Cbor.plain.dump(serializer, obj) - writeFully(bytes) - } - - override fun Input.readObject(): T { - //FIXME reads the whole input - val bytes = readBytes() - return Cbor.plain.load(serializer, bytes) +fun <T : Any> Binary.readWith(format: IOFormat<T>): T = format.run { + read { + readObject() } } \ No newline at end of file diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt index eb975029..6144a211 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/IOPlugin.kt @@ -20,7 +20,7 @@ class IOPlugin(meta: Meta) : AbstractPlugin(meta) { metaFormatFactories.find { it.key == key }?.invoke(meta) fun metaFormat(name: String, meta: Meta = EmptyMeta): MetaFormat? = - metaFormatFactories.find { it.name.last().toString() == name }?.invoke(meta) + metaFormatFactories.find { it.shortName == name }?.invoke(meta) val envelopeFormatFactories by lazy { context.content<EnvelopeFormatFactory>(ENVELOPE_FORMAT_TYPE).values diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt index 5c10505d..9b78cbb3 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt @@ -9,15 +9,15 @@ import hep.dataforge.descriptors.ValueDescriptor import hep.dataforge.meta.Meta import hep.dataforge.meta.MetaBase import hep.dataforge.meta.MetaItem -import hep.dataforge.names.Name import hep.dataforge.names.NameToken -import hep.dataforge.names.plus import hep.dataforge.names.toName import hep.dataforge.values.* -import kotlinx.io.core.Input -import kotlinx.io.core.Output -import kotlinx.io.core.readText -import kotlinx.io.core.writeText +import kotlinx.io.Input +import kotlinx.io.Output +import kotlinx.io.text.readUtf8String +import kotlinx.io.text.writeUtf8String + + import kotlinx.serialization.json.* import kotlin.collections.component1 import kotlin.collections.component2 @@ -28,11 +28,11 @@ class JsonMetaFormat(private val json: Json = Json.indented) : MetaFormat { override fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor?) { val jsonObject = meta.toJson(descriptor) - writeText(json.stringify(JsonObjectSerializer, jsonObject)) + writeUtf8String(json.stringify(JsonObjectSerializer, jsonObject)) } override fun Input.readMeta(descriptor: NodeDescriptor?): Meta { - val str = readText() + val str = readUtf8String() val jsonElement = json.parseJson(str) return jsonElement.toMeta() } @@ -40,13 +40,13 @@ class JsonMetaFormat(private val json: Json = Json.indented) : MetaFormat { companion object : MetaFormatFactory { override fun invoke(meta: Meta, context: Context): MetaFormat = default - override val name: Name = super.name + "json" + override val shortName = "json" override val key: Short = 0x4a53//"JS" private val default = JsonMetaFormat() override fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor?) = - default.run { writeMeta(meta,descriptor) } + default.run { writeMeta(meta, descriptor) } override fun Input.readMeta(descriptor: NodeDescriptor?): Meta = default.run { readMeta(descriptor) } diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/MetaFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/MetaFormat.kt index 9d1af81a..d22359c8 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/MetaFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/MetaFormat.kt @@ -6,8 +6,9 @@ import hep.dataforge.io.MetaFormatFactory.Companion.META_FORMAT_TYPE import hep.dataforge.meta.Meta import hep.dataforge.names.Name import hep.dataforge.names.asName +import hep.dataforge.names.plus import hep.dataforge.provider.Type -import kotlinx.io.core.* +import kotlinx.io.* import kotlin.reflect.KClass /** @@ -28,11 +29,13 @@ interface MetaFormat : IOFormat<Meta> { @Type(META_FORMAT_TYPE) interface MetaFormatFactory : IOFormatFactory<Meta>, MetaFormat { - override val name: Name get() = "meta".asName() + val shortName: String + + override val name: Name get() = "meta".asName() + shortName override val type: KClass<out Meta> get() = Meta::class - val key: Short + val key: Short get() = name.hashCode().toShort() override operator fun invoke(meta: Meta, context: Context): MetaFormat @@ -41,24 +44,16 @@ interface MetaFormatFactory : IOFormatFactory<Meta>, MetaFormat { } } -fun Meta.toString(format: MetaFormat): String = buildPacket { +fun Meta.toString(format: MetaFormat): String = buildBytes { format.run { writeObject(this@toString) } -}.readText() +}.toByteArray().decodeToString() fun Meta.toString(formatFactory: MetaFormatFactory): String = toString(formatFactory()) -fun Meta.toBytes(format: MetaFormat = JsonMetaFormat): ByteReadPacket = buildPacket { - format.run { writeObject(this@toBytes) } -} - fun MetaFormat.parse(str: String): Meta { - return buildPacket { writeText(str) }.readObject() + return str.encodeToByteArray().read { readObject() } } -fun MetaFormatFactory.parse(str: String): Meta = invoke().parse(str) - -fun MetaFormat.fromBytes(packet: ByteReadPacket): Meta { - return packet.readObject() -} +fun MetaFormatFactory.parse(str: String, formatMeta: Meta): Meta = invoke(formatMeta).parse(str) 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 a461d257..01888dac 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt @@ -7,48 +7,51 @@ import hep.dataforge.meta.string import hep.dataforge.names.Name import hep.dataforge.names.plus import hep.dataforge.names.toName -import kotlinx.io.charsets.Charsets -import kotlinx.io.core.* +import kotlinx.io.* +import kotlinx.io.text.readRawString +import kotlinx.io.text.writeRawString -@ExperimentalUnsignedTypes +@ExperimentalIoApi class TaggedEnvelopeFormat( val io: IOPlugin, - val version: VERSION = TaggedEnvelopeFormat.VERSION.DF02 + val version: VERSION = VERSION.DF02 ) : EnvelopeFormat { // private val metaFormat = io.metaFormat(metaFormatKey) // ?: error("Meta format with key $metaFormatKey could not be resolved in $io") - private fun Tag.toBytes(): ByteReadPacket = buildPacket(24) { - writeText(START_SEQUENCE) - writeText(version.name) + private fun Tag.toBytes() = buildBytes(24) { + writeRawString(START_SEQUENCE) + writeRawString(version.name) writeShort(metaFormatKey) writeUInt(metaSize) when (version) { - TaggedEnvelopeFormat.VERSION.DF02 -> { + VERSION.DF02 -> { writeUInt(dataSize.toUInt()) } - TaggedEnvelopeFormat.VERSION.DF03 -> { + VERSION.DF03 -> { writeULong(dataSize) } } - writeText(END_SEQUENCE) + writeRawString(END_SEQUENCE) } override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) { val metaFormat = metaFormatFactory.invoke(formatMeta, io.context) - val metaBytes = metaFormat.writePacket(envelope.meta) + val metaBytes = metaFormat.writeBytes(envelope.meta) val actualSize: ULong = if (envelope.data == null) { - 0u + 0 } else { - envelope.data?.size ?: ULong.MAX_VALUE + envelope.data?.size ?: Binary.INFINITE + }.toULong() + val tag = Tag(metaFormatFactory.key, metaBytes.size.toUInt() + 2u, actualSize) + writeBinary(tag.toBytes()) + writeBinary(metaBytes) + writeRawString("\r\n") + envelope.data?.let { + writeBinary(it) } - val tag = Tag(metaFormatFactory.key, metaBytes.remaining.toUInt() + 2u, actualSize) - writePacket(tag.toBytes()) - writePacket(metaBytes) - writeText("\r\n") - envelope.data?.read { copyTo(this@writeEnvelope) } flush() } @@ -64,14 +67,17 @@ class TaggedEnvelopeFormat( val metaFormat = io.metaFormat(tag.metaFormatKey) ?: error("Meta format with key ${tag.metaFormatKey} not found") - val metaBytes = readBytes(tag.metaSize.toInt()) - val metaPacket = buildPacket { - writeFully(metaBytes) + val meta: Meta = limit(tag.metaSize.toInt()).use { + metaFormat.run { + readObject() + } } - val dataBytes = readBytes(tag.dataSize.toInt()) - val meta = metaFormat.run { metaPacket.readObject() } - return SimpleEnvelope(meta, ArrayBinary(dataBytes)) + val data = buildBytes { + writeInput(this@readObject, tag.dataSize.toInt()) + } + + return SimpleEnvelope(meta, data) } override fun Input.readPartial(): PartialEnvelope { @@ -80,8 +86,11 @@ class TaggedEnvelopeFormat( val metaFormat = io.metaFormat(tag.metaFormatKey) ?: error("Meta format with key ${tag.metaFormatKey} not found") - val metaPacket = ByteReadPacket(readBytes(tag.metaSize.toInt())) - val meta = metaFormat.run { metaPacket.readObject() } + val meta: Meta = limit(tag.metaSize.toInt()).run { + metaFormat.run { + readObject() + } + } return PartialEnvelope(meta, version.tagSize + tag.metaSize, tag.dataSize) } @@ -107,16 +116,16 @@ class TaggedEnvelopeFormat( val io = context.io val metaFormatName = meta["name"].string?.toName() ?: JsonMetaFormat.name - val metaFormatFactory = io.metaFormatFactories.find { it.name == metaFormatName } - ?: error("Meta format could not be resolved") + //Check if appropriate factory exists + io.metaFormatFactories.find { it.name == metaFormatName } ?: error("Meta format could not be resolved") return TaggedEnvelopeFormat(io) } private fun Input.readTag(version: VERSION): Tag { - val start = readTextExactBytes(2, charset = Charsets.ISO_8859_1) + val start = readRawString(2) if (start != START_SEQUENCE) error("The input is not an envelope") - val versionString = readTextExactBytes(4, charset = Charsets.ISO_8859_1) + val versionString = readRawString(4) if (version.name != versionString) error("Wrong version of DataForge: expected $version but found $versionString") val metaFormatKey = readShort() val metaLength = readUInt() @@ -124,14 +133,14 @@ class TaggedEnvelopeFormat( VERSION.DF02 -> readUInt().toULong() VERSION.DF03 -> readULong() } - val end = readTextExactBytes(4, charset = Charsets.ISO_8859_1) + val end = readRawString(4) if (end != END_SEQUENCE) error("The input is not an envelope") return Tag(metaFormatKey, metaLength, dataLength) } override fun peekFormat(io: IOPlugin, input: Input): EnvelopeFormat? { return try { - val header = input.readTextExactBytes(6) + val header = input.readRawString(6) when (header.substring(2..5)) { VERSION.DF02.name -> TaggedEnvelopeFormat(io, VERSION.DF02) VERSION.DF03.name -> TaggedEnvelopeFormat(io, VERSION.DF03) diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt index 1cc62a2b..a8b72cda 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt @@ -3,9 +3,14 @@ package hep.dataforge.io import hep.dataforge.context.Context import hep.dataforge.meta.* import hep.dataforge.names.asName -import kotlinx.io.core.* +import kotlinx.io.* +import kotlinx.io.text.readRawString +import kotlinx.io.text.readUtf8Line +import kotlinx.io.text.writeRawString +import kotlinx.io.text.writeUtf8String import kotlinx.serialization.toUtf8Bytes +@ExperimentalIoApi class TaglessEnvelopeFormat( val io: IOPlugin, meta: Meta = EmptyMeta @@ -15,47 +20,46 @@ class TaglessEnvelopeFormat( private val dataStart = meta[DATA_START_PROPERTY].string ?: DEFAULT_DATA_START private fun Output.writeProperty(key: String, value: Any) { - writeText("#? $key: $value;\r\n") + writeUtf8String("#? $key: $value;\r\n") } override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) { val metaFormat = metaFormatFactory(formatMeta, io.context) //printing header - writeText(TAGLESS_ENVELOPE_HEADER + "\r\n") + writeRawString(TAGLESS_ENVELOPE_HEADER + "\r\n") //printing all properties - writeProperty(META_TYPE_PROPERTY, metaFormatFactory.type) + writeProperty(META_TYPE_PROPERTY, metaFormatFactory.shortName) //TODO add optional metaFormat properties - val actualSize: ULong = if (envelope.data == null) { - 0u + val actualSize: Int = if (envelope.data == null) { + 0 } else { - envelope.data?.size ?: ULong.MAX_VALUE + envelope.data?.size ?: Binary.INFINITE } writeProperty(DATA_LENGTH_PROPERTY, actualSize) //Printing meta if (!envelope.meta.isEmpty()) { - val metaBytes = metaFormat.writePacket(envelope.meta) - writeProperty(META_LENGTH_PROPERTY, metaBytes.remaining) - writeText(metaStart + "\r\n") - writePacket(metaBytes) - writeText("\r\n") + val metaBytes = metaFormat.writeBytes(envelope.meta) + writeProperty(META_LENGTH_PROPERTY, metaBytes.size + 2) + writeUtf8String(metaStart + "\r\n") + writeBinary(metaBytes) + writeUtf8String("\r\n") } //Printing data envelope.data?.let { data -> - writeText(dataStart + "\r\n") - writeFully(data.toBytes()) + writeUtf8String(dataStart + "\r\n") + writeBinary(data) } - flush() } override fun Input.readObject(): Envelope { - var line: String = "" + var line: String do { - line = readUTF8Line() ?: error("Input does not contain tagless envelope header") + line = readUtf8Line() // ?: error("Input does not contain tagless envelope header") } while (!line.startsWith(TAGLESS_ENVELOPE_HEADER)) val properties = HashMap<String, String>() @@ -67,19 +71,23 @@ class TaglessEnvelopeFormat( val (key, value) = match.destructured properties[key] = value } - line = readUTF8Line() ?: return SimpleEnvelope(Meta.empty, null) + try { + line = readUtf8Line() + } catch (ex: EOFException) { + //If can't read line, return envelope without data + return SimpleEnvelope(Meta.empty, null) + } } var meta: Meta = EmptyMeta if (line.startsWith(metaStart)) { val metaFormat = properties[META_TYPE_PROPERTY]?.let { io.metaFormat(it) } ?: JsonMetaFormat - val metaSize = properties.get(META_LENGTH_PROPERTY)?.toInt() + val metaSize = properties[META_LENGTH_PROPERTY]?.toInt() meta = if (metaSize != null) { - val metaPacket = buildPacket { - writeFully(readBytes(metaSize)) + limit(metaSize).run { + metaFormat.run { readObject() } } - metaFormat.run { metaPacket.readObject() } } else { metaFormat.run { readObject() @@ -88,17 +96,22 @@ class TaglessEnvelopeFormat( } do { - line = readUTF8Line() ?: return SimpleEnvelope(meta, null) - //returning an Envelope without data if end of input is reached + try { + line = readUtf8Line() + } catch (ex: EOFException) { + //returning an Envelope without data if end of input is reached + return SimpleEnvelope(meta, null) + } } while (!line.startsWith(dataStart)) val data: Binary? = if (properties.containsKey(DATA_LENGTH_PROPERTY)) { val bytes = ByteArray(properties[DATA_LENGTH_PROPERTY]!!.toInt()) - readFully(bytes) + readArray(bytes) bytes.asBinary() } else { - val bytes = readBytes() - bytes.asBinary() + buildBytes { + writeInput(this@readObject) + } } return SimpleEnvelope(meta, data) @@ -106,9 +119,9 @@ class TaglessEnvelopeFormat( override fun Input.readPartial(): PartialEnvelope { var offset = 0u - var line: String = "" + var line: String do { - line = readUTF8Line() ?: error("Input does not contain tagless envelope header") + line = readUtf8Line()// ?: error("Input does not contain tagless envelope header") offset += line.toUtf8Bytes().size.toUInt() } while (!line.startsWith(TAGLESS_ENVELOPE_HEADER)) val properties = HashMap<String, String>() @@ -121,29 +134,31 @@ class TaglessEnvelopeFormat( val (key, value) = match.destructured properties[key] = value } - line = readUTF8Line() ?: return PartialEnvelope(Meta.empty, offset.toUInt(), 0.toULong()) - offset += line.toUtf8Bytes().size.toUInt() + try { + line = readUtf8Line() + offset += line.toUtf8Bytes().size.toUInt() + } catch (ex: EOFException) { + return PartialEnvelope(Meta.empty, offset.toUInt(), 0.toULong()) + } } var meta: Meta = EmptyMeta if (line.startsWith(metaStart)) { val metaFormat = properties[META_TYPE_PROPERTY]?.let { io.metaFormat(it) } ?: JsonMetaFormat - - val metaSize = properties.get(META_LENGTH_PROPERTY)?.toInt() + val metaSize = properties[META_LENGTH_PROPERTY]?.toInt() meta = if (metaSize != null) { - val metaPacket = buildPacket { - writeFully(readBytes(metaSize)) - } offset += metaSize.toUInt() - metaFormat.run { metaPacket.readObject() } + limit(metaSize).run { + metaFormat.run { readObject() } + } } else { error("Can't partially read an envelope with undefined meta size") } } do { - line = readUTF8Line() ?: return PartialEnvelope(Meta.empty, offset.toUInt(), 0.toULong()) + line = readUtf8Line() ?: return PartialEnvelope(Meta.empty, offset.toUInt(), 0.toULong()) offset += line.toUtf8Bytes().size.toUInt() //returning an Envelope without data if end of input is reached } while (!line.startsWith(dataStart)) @@ -190,9 +205,8 @@ class TaglessEnvelopeFormat( override fun peekFormat(io: IOPlugin, input: Input): EnvelopeFormat? { return try { - val buffer = ByteArray(TAGLESS_ENVELOPE_HEADER.length) - input.readFully(buffer) - return if (String(buffer) == TAGLESS_ENVELOPE_HEADER) { + val string = input.readRawString(TAGLESS_ENVELOPE_HEADER.length) + return if (string == TAGLESS_ENVELOPE_HEADER) { TaglessEnvelopeFormat(io) } else { null diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt index b22fed4a..31925c9f 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt @@ -13,6 +13,7 @@ import kotlinx.serialization.json.JsonOutput @Serializer(Value::class) +@UseExperimental(InternalSerializationApi::class) object ValueSerializer : KSerializer<Value> { private val valueTypeSerializer = EnumSerializer(ValueType::class) private val listSerializer by lazy { ArrayListSerializer(ValueSerializer) } diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/serializationUtils.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/serializationUtils.kt index b32abb14..18e28423 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/serializationUtils.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/serializationUtils.kt @@ -55,6 +55,7 @@ inline class SerialDescriptorBuilder(private val impl: SerialClassDescImpl) { fun doubleArray(name: String, isOptional: Boolean = false, vararg annotations: Annotation) = element(name, DoubleArraySerializer.descriptor, isOptional, *annotations) + @UseExperimental(InternalSerializationApi::class) inline fun <reified E : Enum<E>> enum(name: String, isOptional: Boolean = false, vararg annotations: Annotation) = element(name, EnumSerializer(E::class).descriptor, isOptional, *annotations) diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopeFormatTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopeFormatTest.kt index 37ee827d..0d6782b1 100644 --- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopeFormatTest.kt +++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopeFormatTest.kt @@ -1,5 +1,7 @@ package hep.dataforge.io +import kotlinx.io.readDouble +import kotlinx.io.writeDouble import kotlin.test.Test import kotlin.test.assertEquals @@ -12,16 +14,18 @@ class EnvelopeFormatTest { } data{ writeDouble(22.2) +// repeat(2000){ +// writeInt(it) +// } } } - @ExperimentalStdlibApi @Test fun testTaggedFormat(){ TaggedEnvelopeFormat.run { - val bytes = writeBytes(envelope) - println(bytes.decodeToString()) - val res = readBytes(bytes) + val byteArray = this.writeByteArray(envelope) + println(byteArray.decodeToString()) + val res = readByteArray(byteArray) assertEquals(envelope.meta,res.meta) val double = res.data?.read { readDouble() @@ -33,9 +37,9 @@ class EnvelopeFormatTest { @Test fun testTaglessFormat(){ TaglessEnvelopeFormat.run { - val bytes = writeBytes(envelope) - println(bytes.decodeToString()) - val res = readBytes(bytes) + val byteArray = writeByteArray(envelope) + println(byteArray.decodeToString()) + val res = readByteArray(byteArray) assertEquals(envelope.meta,res.meta) val double = res.data?.read { readDouble() 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 02180dbc..9064a485 100644 --- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaFormatTest.kt +++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaFormatTest.kt @@ -1,12 +1,22 @@ package hep.dataforge.io import hep.dataforge.meta.* +import kotlinx.io.Bytes +import kotlinx.io.buildBytes import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.json import kotlinx.serialization.json.jsonArray import kotlin.test.Test import kotlin.test.assertEquals +fun Meta.toBytes(format: MetaFormat = JsonMetaFormat): Bytes = buildBytes { + format.run { writeObject(this@toBytes) } +} + +fun MetaFormat.fromBytes(packet: Bytes): Meta { + return packet.read { readObject() } +} + class MetaFormatTest { @Test fun testBinaryMetaFormat() { diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaSerializerTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaSerializerTest.kt index 7a8447c0..37db8833 100644 --- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaSerializerTest.kt +++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaSerializerTest.kt @@ -5,8 +5,6 @@ import hep.dataforge.io.serialization.MetaSerializer import hep.dataforge.io.serialization.NameSerializer import hep.dataforge.meta.buildMeta import hep.dataforge.names.toName -import kotlinx.io.charsets.Charsets -import kotlinx.io.core.String import kotlinx.serialization.cbor.Cbor import kotlinx.serialization.json.Json import kotlin.test.Test @@ -41,7 +39,7 @@ class MetaSerializerTest { } val bytes = Cbor.dump(MetaSerializer, meta) - println(String(bytes, charset = Charsets.ISO_8859_1)) + println(bytes.contentToString()) val restored = Cbor.load(MetaSerializer, bytes) assertEquals(restored, meta) } diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopePartsTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt similarity index 54% rename from dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopePartsTest.kt rename to dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt index d123d632..34827c88 100644 --- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopePartsTest.kt +++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt @@ -1,33 +1,38 @@ package hep.dataforge.io +import hep.dataforge.meta.DFExperimental import hep.dataforge.meta.get import hep.dataforge.meta.int -import kotlinx.io.core.writeText +import kotlinx.io.text.writeUtf8String + import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertTrue -class EnvelopePartsTest { +@DFExperimental +class MultipartTest { val envelopes = (0..5).map { Envelope { meta { "value" put it } data { - writeText("Hello World $it") - repeat(200){ + writeUtf8String("Hello World $it") + repeat(2000) { writeInt(it) } } } } val partsEnvelope = Envelope { - multipart(TaggedEnvelopeFormat, envelopes) + multipart(envelopes, TaggedEnvelopeFormat) } @Test fun testParts() { - val bytes = TaggedEnvelopeFormat.writeBytes(partsEnvelope) - val reconstructed = TaggedEnvelopeFormat.readBytes(bytes) + val bytes = TaggedEnvelopeFormat.writeByteArray(partsEnvelope) + assertTrue { bytes.size > envelopes.sumBy { it.data!!.size.toInt() } } + val reconstructed = TaggedEnvelopeFormat.readByteArray(bytes) val parts = reconstructed.parts()?.toList() ?: emptyList() assertEquals(2, parts[2].meta["value"].int) println(reconstructed.data!!.size) diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/FileBinary.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/FileBinary.kt deleted file mode 100644 index aa90a638..00000000 --- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/FileBinary.kt +++ /dev/null @@ -1,31 +0,0 @@ -package hep.dataforge.io - -import kotlinx.io.core.Input -import kotlinx.io.core.buildPacket -import java.nio.channels.FileChannel -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.StandardOpenOption -import kotlin.math.min - -@ExperimentalUnsignedTypes -class FileBinary(val path: Path, private val offset: UInt = 0u, size: ULong? = null) : RandomAccessBinary { - - override val size: ULong = size ?: (Files.size(path).toULong() - offset).toULong() - - init { - if( size != null && Files.size(path) < offset.toLong() + size.toLong()){ - error("Can't read binary from file. File is to short.") - } - } - - override fun <R> read(from: UInt, size: UInt, block: Input.() -> R): R { - FileChannel.open(path, StandardOpenOption.READ).use { - val theSize: UInt = min(size, Files.size(path).toUInt() - offset) - val buffer = it.map(FileChannel.MapMode.READ_ONLY, (from + offset).toLong(), theSize.toLong()) - return buildPacket { writeFully(buffer) }.block() - } - } -} - -fun Path.asBinary(offset: UInt = 0u, size: ULong? = null): FileBinary = FileBinary(this, offset, size) \ No newline at end of file diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/FileEnvelope.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/FileEnvelope.kt index 5f21e6ae..3b34c26c 100644 --- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/FileEnvelope.kt +++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/FileEnvelope.kt @@ -1,23 +1,20 @@ package hep.dataforge.io import hep.dataforge.meta.Meta -import kotlinx.io.nio.asInput -import java.nio.file.Files +import kotlinx.io.Binary +import kotlinx.io.FileBinary +import kotlinx.io.read import java.nio.file.Path -import java.nio.file.StandardOpenOption class FileEnvelope internal constructor(val path: Path, val format: EnvelopeFormat) : Envelope { //TODO do not like this constructor. Hope to replace it later - private val partialEnvelope: PartialEnvelope - - init { - val input = Files.newByteChannel(path, StandardOpenOption.READ).asInput() - partialEnvelope = format.run { input.use { it.readPartial() } } + private val partialEnvelope: PartialEnvelope = path.read { + format.run { readPartial() } } override val meta: Meta get() = partialEnvelope.meta - override val data: Binary? = FileBinary(path, partialEnvelope.dataOffset, partialEnvelope.dataSize) + override val data: Binary? = FileBinary(path, partialEnvelope.dataOffset.toInt(), partialEnvelope.dataSize?.toInt()) } diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt index 9203d306..4e60707f 100644 --- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt +++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt @@ -5,13 +5,9 @@ import hep.dataforge.meta.DFExperimental import hep.dataforge.meta.EmptyMeta import hep.dataforge.meta.Meta import hep.dataforge.meta.isEmpty -import kotlinx.io.core.Output -import kotlinx.io.core.copyTo -import kotlinx.io.nio.asInput -import kotlinx.io.nio.asOutput +import kotlinx.io.* import java.nio.file.Files import java.nio.file.Path -import java.nio.file.StandardOpenOption import kotlin.reflect.full.isSuperclassOf import kotlin.streams.asSequence @@ -23,7 +19,6 @@ inline fun <reified T : Any> IOPlugin.resolveIOFormat(): IOFormat<T>? { return ioFormats.values.find { it.type.isSuperclassOf(T::class) } as IOFormat<T>? } - /** * Read file containing meta using given [formatOverride] or file extension to infer meta type. * If [path] is a directory search for file starting with `meta` in it @@ -41,7 +36,9 @@ fun IOPlugin.readMetaFile(path: Path, formatOverride: MetaFormat? = null, descri val metaFormat = formatOverride ?: metaFormat(extension) ?: error("Can't resolve meta format $extension") return metaFormat.run { - Files.newByteChannel(actualPath, StandardOpenOption.READ).asInput().use { it.readMeta(descriptor) } + actualPath.read{ + readMeta(descriptor) + } } } @@ -61,8 +58,8 @@ fun IOPlugin.writeMetaFile( path } metaFormat.run { - Files.newByteChannel(actualPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW).asOutput().use { - it.writeMeta(meta, descriptor) + actualPath.write{ + writeMeta(meta, descriptor) } } } @@ -139,24 +136,12 @@ fun IOPlugin.readEnvelopeFile( } else null } -private fun Path.useOutput(consumer: Output.() -> Unit) { - //TODO forbid rewrite? - Files.newByteChannel( - this, - StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING - ).asOutput().use { - it.consumer() - it.flush() - } -} - /** * Write a binary into file. Throws an error if file already exists */ fun <T : Any> IOFormat<T>.writeToFile(path: Path, obj: T) { - path.useOutput { + path.write { writeObject(obj) - flush() } } @@ -170,7 +155,7 @@ fun IOPlugin.writeEnvelopeFile( envelopeFormat: EnvelopeFormat = TaggedEnvelopeFormat, metaFormat: MetaFormatFactory? = null ) { - path.useOutput { + path.write { with(envelopeFormat) { writeEnvelope(envelope, metaFormat ?: envelopeFormat.defaultMetaFormat) } @@ -196,10 +181,10 @@ fun IOPlugin.writeEnvelopeDirectory( writeMetaFile(path, envelope.meta, metaFormat) } val dataFile = path.resolve(IOPlugin.DATA_FILE_NAME) - dataFile.useOutput { + dataFile.write { envelope.data?.read { - val copied = copyTo(this@useOutput) - if (envelope.data?.size != ULong.MAX_VALUE && copied != envelope.data?.size?.toLong()) { + val copied = writeInput(this) + if (envelope.data?.size != Binary.INFINITE && copied != envelope.data?.size) { error("The number of copied bytes does not equal data size") } } diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeClient.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeClient.kt index b6b85101..6c7e36c6 100644 --- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeClient.kt +++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeClient.kt @@ -7,7 +7,6 @@ import hep.dataforge.meta.EmptyMeta import hep.dataforge.meta.Meta import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.withContext -import kotlinx.io.streams.writePacket import java.net.Socket import java.util.concurrent.Executors import kotlin.time.ExperimentalTime @@ -52,14 +51,14 @@ class EnvelopeClient( override suspend fun respond(request: Envelope): Envelope = withContext(dispatcher) { //val address = InetSocketAddress(host,port) val socket = Socket(host, port) - val input = socket.getInputStream().asInput() - val output = socket.getOutputStream() + val inputStream = socket.getInputStream() + val outputStream = socket.getOutputStream() format.run { - output.writePacket { + outputStream.write { writeObject(request) } logger.debug { "Sent request with type ${request.type} to ${socket.remoteSocketAddress}" } - val res = input.readObject() + val res = inputStream.readBlocking { readObject() } logger.debug { "Received response with type ${res.type} from ${socket.remoteSocketAddress}" } return@withContext res } diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeServer.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeServer.kt index b733aedd..778e563f 100644 --- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeServer.kt +++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeServer.kt @@ -9,7 +9,6 @@ import hep.dataforge.io.type import hep.dataforge.meta.EmptyMeta import hep.dataforge.meta.Meta import kotlinx.coroutines.* -import kotlinx.io.streams.writePacket import java.net.ServerSocket import java.net.Socket import kotlin.concurrent.thread @@ -71,11 +70,11 @@ class EnvelopeServer( private fun readSocket(socket: Socket) { thread { - val input = socket.getInputStream().asInput() + val inputStream = socket.getInputStream() val outputStream = socket.getOutputStream() format.run { while (socket.isConnected) { - val request = input.readObject() + val request = inputStream.readBlocking { readObject() } logger.debug { "Accepted request with type ${request.type} from ${socket.remoteSocketAddress}" } if (request.type == SHUTDOWN_ENVELOPE_TYPE) { //Echo shutdown command @@ -86,7 +85,7 @@ class EnvelopeServer( } runBlocking { val response = responder.respond(request) - outputStream.writePacket { + outputStream.write { writeObject(response) } logger.debug { "Sent response with type ${response.type} to ${socket.remoteSocketAddress}" } diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/InputStreamAsInput.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/InputStreamAsInput.kt deleted file mode 100644 index eb743625..00000000 --- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/InputStreamAsInput.kt +++ /dev/null @@ -1,32 +0,0 @@ -package hep.dataforge.io.tcp - -import kotlinx.io.core.AbstractInput -import kotlinx.io.core.Input -import kotlinx.io.core.IoBuffer -import kotlinx.io.core.writePacket -import kotlinx.io.streams.readPacketAtMost -import java.io.InputStream - -/** - * Modified version of InputStream to Input converter that supports waiting for input - */ -internal class InputStreamAsInput( - private val stream: InputStream -) : AbstractInput(pool = IoBuffer.Pool) { - - - override fun fill(): IoBuffer? { - val packet = stream.readPacketAtMost(4096) - return pool.borrow().apply { - resetForWrite(4096) - writePacket(packet) - } - } - - override fun closeSource() { - stream.close() - } -} - -fun InputStream.asInput(): Input = - InputStreamAsInput(this) diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/streams.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/streams.kt new file mode 100644 index 00000000..2c240f77 --- /dev/null +++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/streams.kt @@ -0,0 +1,62 @@ +package hep.dataforge.io.tcp + +import kotlinx.io.Input +import kotlinx.io.Output +import kotlinx.io.asBinary +import kotlinx.io.buffer.Buffer +import kotlinx.io.buffer.get +import kotlinx.io.buffer.set +import java.io.InputStream +import java.io.OutputStream + +private class InputStreamInput(val source: InputStream, val waitForInput: Boolean = false) : Input() { + override fun closeSource() { + source.close() + } + + override fun fill(buffer: Buffer): Int { + if (waitForInput) { + while (source.available() == 0) { + //block until input is available + } + } + var bufferPos = 0 + do { + val byte = source.read() + buffer[bufferPos] = byte.toByte() + bufferPos++ + } while (byte > 0 && bufferPos < buffer.size && source.available() > 0) + return bufferPos + } +} + +private class OutputStreamOutput(val out: OutputStream) : Output() { + override fun flush(source: Buffer, length: Int) { + for (i in 0..length) { + out.write(source[i].toInt()) + } + out.flush() + } + + override fun closeSource() { + out.flush() + out.close() + } +} + + +fun <R> InputStream.read(size: Int, block: Input.() -> R): R { + val buffer = ByteArray(size) + read(buffer) + return buffer.asBinary().read(block) +} + +fun <R> InputStream.read(block: Input.() -> R): R = + InputStreamInput(this, false).block() + +fun <R> InputStream.readBlocking(block: Input.() -> R): R = + InputStreamInput(this, true).block() + +fun OutputStream.write(block: Output.() -> Unit) { + OutputStreamOutput(this).block() +} \ No newline at end of file diff --git a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileBinaryTest.kt b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileBinaryTest.kt index d8c7c67a..685342cf 100644 --- a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileBinaryTest.kt +++ b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileBinaryTest.kt @@ -1,6 +1,9 @@ package hep.dataforge.io import hep.dataforge.context.Global +import kotlinx.io.asBinary +import kotlinx.io.toByteArray +import kotlinx.io.writeDouble import java.nio.file.Files import kotlin.test.Test import kotlin.test.assertEquals @@ -21,11 +24,11 @@ class FileBinaryTest { @Test fun testSize() { val binary = envelope.data - assertEquals(binary?.size?.toInt(), binary?.toBytes()?.size) + assertEquals(binary?.size?.toInt(), binary?.toByteArray()?.size) } @Test - fun testFileData(){ + fun testFileData() { val dataFile = Files.createTempFile("dataforge_test_bin", ".bin") dataFile.toFile().writeText("This is my binary") val envelopeFromFile = Envelope { @@ -34,12 +37,12 @@ class FileBinaryTest { "b" put 22.2 } dataType = "hep.dataforge.satellite" - dataID = "cellDepositTest" // добавил только что + dataID = "cellDepositTest" data = dataFile.asBinary() } val binary = envelopeFromFile.data!! - println(binary.toBytes().size) - assertEquals(binary.size?.toInt(), binary.toBytes().size) + println(binary.toByteArray().size) + assertEquals(binary.size.toInt(), binary.toByteArray().size) } @@ -50,7 +53,6 @@ class FileBinaryTest { Global.io.writeEnvelopeFile(tmpPath, envelope) val binary = Global.io.readEnvelopeFile(tmpPath)?.data!! - assertEquals(binary.size.toInt(), binary.toBytes().size) + assertEquals(binary.size.toInt(), binary.toByteArray().size) } - } \ No newline at end of file diff --git a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt index 585aced9..edee906b 100644 --- a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt +++ b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/FileEnvelopeTest.kt @@ -1,6 +1,7 @@ package hep.dataforge.io import hep.dataforge.context.Global +import kotlinx.io.writeDouble import java.nio.file.Files import kotlin.test.Test import kotlin.test.assertTrue diff --git a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/tcp/EnvelopeServerTest.kt b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/tcp/EnvelopeServerTest.kt index 64067dec..de1d35ff 100644 --- a/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/tcp/EnvelopeServerTest.kt +++ b/dataforge-io/src/jvmTest/kotlin/hep/dataforge/io/tcp/EnvelopeServerTest.kt @@ -4,11 +4,12 @@ import hep.dataforge.context.Global import hep.dataforge.io.Envelope import hep.dataforge.io.Responder import hep.dataforge.io.TaggedEnvelopeFormat -import hep.dataforge.io.writeBytes +import hep.dataforge.io.writeByteArray import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.runBlocking -import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.BeforeAll +import kotlinx.io.writeDouble +import org.junit.AfterClass +import org.junit.BeforeClass import kotlin.test.Test import kotlin.test.assertEquals import kotlin.time.ExperimentalTime @@ -16,7 +17,7 @@ import kotlin.time.ExperimentalTime @ExperimentalStdlibApi object EchoResponder : Responder { override suspend fun respond(request: Envelope): Envelope { - val string = TaggedEnvelopeFormat().run { writeBytes(request).decodeToString() } + val string = TaggedEnvelopeFormat().run { writeByteArray(request).decodeToString() } println("ECHO:") println(string) return request @@ -30,20 +31,20 @@ class EnvelopeServerTest { @JvmStatic val echoEnvelopeServer = EnvelopeServer(Global, 7778, EchoResponder, GlobalScope) - @BeforeAll + @BeforeClass @JvmStatic fun start() { echoEnvelopeServer.start() } - @AfterAll + @AfterClass @JvmStatic fun close() { echoEnvelopeServer.stop() } } - @Test + @Test(timeout = 1000) fun doEchoTest() { val request = Envelope.invoke { type = "test.echo" diff --git a/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/TextOutput.kt b/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/TextOutput.kt index 91aa5024..77f589df 100644 --- a/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/TextOutput.kt +++ b/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/TextOutput.kt @@ -9,7 +9,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlin.reflect.KClass -class TextOutput(override val context: Context, private val output: kotlinx.io.core.Output) : Output<Any> { +class TextOutput(override val context: Context, private val output: kotlinx.io.Output) : Output<Any> { private val cache = HashMap<KClass<*>, TextRenderer>() /** @@ -40,7 +40,7 @@ class TextOutput(override val context: Context, private val output: kotlinx.io.c } /** - * A text or binary renderer based on [kotlinx.io.core.Output] + * A text or binary renderer based on [kotlinx.io.Output] */ @Type(TEXT_RENDERER_TYPE) interface TextRenderer { @@ -53,7 +53,7 @@ interface TextRenderer { */ val type: KClass<*> - suspend fun kotlinx.io.core.Output.render(obj: Any) + suspend fun kotlinx.io.Output.render(obj: Any) companion object { const val TEXT_RENDERER_TYPE = "dataforge.textRenderer" @@ -64,7 +64,7 @@ object DefaultTextRenderer : TextRenderer { override val priority: Int = Int.MAX_VALUE override val type: KClass<*> = Any::class - override suspend fun kotlinx.io.core.Output.render(obj: Any) { + override suspend fun kotlinx.io.Output.render(obj: Any) { append(obj.toString()) append('\n') } diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt index 111bba76..1b3a27cd 100644 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt @@ -2,10 +2,13 @@ package hep.dataforge.workspace import hep.dataforge.data.Data import hep.dataforge.data.await -import hep.dataforge.io.* +import hep.dataforge.io.Envelope +import hep.dataforge.io.IOFormat +import hep.dataforge.io.SimpleEnvelope +import hep.dataforge.io.readWith import kotlinx.coroutines.coroutineScope -import kotlinx.io.core.Input -import kotlinx.io.core.buildPacket +import kotlinx.io.Input +import kotlinx.io.buildPacket import kotlin.reflect.KClass /** diff --git a/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/FileDataTest.kt b/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/FileDataTest.kt index b73a4d59..8d8bc385 100644 --- a/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/FileDataTest.kt +++ b/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/FileDataTest.kt @@ -6,10 +6,10 @@ import hep.dataforge.io.IOFormat import hep.dataforge.io.io import hep.dataforge.meta.DFExperimental import kotlinx.coroutines.runBlocking -import kotlinx.io.core.Input -import kotlinx.io.core.Output -import kotlinx.io.core.readText -import kotlinx.io.core.writeText +import kotlinx.io.Input +import kotlinx.io.Output +import kotlinx.io.readText +import kotlinx.io.writeText import java.nio.file.Files import kotlin.test.Ignore import kotlin.test.Test diff --git a/settings.gradle.kts b/settings.gradle.kts index b486c03f..42033ac1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -31,4 +31,6 @@ include( ":dataforge-output-html", ":dataforge-workspace", ":dataforge-scripting" -) \ No newline at end of file +) + +//includeBuild("../kotlinx-io") \ No newline at end of file From 76e7f47528db48460f66593e02fbf62921db2d12 Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Sun, 1 Dec 2019 15:00:27 +0300 Subject: [PATCH 10/41] fix envelope formats --- build.gradle.kts | 6 +++--- dataforge-io/build.gradle.kts | 2 +- .../kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt | 2 +- .../kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt | 11 ++++------- .../kotlin/hep/dataforge/io/EnvelopeFormatTest.kt | 4 ++-- 5 files changed, 11 insertions(+), 14 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 9e432336..969de524 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,9 +1,9 @@ import scientifik.ScientifikExtension plugins { - id("scientifik.mpp") version "0.2.5" apply false - id("scientifik.jvm") version "0.2.5" apply false - id("scientifik.publish") version "0.2.5" apply false + id("scientifik.mpp") version "0.2.6" apply false + id("scientifik.jvm") version "0.2.6" apply false + id("scientifik.publish") version "0.2.6" apply false } val dataforgeVersion by extra("0.1.5-dev-3") diff --git a/dataforge-io/build.gradle.kts b/dataforge-io/build.gradle.kts index bb27ceff..a8d30c2d 100644 --- a/dataforge-io/build.gradle.kts +++ b/dataforge-io/build.gradle.kts @@ -9,7 +9,7 @@ scientifik { //withIO() } -val ioVersion by rootProject.extra("0.2.0-npm-dev-2") +val ioVersion by rootProject.extra("0.2.0-npm-dev-3") kotlin { sourceSets { 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 01888dac..7cea659f 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt @@ -67,7 +67,7 @@ class TaggedEnvelopeFormat( val metaFormat = io.metaFormat(tag.metaFormatKey) ?: error("Meta format with key ${tag.metaFormatKey} not found") - val meta: Meta = limit(tag.metaSize.toInt()).use { + val meta: Meta = limit(tag.metaSize.toInt()).run { metaFormat.run { readObject() } diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt index a8b72cda..b92e4d44 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt @@ -46,7 +46,7 @@ class TaglessEnvelopeFormat( writeProperty(META_LENGTH_PROPERTY, metaBytes.size + 2) writeUtf8String(metaStart + "\r\n") writeBinary(metaBytes) - writeUtf8String("\r\n") + writeRawString("\r\n") } //Printing data @@ -71,12 +71,9 @@ class TaglessEnvelopeFormat( val (key, value) = match.destructured properties[key] = value } - try { - line = readUtf8Line() - } catch (ex: EOFException) { - //If can't read line, return envelope without data - return SimpleEnvelope(Meta.empty, null) - } + //If can't read line, return envelope without data + if (eof()) return SimpleEnvelope(Meta.empty, null) + line = readUtf8Line() } var meta: Meta = EmptyMeta diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopeFormatTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopeFormatTest.kt index 0d6782b1..0851d6df 100644 --- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopeFormatTest.kt +++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/EnvelopeFormatTest.kt @@ -24,7 +24,7 @@ class EnvelopeFormatTest { fun testTaggedFormat(){ TaggedEnvelopeFormat.run { val byteArray = this.writeByteArray(envelope) - println(byteArray.decodeToString()) + //println(byteArray.decodeToString()) val res = readByteArray(byteArray) assertEquals(envelope.meta,res.meta) val double = res.data?.read { @@ -38,7 +38,7 @@ class EnvelopeFormatTest { fun testTaglessFormat(){ TaglessEnvelopeFormat.run { val byteArray = writeByteArray(envelope) - println(byteArray.decodeToString()) + //println(byteArray.decodeToString()) val res = readByteArray(byteArray) assertEquals(envelope.meta,res.meta) val double = res.data?.read { From ad2f5681b609ce38159fd93144cf38afe221b6b0 Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Sun, 1 Dec 2019 18:13:10 +0300 Subject: [PATCH 11/41] Fix everything bu function server --- .../hep/dataforge/io/EnvelopeBuilder.kt | 10 ++++---- .../kotlin/hep/dataforge/io/EnvelopeParts.kt | 11 ++++++++- .../hep/dataforge/io/TaggedEnvelopeFormat.kt | 4 +--- .../hep/dataforge/io/TaglessEnvelopeFormat.kt | 2 +- .../kotlin/hep/dataforge/io/MultipartTest.kt | 23 +++++++++++-------- 5 files changed, 31 insertions(+), 19 deletions(-) diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt index a0b21b64..b5a32590 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt @@ -22,13 +22,13 @@ class EnvelopeBuilder { var name by metaBuilder.string(key = Envelope.ENVELOPE_NAME_KEY) /** - * Construct a binary and transform it into byte-array based buffer + * Construct a data binary from given builder */ + @ExperimentalIoApi fun data(block: Output.() -> Unit) { - val bytes = buildBytes { - block() - } - data = ArrayBinary(bytes.toByteArray()) + val bytes = buildBytes(builder = block) + data = bytes.toByteArray().asBinary() //save data to byte array so + bytes.close() } internal fun build() = SimpleEnvelope(metaBuilder.seal(), data) diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt index d7d981c4..471ee3be 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt @@ -4,12 +4,15 @@ import hep.dataforge.context.Global import hep.dataforge.io.EnvelopeParts.FORMAT_META_KEY import hep.dataforge.io.EnvelopeParts.FORMAT_NAME_KEY import hep.dataforge.io.EnvelopeParts.INDEX_KEY +import hep.dataforge.io.EnvelopeParts.MULTIPART_DATA_SEPARATOR import hep.dataforge.io.EnvelopeParts.MULTIPART_DATA_TYPE import hep.dataforge.io.EnvelopeParts.SIZE_KEY import hep.dataforge.meta.* import hep.dataforge.names.asName import hep.dataforge.names.plus import hep.dataforge.names.toName +import kotlinx.io.text.readRawString +import kotlinx.io.text.writeRawString object EnvelopeParts { val MULTIPART_KEY = "multipart".asName() @@ -18,6 +21,8 @@ object EnvelopeParts { val FORMAT_NAME_KEY = Envelope.ENVELOPE_NODE_KEY + MULTIPART_KEY + "format" val FORMAT_META_KEY = Envelope.ENVELOPE_NODE_KEY + MULTIPART_KEY + "meta" + const val MULTIPART_DATA_SEPARATOR = "#~PART~#\r\n" + const val MULTIPART_DATA_TYPE = "envelope.multipart" } @@ -41,6 +46,7 @@ fun EnvelopeBuilder.multipart( data { format(formatMeta).run { envelopes.forEach { + writeRawString(MULTIPART_DATA_SEPARATOR) writeEnvelope(it) } } @@ -68,7 +74,8 @@ fun EnvelopeBuilder.multipart( format.run { var counter = 0 envelopes.forEach { (key, envelope) -> - writeObject(envelope) + writeRawString(MULTIPART_DATA_SEPARATOR) + writeEnvelope(envelope) meta { append(INDEX_KEY, buildMeta { "key" put key @@ -105,6 +112,8 @@ fun Envelope.parts(io: IOPlugin = Global.plugins.fetch(IOPlugin)): Sequence<Enve data?.read { sequence { repeat(size) { + val separator = readRawString(MULTIPART_DATA_SEPARATOR.length) + if(separator!= MULTIPART_DATA_SEPARATOR) error("Separator is expected") yield(readObject()) } } 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 7cea659f..0e18fa35 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaggedEnvelopeFormat.kt @@ -73,9 +73,7 @@ class TaggedEnvelopeFormat( } } - val data = buildBytes { - writeInput(this@readObject, tag.dataSize.toInt()) - } + val data = ByteArray(tag.dataSize.toInt()).also { readArray(it) }.asBinary() return SimpleEnvelope(meta, data) } diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt index b92e4d44..32787cf7 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt @@ -108,7 +108,7 @@ class TaglessEnvelopeFormat( } else { buildBytes { writeInput(this@readObject) - } + }.toByteArray().asBinary() } return SimpleEnvelope(meta, data) diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt index 34827c88..ae25df06 100644 --- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt +++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt @@ -18,24 +18,29 @@ class MultipartTest { } data { writeUtf8String("Hello World $it") - repeat(2000) { - writeInt(it) - } +// repeat(2000) { +// writeInt(it) +// } } } } + val partsEnvelope = Envelope { multipart(envelopes, TaggedEnvelopeFormat) } @Test fun testParts() { - val bytes = TaggedEnvelopeFormat.writeByteArray(partsEnvelope) - assertTrue { bytes.size > envelopes.sumBy { it.data!!.size.toInt() } } - val reconstructed = TaggedEnvelopeFormat.readByteArray(bytes) - val parts = reconstructed.parts()?.toList() ?: emptyList() - assertEquals(2, parts[2].meta["value"].int) - println(reconstructed.data!!.size) + TaggedEnvelopeFormat.run { + val singleEnvelopeData = writeBytes(envelopes[0]) + val singleEnvelopeSize = singleEnvelopeData.size + val bytes = writeBytes(partsEnvelope) + assertTrue(5*singleEnvelopeSize < bytes.size) + val reconstructed = bytes.readWith(this) + val parts = reconstructed.parts()?.toList() ?: emptyList() + assertEquals(2, parts[2].meta["value"].int) + println(reconstructed.data!!.size) + } } } \ No newline at end of file From ab47d7723e4930e6eb241b7e38840cdefe752aca Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Sun, 1 Dec 2019 19:46:53 +0300 Subject: [PATCH 12/41] All tests running --- .../kotlin/hep/dataforge/io/EnvelopeBuilder.kt | 9 +++++---- .../commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt | 4 ++-- .../kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt | 4 ++-- .../commonTest/kotlin/hep/dataforge/io/MultipartTest.kt | 7 ++++--- .../kotlin/hep/dataforge/io/tcp/EnvelopeClient.kt | 2 +- .../kotlin/hep/dataforge/io/tcp/EnvelopeServer.kt | 3 +++ 6 files changed, 17 insertions(+), 12 deletions(-) diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt index b5a32590..0f12c213 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt @@ -1,7 +1,10 @@ package hep.dataforge.io import hep.dataforge.meta.* -import kotlinx.io.* +import kotlinx.io.ArrayBinary +import kotlinx.io.Binary +import kotlinx.io.ExperimentalIoApi +import kotlinx.io.Output class EnvelopeBuilder { private val metaBuilder = MetaBuilder() @@ -26,9 +29,7 @@ class EnvelopeBuilder { */ @ExperimentalIoApi fun data(block: Output.() -> Unit) { - val bytes = buildBytes(builder = block) - data = bytes.toByteArray().asBinary() //save data to byte array so - bytes.close() + data = ArrayBinary.write(builder = block) } internal fun build() = SimpleEnvelope(metaBuilder.seal(), data) diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt index 471ee3be..8e0b16de 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt @@ -21,7 +21,7 @@ object EnvelopeParts { val FORMAT_NAME_KEY = Envelope.ENVELOPE_NODE_KEY + MULTIPART_KEY + "format" val FORMAT_META_KEY = Envelope.ENVELOPE_NODE_KEY + MULTIPART_KEY + "meta" - const val MULTIPART_DATA_SEPARATOR = "#~PART~#\r\n" + const val MULTIPART_DATA_SEPARATOR = "\r\n#~PART~#\r\n" const val MULTIPART_DATA_TYPE = "envelope.multipart" } @@ -113,7 +113,7 @@ fun Envelope.parts(io: IOPlugin = Global.plugins.fetch(IOPlugin)): Sequence<Enve sequence { repeat(size) { val separator = readRawString(MULTIPART_DATA_SEPARATOR.length) - if(separator!= MULTIPART_DATA_SEPARATOR) error("Separator is expected") + if(separator!= MULTIPART_DATA_SEPARATOR) error("Separator is expected, but $separator found") yield(readObject()) } } diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt index 32787cf7..5b22ae8c 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt @@ -106,9 +106,9 @@ class TaglessEnvelopeFormat( readArray(bytes) bytes.asBinary() } else { - buildBytes { + ArrayBinary.write { writeInput(this@readObject) - }.toByteArray().asBinary() + } } return SimpleEnvelope(meta, data) diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt index ae25df06..0f60c077 100644 --- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt +++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt @@ -3,6 +3,7 @@ package hep.dataforge.io import hep.dataforge.meta.DFExperimental import hep.dataforge.meta.get import hep.dataforge.meta.int +import kotlinx.io.text.writeRawString import kotlinx.io.text.writeUtf8String import kotlin.test.Test @@ -18,9 +19,9 @@ class MultipartTest { } data { writeUtf8String("Hello World $it") -// repeat(2000) { -// writeInt(it) -// } + repeat(300) { + writeRawString("$it ") + } } } } diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeClient.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeClient.kt index 6c7e36c6..9562d146 100644 --- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeClient.kt +++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeClient.kt @@ -38,7 +38,7 @@ class EnvelopeClient( suspend fun close() { try { respond( - Envelope.invoke { + Envelope { type = EnvelopeServer.SHUTDOWN_ENVELOPE_TYPE } ) diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeServer.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeServer.kt index 778e563f..10c5b712 100644 --- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeServer.kt +++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/tcp/EnvelopeServer.kt @@ -78,6 +78,9 @@ class EnvelopeServer( logger.debug { "Accepted request with type ${request.type} from ${socket.remoteSocketAddress}" } if (request.type == SHUTDOWN_ENVELOPE_TYPE) { //Echo shutdown command + outputStream.write{ + writeObject(request) + } logger.info { "Accepted graceful shutdown signal from ${socket.inetAddress}" } socket.close() return@thread From c789dabdae3db1f0be9b4defcf61cbd6ec6d52e7 Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Sun, 1 Dec 2019 20:53:32 +0300 Subject: [PATCH 13/41] Fix build with new IO --- .../io/yaml/FrontMatterEnvelopeFormat.kt | 39 ++++++++++++------- .../hep/dataforge/io/yaml/YamlMetaFormat.kt | 6 +-- .../dataforge/io/yaml/YamlMetaFormatTest.kt | 2 +- .../html/{HtmlOutput.kt => HtmlRenderer.kt} | 9 +++-- .../hep/dataforge/output/OutputManager.kt | 24 +++++++----- .../output/{Output.kt => Renderer.kt} | 4 +- .../output/{TextOutput.kt => TextRenderer.kt} | 33 ++++++++-------- .../hep/dataforge/output/ConsoleOutput.kt | 22 ----------- .../kotlin/hep/dataforge/output/outputJS.kt | 7 ++++ .../hep/dataforge/output/ConsoleOutput.kt | 13 ------- .../kotlin/hep/dataforge/output/outputJVM.kt | 5 +++ .../hep/dataforge/workspace/envelopeData.kt | 14 ++----- .../hep/dataforge/workspace/FileDataTest.kt | 8 ++-- 13 files changed, 86 insertions(+), 100 deletions(-) rename dataforge-output-html/src/commonMain/kotlin/hep/dataforge/output/html/{HtmlOutput.kt => HtmlRenderer.kt} (86%) rename dataforge-output/src/commonMain/kotlin/hep/dataforge/output/{Output.kt => Renderer.kt} (86%) rename dataforge-output/src/commonMain/kotlin/hep/dataforge/output/{TextOutput.kt => TextRenderer.kt} (56%) delete mode 100644 dataforge-output/src/jsMain/kotlin/hep/dataforge/output/ConsoleOutput.kt create mode 100644 dataforge-output/src/jsMain/kotlin/hep/dataforge/output/outputJS.kt delete mode 100644 dataforge-output/src/jvmMain/kotlin/hep/dataforge/output/ConsoleOutput.kt create mode 100644 dataforge-output/src/jvmMain/kotlin/hep/dataforge/output/outputJVM.kt diff --git a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt index be6e1d75..be74d080 100644 --- a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt +++ b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt @@ -5,8 +5,10 @@ import hep.dataforge.io.* import hep.dataforge.meta.DFExperimental import hep.dataforge.meta.EmptyMeta import hep.dataforge.meta.Meta -import kotlinx.io.Input -import kotlinx.io.Output +import kotlinx.io.* +import kotlinx.io.text.readUtf8Line +import kotlinx.io.text.writeRawString +import kotlinx.io.text.writeUtf8String import kotlinx.serialization.toUtf8Bytes @DFExperimental @@ -27,14 +29,17 @@ class FrontMatterEnvelopeFormat( metaTypeRegex.matchEntire(line)?.groupValues?.first() ?.let { io.metaFormat(it) } ?: YamlMetaFormat - val metaBlock = buildPacket { + val meta = buildBytes { do { - line = readUtf8Line() ?: error("Input does not contain closing front matter separator") - appendln(line) + line = readUtf8Line() + writeUtf8String(line + "\r\n") offset += line.toUtf8Bytes().size.toUInt() } while (!line.startsWith(SEPARATOR)) + }.read { + readMetaFormat.run { + readMeta() + } } - val meta = readMetaFormat.fromBytes(metaBlock) return PartialEnvelope(meta, offset, null) } @@ -48,23 +53,29 @@ class FrontMatterEnvelopeFormat( metaTypeRegex.matchEntire(line)?.groupValues?.first() ?.let { io.metaFormat(it) } ?: YamlMetaFormat - val metaBlock = buildPacket { + val meta = buildBytes { do { - appendln(readUtf8Line() ?: error("Input does not contain closing front matter separator")) + writeUtf8String(readUtf8Line() + "\r\n") } while (!line.startsWith(SEPARATOR)) + }.read { + readMetaFormat.run { + readMeta() + } } - val meta = readMetaFormat.fromBytes(metaBlock) - val bytes = readBytes() + val bytes = readRemaining() val data = bytes.asBinary() return SimpleEnvelope(meta, data) } override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) { val metaFormat = metaFormatFactory(formatMeta, io.context) - writeText("$SEPARATOR\r\n") + writeRawString("$SEPARATOR\r\n") metaFormat.run { writeObject(envelope.meta) } - writeText("$SEPARATOR\r\n") - envelope.data?.read { copyTo(this@writeEnvelope) } + writeRawString("$SEPARATOR\r\n") + //Printing data + envelope.data?.let { data -> + writeBinary(data) + } } companion object : EnvelopeFormatFactory { @@ -77,7 +88,7 @@ class FrontMatterEnvelopeFormat( } override fun peekFormat(io: IOPlugin, input: Input): EnvelopeFormat? { - val line = input.readUtf8Line(3, 30) + val line = input.readUtf8Line() return if (line != null && line.startsWith("---")) { invoke() } else { diff --git a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt index 5e31a69e..acd19652 100644 --- a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt +++ b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt @@ -11,13 +11,13 @@ import hep.dataforge.meta.toMeta import kotlinx.io.Input import kotlinx.io.Output import kotlinx.io.readUByte -import kotlinx.io.writeText +import kotlinx.io.text.writeUtf8String import org.yaml.snakeyaml.Yaml import java.io.InputStream private class InputAsStream(val input: Input) : InputStream() { override fun read(): Int { - if (input.endOfInput) return -1 + if (input.eof()) return -1 return input.readUByte().toInt() } @@ -34,7 +34,7 @@ class YamlMetaFormat(val meta: Meta) : MetaFormat { override fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor?) { val string = yaml.dump(meta.toMap(descriptor)) - writeText(string) + writeUtf8String(string) } override fun Input.readMeta(descriptor: NodeDescriptor?): Meta { diff --git a/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt b/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt index 5c5b3c18..2be83509 100644 --- a/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt +++ b/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt @@ -6,7 +6,7 @@ import hep.dataforge.meta.Meta import hep.dataforge.meta.buildMeta import hep.dataforge.meta.get import hep.dataforge.meta.seal -import org.junit.jupiter.api.Test +import kotlin.test.Test import kotlin.test.assertEquals diff --git a/dataforge-output-html/src/commonMain/kotlin/hep/dataforge/output/html/HtmlOutput.kt b/dataforge-output-html/src/commonMain/kotlin/hep/dataforge/output/html/HtmlRenderer.kt similarity index 86% rename from dataforge-output-html/src/commonMain/kotlin/hep/dataforge/output/html/HtmlOutput.kt rename to dataforge-output-html/src/commonMain/kotlin/hep/dataforge/output/html/HtmlRenderer.kt index 3ff23403..c0aeaaab 100644 --- a/dataforge-output-html/src/commonMain/kotlin/hep/dataforge/output/html/HtmlOutput.kt +++ b/dataforge-output-html/src/commonMain/kotlin/hep/dataforge/output/html/HtmlRenderer.kt @@ -3,7 +3,8 @@ package hep.dataforge.output.html import hep.dataforge.context.Context import hep.dataforge.meta.Meta import hep.dataforge.output.Output -import hep.dataforge.output.TextRenderer +import hep.dataforge.output.Renderer +import hep.dataforge.output.TextFormat import hep.dataforge.output.html.HtmlBuilder.Companion.HTML_CONVERTER_TYPE import hep.dataforge.provider.Type import hep.dataforge.provider.top @@ -14,11 +15,11 @@ import kotlinx.html.p import kotlin.reflect.KClass -class HtmlOutput<T : Any>(override val context: Context, private val consumer: TagConsumer<*>) : Output<T> { +class HtmlRenderer<T : Any>(override val context: Context, private val consumer: TagConsumer<*>) : Renderer<T> { private val cache = HashMap<KClass<*>, HtmlBuilder<*>>() /** - * Find the first [TextRenderer] matching the given object type. + * Find the first [TextFormat] matching the given object type. */ override fun render(obj: T, meta: Meta) { @@ -47,7 +48,7 @@ class HtmlOutput<T : Any>(override val context: Context, private val consumer: T } /** - * A text or binary renderer based on [Output] + * A text or binary renderer based on [Renderer] */ @Type(HTML_CONVERTER_TYPE) interface HtmlBuilder<T : Any> { diff --git a/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/OutputManager.kt b/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/OutputManager.kt index d9f7c1b2..c05d5bb4 100644 --- a/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/OutputManager.kt +++ b/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/OutputManager.kt @@ -1,9 +1,6 @@ package hep.dataforge.output -import hep.dataforge.context.AbstractPlugin -import hep.dataforge.context.Context -import hep.dataforge.context.PluginFactory -import hep.dataforge.context.PluginTag +import hep.dataforge.context.* import hep.dataforge.context.PluginTag.Companion.DATAFORGE_GROUP import hep.dataforge.meta.EmptyMeta import hep.dataforge.meta.Meta @@ -22,14 +19,14 @@ interface OutputManager { * Get an output specialized for given type, name and stage. * @param stage represents the node or directory for the output. Empty means root node. * @param name represents the name inside the node. - * @param meta configuration for [Output] (not for rendered object) + * @param meta configuration for [Renderer] (not for rendered object) */ operator fun <T : Any> get( type: KClass<out T>, name: Name, stage: Name = EmptyName, meta: Meta = EmptyMeta - ): Output<T> + ): Renderer<T> } /** @@ -44,7 +41,7 @@ inline operator fun <reified T : Any> OutputManager.get( name: Name, stage: Name = EmptyName, meta: Meta = EmptyMeta -): Output<T> { +): Renderer<T> { return get(T::class, name, stage, meta) } @@ -56,14 +53,21 @@ fun OutputManager.render(obj: Any, name: Name, stage: Name = EmptyName, meta: Me /** * System console output. - * The [ConsoleOutput] is used when no other [OutputManager] is provided. + * The [CONSOLE_RENDERER] is used when no other [OutputManager] is provided. */ -expect val ConsoleOutput: Output<Any> +val CONSOLE_RENDERER: Renderer<Any> = object : Renderer<Any> { + override fun render(obj: Any, meta: Meta) { + println(obj) + } + + override val context: Context get() = Global + +} class ConsoleOutputManager : AbstractPlugin(), OutputManager { override val tag: PluginTag get() = ConsoleOutputManager.tag - override fun <T : Any> get(type: KClass<out T>, name: Name, stage: Name, meta: Meta): Output<T> = ConsoleOutput + override fun <T : Any> get(type: KClass<out T>, name: Name, stage: Name, meta: Meta): Renderer<T> = CONSOLE_RENDERER companion object : PluginFactory<ConsoleOutputManager> { override val tag = PluginTag("output.console", group = DATAFORGE_GROUP) diff --git a/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/Output.kt b/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/Renderer.kt similarity index 86% rename from dataforge-output/src/commonMain/kotlin/hep/dataforge/output/Output.kt rename to dataforge-output/src/commonMain/kotlin/hep/dataforge/output/Renderer.kt index 091cb999..c1bcf6e5 100644 --- a/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/Output.kt +++ b/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/Renderer.kt @@ -7,11 +7,11 @@ import hep.dataforge.meta.Meta /** * A generic way to render any object in the output. * - * An object could be rendered either in append or overlay mode. The mode is decided by the [Output] + * An object could be rendered either in append or overlay mode. The mode is decided by the [Renderer] * based on its configuration and provided meta * */ -interface Output<in T : Any> : ContextAware { +interface Renderer<in T : Any> : ContextAware { /** * Render specific object with configuration. * diff --git a/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/TextOutput.kt b/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/TextRenderer.kt similarity index 56% rename from dataforge-output/src/commonMain/kotlin/hep/dataforge/output/TextOutput.kt rename to dataforge-output/src/commonMain/kotlin/hep/dataforge/output/TextRenderer.kt index 77f589df..cc9d24f3 100644 --- a/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/TextOutput.kt +++ b/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/TextRenderer.kt @@ -2,48 +2,50 @@ package hep.dataforge.output import hep.dataforge.context.Context import hep.dataforge.meta.Meta -import hep.dataforge.output.TextRenderer.Companion.TEXT_RENDERER_TYPE +import hep.dataforge.output.TextFormat.Companion.TEXT_RENDERER_TYPE import hep.dataforge.provider.Type import hep.dataforge.provider.top import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.io.Output +import kotlinx.io.text.writeUtf8String import kotlin.reflect.KClass -class TextOutput(override val context: Context, private val output: kotlinx.io.Output) : Output<Any> { - private val cache = HashMap<KClass<*>, TextRenderer>() +class TextRenderer(override val context: Context, private val output: Output) : Renderer<Any> { + private val cache = HashMap<KClass<*>, TextFormat>() /** - * Find the first [TextRenderer] matching the given object type. + * Find the first [TextFormat] matching the given object type. */ override fun render(obj: Any, meta: Meta) { - val renderer: TextRenderer = if (obj is CharSequence) { - DefaultTextRenderer + val format: TextFormat = if (obj is CharSequence) { + DefaultTextFormat } else { val value = cache[obj::class] if (value == null) { val answer = - context.top<TextRenderer>(TEXT_RENDERER_TYPE).values.firstOrNull { it.type.isInstance(obj) } + context.top<TextFormat>(TEXT_RENDERER_TYPE).values.firstOrNull { it.type.isInstance(obj) } if (answer != null) { cache[obj::class] = answer answer } else { - DefaultTextRenderer + DefaultTextFormat } } else { value } } context.launch(Dispatchers.Output) { - renderer.run { output.render(obj) } + format.run { output.render(obj) } } } } /** - * A text or binary renderer based on [kotlinx.io.Output] + * A text or binary renderer based on [Output] */ @Type(TEXT_RENDERER_TYPE) -interface TextRenderer { +interface TextFormat { /** * The priority of this renderer compared to other renderers */ @@ -53,19 +55,18 @@ interface TextRenderer { */ val type: KClass<*> - suspend fun kotlinx.io.Output.render(obj: Any) + suspend fun Output.render(obj: Any) companion object { const val TEXT_RENDERER_TYPE = "dataforge.textRenderer" } } -object DefaultTextRenderer : TextRenderer { +object DefaultTextFormat : TextFormat { override val priority: Int = Int.MAX_VALUE override val type: KClass<*> = Any::class - override suspend fun kotlinx.io.Output.render(obj: Any) { - append(obj.toString()) - append('\n') + override suspend fun Output.render(obj: Any) { + writeUtf8String(obj.toString() + "\n") } } \ No newline at end of file diff --git a/dataforge-output/src/jsMain/kotlin/hep/dataforge/output/ConsoleOutput.kt b/dataforge-output/src/jsMain/kotlin/hep/dataforge/output/ConsoleOutput.kt deleted file mode 100644 index b927a386..00000000 --- a/dataforge-output/src/jsMain/kotlin/hep/dataforge/output/ConsoleOutput.kt +++ /dev/null @@ -1,22 +0,0 @@ -package hep.dataforge.output - -import hep.dataforge.context.Context -import hep.dataforge.context.Global -import hep.dataforge.meta.Meta -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers - -/** - * System console output. - * The [ConsoleOutput] is used when no other [OutputManager] is provided. - */ -actual val ConsoleOutput: Output<Any> = object : Output<Any> { - override fun render(obj: Any, meta: Meta) { - println(obj) - } - - override val context: Context get() = Global - -} - -actual val Dispatchers.Output: CoroutineDispatcher get() = Dispatchers.Default \ No newline at end of file diff --git a/dataforge-output/src/jsMain/kotlin/hep/dataforge/output/outputJS.kt b/dataforge-output/src/jsMain/kotlin/hep/dataforge/output/outputJS.kt new file mode 100644 index 00000000..18a71f07 --- /dev/null +++ b/dataforge-output/src/jsMain/kotlin/hep/dataforge/output/outputJS.kt @@ -0,0 +1,7 @@ +package hep.dataforge.output + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers + + +actual val Dispatchers.Output: CoroutineDispatcher get() = Dispatchers.Default \ No newline at end of file diff --git a/dataforge-output/src/jvmMain/kotlin/hep/dataforge/output/ConsoleOutput.kt b/dataforge-output/src/jvmMain/kotlin/hep/dataforge/output/ConsoleOutput.kt deleted file mode 100644 index 57ae4294..00000000 --- a/dataforge-output/src/jvmMain/kotlin/hep/dataforge/output/ConsoleOutput.kt +++ /dev/null @@ -1,13 +0,0 @@ -package hep.dataforge.output - -import hep.dataforge.context.Global -import kotlinx.coroutines.Dispatchers -import kotlinx.io.streams.asOutput - -/** - * System console output. - * The [ConsoleOutput] is used when no other [OutputManager] is provided. - */ -actual val ConsoleOutput: Output<Any> = TextOutput(Global, System.out.asOutput()) - -actual val Dispatchers.Output get() = Dispatchers.IO \ No newline at end of file diff --git a/dataforge-output/src/jvmMain/kotlin/hep/dataforge/output/outputJVM.kt b/dataforge-output/src/jvmMain/kotlin/hep/dataforge/output/outputJVM.kt new file mode 100644 index 00000000..ea7c416c --- /dev/null +++ b/dataforge-output/src/jvmMain/kotlin/hep/dataforge/output/outputJVM.kt @@ -0,0 +1,5 @@ +package hep.dataforge.output + +import kotlinx.coroutines.Dispatchers + +actual val Dispatchers.Output get() = Dispatchers.IO \ No newline at end of file diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt index 1b3a27cd..1a713e37 100644 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt @@ -7,8 +7,7 @@ import hep.dataforge.io.IOFormat import hep.dataforge.io.SimpleEnvelope import hep.dataforge.io.readWith import kotlinx.coroutines.coroutineScope -import kotlinx.io.Input -import kotlinx.io.buildPacket +import kotlinx.io.ArrayBinary import kotlin.reflect.KClass /** @@ -22,15 +21,8 @@ suspend fun <T : Any> Data<T>.toEnvelope(format: IOFormat<T>): Envelope { val obj = coroutineScope { await(this) } - val binary = object : Binary { - override fun <R> read(block: Input.() -> R): R { - //TODO optimize away additional copy by creating inputs that reads directly from output - val packet = buildPacket { - format.run { writeObject(obj) } - } - return packet.block() - } - + val binary = ArrayBinary.write { + format.run { writeObject(obj) } } return SimpleEnvelope(meta, binary) } \ No newline at end of file diff --git a/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/FileDataTest.kt b/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/FileDataTest.kt index 8d8bc385..4fc9e9a4 100644 --- a/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/FileDataTest.kt +++ b/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/FileDataTest.kt @@ -8,8 +8,8 @@ import hep.dataforge.meta.DFExperimental import kotlinx.coroutines.runBlocking import kotlinx.io.Input import kotlinx.io.Output -import kotlinx.io.readText -import kotlinx.io.writeText +import kotlinx.io.text.readUtf8String +import kotlinx.io.text.writeUtf8String import java.nio.file.Files import kotlin.test.Ignore import kotlin.test.Test @@ -30,11 +30,11 @@ class FileDataTest { object StringIOFormat : IOFormat<String> { override fun Output.writeObject(obj: String) { - writeText(obj) + writeUtf8String(obj) } override fun Input.readObject(): String { - return readText() + return readUtf8String() } } From 0ec42689d729b3fb9bbd99a81c3163d6f286650b Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Tue, 3 Dec 2019 17:01:56 +0300 Subject: [PATCH 14/41] Minor change to Goal API --- .../kotlin/hep/dataforge/data/Data.kt | 10 ++--- .../kotlin/hep/dataforge/data/DataNode.kt | 28 ++++++++------ .../kotlin/hep/dataforge/data/Goal.kt | 37 +++++++++---------- .../kotlin/hep/dataforge/data/dataCast.kt | 2 +- .../dataforge/io/yaml/YamlMetaFormatTest.kt | 4 +- .../hep/dataforge/workspace/envelopeData.kt | 5 +-- 6 files changed, 42 insertions(+), 44 deletions(-) diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Data.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Data.kt index 718fb46f..ea25e070 100644 --- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Data.kt +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Data.kt @@ -91,7 +91,7 @@ fun <T : Any, R : Any> Data<T>.map( meta: Meta = this.meta, block: suspend CoroutineScope.(T) -> R ): Data<R> = DynamicData(outputType, meta, coroutineContext, listOf(this)) { - block(await(this)) + block(await()) } @@ -103,7 +103,7 @@ inline fun <T : Any, reified R : Any> Data<T>.map( meta: Meta = this.meta, noinline block: suspend CoroutineScope.(T) -> R ): Data<R> = DynamicData(R::class, meta, coroutineContext, listOf(this)) { - block(await(this)) + block(await()) } /** @@ -119,7 +119,7 @@ inline fun <T : Any, reified R : Any> Collection<Data<T>>.reduce( coroutineContext, this ) { - block(map { run { it.await(this) } }) + block(map { run { it.await() } }) } fun <K, T : Any, R : Any> Map<K, Data<T>>.reduce( @@ -133,7 +133,7 @@ fun <K, T : Any, R : Any> Map<K, Data<T>>.reduce( coroutineContext, this.values ) { - block(mapValues { it.value.await(this) }) + block(mapValues { it.value.await() }) } @@ -153,7 +153,7 @@ inline fun <K, T : Any, reified R : Any> Map<K, Data<T>>.reduce( coroutineContext, this.values ) { - block(mapValues { it.value.await(this) }) + block(mapValues { it.value.await() }) } diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt index a673f0b7..764aea62 100644 --- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt @@ -4,6 +4,7 @@ import hep.dataforge.meta.* import hep.dataforge.names.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import kotlin.collections.component1 import kotlin.collections.component2 @@ -55,6 +56,19 @@ interface DataNode<out T : Any> : MetaRepr { } } + /** + * Start computation for all goals in data node and return a job for the whole node + */ + @Suppress("DeferredResultUnused") + fun CoroutineScope.startAll(): Job = launch { + items.values.forEach { + when (it) { + is DataItem.Node<*> -> it.node.run { startAll() } + is DataItem.Leaf<*> -> it.data.run { startAsync() } + } + } + } + companion object { const val TYPE = "dataNode" @@ -68,21 +82,11 @@ interface DataNode<out T : Any> : MetaRepr { } } +suspend fun <T: Any> DataNode<T>.join(): Unit = coroutineScope { startAll().join() } + val <T : Any> DataItem<T>?.node: DataNode<T>? get() = (this as? DataItem.Node<T>)?.node val <T : Any> DataItem<T>?.data: Data<T>? get() = (this as? DataItem.Leaf<T>)?.data -/** - * Start computation for all goals in data node and return a job for the whole node - */ -fun DataNode<*>.launchAll(scope: CoroutineScope): Job = scope.launch { - items.values.forEach { - when (it) { - is DataItem.Node<*> -> it.node.launchAll(scope) - is DataItem.Leaf<*> -> it.data.start(scope) - } - } -} - operator fun <T : Any> DataNode<T>.get(name: Name): DataItem<T>? = when (name.length) { 0 -> error("Empty name") 1 -> items[name.first()] diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Goal.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Goal.kt index 8275d31e..8c0eeec7 100644 --- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Goal.kt +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Goal.kt @@ -15,9 +15,7 @@ interface Goal<out T> { * Get ongoing computation or start a new one. * Does not guarantee thread safety. In case of multi-thread access, could create orphan computations. */ - fun startAsync(scope: CoroutineScope): Deferred<T> - - suspend fun CoroutineScope.await(): T = startAsync(this).await() + fun CoroutineScope.startAsync(): Deferred<T> /** * Reset the computation @@ -29,17 +27,15 @@ interface Goal<out T> { } } -fun Goal<*>.start(scope: CoroutineScope): Job = startAsync(scope) +suspend fun <T> Goal<T>.await(): T = coroutineScope { startAsync().await() } val Goal<*>.isComplete get() = result?.isCompleted ?: false -suspend fun <T> Goal<T>.await(scope: CoroutineScope): T = scope.await() - open class StaticGoal<T>(val value: T) : Goal<T> { override val dependencies: Collection<Goal<*>> get() = emptyList() override val result: Deferred<T> = CompletableDeferred(value) - override fun startAsync(scope: CoroutineScope): Deferred<T> = result + override fun CoroutineScope.startAsync(): Deferred<T> = result override fun reset() { //doNothing @@ -59,18 +55,19 @@ open class DynamicGoal<T>( * Get ongoing computation or start a new one. * Does not guarantee thread safety. In case of multi-thread access, could create orphan computations. */ - override fun startAsync(scope: CoroutineScope): Deferred<T> { - val startedDependencies = this.dependencies.map { goal -> - goal.startAsync(scope) + override fun CoroutineScope.startAsync(): Deferred<T> { + val startedDependencies = this@DynamicGoal.dependencies.map { goal -> + goal.run { startAsync() } } - return result ?: scope.async(coroutineContext + CoroutineMonitor() + Dependencies(startedDependencies)) { - startedDependencies.forEach { deferred -> - deferred.invokeOnCompletion { error -> - if (error != null) cancel(CancellationException("Dependency $deferred failed with error: ${error.message}")) + return result + ?: async(this@DynamicGoal.coroutineContext + CoroutineMonitor() + Dependencies(startedDependencies)) { + startedDependencies.forEach { deferred -> + deferred.invokeOnCompletion { error -> + if (error != null) cancel(CancellationException("Dependency $deferred failed with error: ${error.message}")) + } } - } - block() - }.also { result = it } + block() + }.also { result = it } } /** @@ -89,7 +86,7 @@ fun <T, R> Goal<T>.map( coroutineContext: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.(T) -> R ): Goal<R> = DynamicGoal(coroutineContext, listOf(this)) { - block(await(this)) + block(await()) } /** @@ -99,7 +96,7 @@ fun <T, R> Collection<Goal<T>>.reduce( coroutineContext: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.(Collection<T>) -> R ): Goal<R> = DynamicGoal(coroutineContext, this) { - block(map { run { it.await(this) } }) + block(map { run { it.await() } }) } /** @@ -112,6 +109,6 @@ fun <K, T, R> Map<K, Goal<T>>.reduce( coroutineContext: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.(Map<K, T>) -> R ): Goal<R> = DynamicGoal(coroutineContext, this.values) { - block(mapValues { it.value.await(this) }) + block(mapValues { it.value.await() }) } diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataCast.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataCast.kt index 2bf8adde..21301dd4 100644 --- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataCast.kt +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/dataCast.kt @@ -41,7 +41,7 @@ fun <R : Any> Data<*>.cast(type: KClass<out R>): Data<R> { override val meta: Meta get() = this@cast.meta override val dependencies: Collection<Goal<*>> get() = this@cast.dependencies override val result: Deferred<R>? get() = this@cast.result as Deferred<R> - override fun startAsync(scope: CoroutineScope): Deferred<R> = this@cast.startAsync(scope) as Deferred<R> + override fun CoroutineScope.startAsync(): Deferred<R> = this@cast.run { startAsync() as Deferred<R> } override fun reset() = this@cast.reset() override val type: KClass<out R> = type } diff --git a/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt b/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt index 2be83509..b330e080 100644 --- a/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt +++ b/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt @@ -10,9 +10,9 @@ import kotlin.test.Test import kotlin.test.assertEquals -class YamlMetaFormatTest{ +class YamlMetaFormatTest { @Test - fun testYamlMetaFormat(){ + fun testYamlMetaFormat() { val meta = buildMeta { "a" put 22 "node" put { diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt index 1a713e37..d378726f 100644 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/envelopeData.kt @@ -6,7 +6,6 @@ import hep.dataforge.io.Envelope import hep.dataforge.io.IOFormat import hep.dataforge.io.SimpleEnvelope import hep.dataforge.io.readWith -import kotlinx.coroutines.coroutineScope import kotlinx.io.ArrayBinary import kotlin.reflect.KClass @@ -18,9 +17,7 @@ fun <T : Any> Envelope.toData(type: KClass<out T>, format: IOFormat<T>): Data<T> } suspend fun <T : Any> Data<T>.toEnvelope(format: IOFormat<T>): Envelope { - val obj = coroutineScope { - await(this) - } + val obj = await() val binary = ArrayBinary.write { format.run { writeObject(obj) } } From 4eb07949b41f6495259266de6995c39746a6d85a Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Tue, 10 Dec 2019 17:15:20 +0300 Subject: [PATCH 15/41] Bump build and io versions --- build.gradle.kts | 8 ++++---- dataforge-io/build.gradle.kts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 969de524..ccc46351 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,12 +1,12 @@ import scientifik.ScientifikExtension plugins { - id("scientifik.mpp") version "0.2.6" apply false - id("scientifik.jvm") version "0.2.6" apply false - id("scientifik.publish") version "0.2.6" apply false + id("scientifik.mpp") version "0.2.7" apply false + id("scientifik.jvm") version "0.2.7" apply false + id("scientifik.publish") version "0.2.7" apply false } -val dataforgeVersion by extra("0.1.5-dev-3") +val dataforgeVersion by extra("0.1.5-dev-4") val bintrayRepo by extra("dataforge") val githubProject by extra("dataforge-core") diff --git a/dataforge-io/build.gradle.kts b/dataforge-io/build.gradle.kts index a8d30c2d..fa4b336d 100644 --- a/dataforge-io/build.gradle.kts +++ b/dataforge-io/build.gradle.kts @@ -9,7 +9,7 @@ scientifik { //withIO() } -val ioVersion by rootProject.extra("0.2.0-npm-dev-3") +val ioVersion by rootProject.extra("0.2.0-npm-dev-4") kotlin { sourceSets { From be2bca24b125be629b120759441089c65491652d Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Sun, 22 Dec 2019 20:29:32 +0300 Subject: [PATCH 16/41] Build migration --- README.md | 3 +++ build.gradle.kts | 13 +++++-------- dataforge-io/build.gradle.kts | 7 +++---- dataforge-io/dataforge-io-yaml/build.gradle.kts | 4 ++++ 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index b712501c..626ee576 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +[](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub) + +[  ](https://bintray.com/mipt-npm/dataforge/dataforge-meta/_latestVersion) [](https://zenodo.org/badge/latestdoi/148831678) diff --git a/build.gradle.kts b/build.gradle.kts index ccc46351..0dbaef48 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,12 +1,12 @@ -import scientifik.ScientifikExtension plugins { - id("scientifik.mpp") version "0.2.7" apply false - id("scientifik.jvm") version "0.2.7" apply false - id("scientifik.publish") version "0.2.7" apply false + val toolsVersion = "0.3.1" + id("scientifik.mpp") version toolsVersion apply false + id("scientifik.jvm") version toolsVersion apply false + id("scientifik.publish") version toolsVersion apply false } -val dataforgeVersion by extra("0.1.5-dev-4") +val dataforgeVersion by extra("0.1.5-dev-5") val bintrayRepo by extra("dataforge") val githubProject by extra("dataforge-core") @@ -22,7 +22,4 @@ allprojects { subprojects { apply(plugin = "scientifik.publish") - afterEvaluate { - extensions.findByType<ScientifikExtension>()?.apply { withDokka() } - } } \ No newline at end of file diff --git a/dataforge-io/build.gradle.kts b/dataforge-io/build.gradle.kts index fa4b336d..2606f5ea 100644 --- a/dataforge-io/build.gradle.kts +++ b/dataforge-io/build.gradle.kts @@ -1,13 +1,12 @@ +import scientifik.useSerialization + plugins { id("scientifik.mpp") } description = "IO module" -scientifik { - withSerialization() - //withIO() -} +useSerialization() val ioVersion by rootProject.extra("0.2.0-npm-dev-4") diff --git a/dataforge-io/dataforge-io-yaml/build.gradle.kts b/dataforge-io/dataforge-io-yaml/build.gradle.kts index d287d9ac..bad9b42e 100644 --- a/dataforge-io/dataforge-io-yaml/build.gradle.kts +++ b/dataforge-io/dataforge-io-yaml/build.gradle.kts @@ -1,9 +1,13 @@ +import scientifik.useSerialization + plugins { id("scientifik.jvm") } description = "YAML meta IO" +useSerialization() + dependencies { api(project(":dataforge-io")) api("org.yaml:snakeyaml:1.25") From effac131de001925fcf5cf49f8511f41fafe2557 Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Sun, 22 Dec 2019 20:29:58 +0300 Subject: [PATCH 17/41] Fix name comparison --- .../src/commonMain/kotlin/hep/dataforge/names/Name.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 41680eb0..084f27df 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt @@ -182,6 +182,8 @@ fun Name.startsWith(token: NameToken): Boolean = first() == token fun Name.endsWith(token: NameToken): Boolean = last() == token -fun Name.startsWith(name: Name): Boolean = tokens.subList(0, name.length) == name.tokens +fun Name.startsWith(name: Name): Boolean = + this.length >= name.length && tokens.subList(0, name.length) == name.tokens -fun Name.endsWith(name: Name): Boolean = tokens.subList(length - name.length, length) == name.tokens \ No newline at end of file +fun Name.endsWith(name: Name): Boolean = + this.length >= name.length && tokens.subList(length - name.length, length) == name.tokens \ No newline at end of file From f9ae9348e20f20c0bbccffae63de85640a325b88 Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Sun, 22 Dec 2019 20:30:47 +0300 Subject: [PATCH 18/41] Meta subtyping and get by empty name. --- .../kotlin/hep/dataforge/meta/Meta.kt | 29 +++++++++++-------- .../kotlin/hep/dataforge/meta/MutableMeta.kt | 10 ++++--- 2 files changed, 23 insertions(+), 16 deletions(-) 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 e5aa0c45..57364593 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt @@ -66,8 +66,14 @@ interface Meta : MetaRepr { /* Get operations*/ +/** + * Perform recursive item search using given [name]. Each [NameToken] is treated as a name in [Meta.items] of a parent node. + * + * If [name] is empty reture 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) { @@ -78,6 +84,9 @@ operator fun Meta?.get(name: Name): MetaItem<*>? { } operator fun Meta?.get(token: NameToken): MetaItem<*>? = this?.items?.get(token) +/** + * Parse [Name] from [key] using full name notation and pass it to [Meta.get] + */ operator fun Meta?.get(key: String): MetaItem<*>? = get(key.toName()) /** @@ -113,12 +122,16 @@ operator fun Meta.iterator(): Iterator<Pair<Name, MetaItem<*>>> = sequence().ite /** * A meta node that ensures that all of its descendants has at least the same type */ -interface MetaNode<M : MetaNode<M>> : Meta { +interface MetaNode<out M : MetaNode<M>> : Meta { override val items: Map<NameToken, MetaItem<M>> } -operator fun <M : MetaNode<M>> MetaNode<M>?.get(name: Name): MetaItem<M>? { +/** + * The same as [Meta.get], but with specific node type + */ +operator fun <M : MetaNode<M>> M?.get(name: Name): MetaItem<M>? { if (this == null) return null + if (name.isEmpty()) return NodeItem(this) return name.first()?.let { token -> val tail = name.cutFirst() when (tail.length) { @@ -128,17 +141,9 @@ operator fun <M : MetaNode<M>> MetaNode<M>?.get(name: Name): MetaItem<M>? { } } -operator fun <M : MetaNode<M>> MetaNode<M>?.get(key: String): MetaItem<M>? = if (this == null) { - null -} else { - this[key.toName()] -} +operator fun <M : MetaNode<M>> M?.get(key: String): MetaItem<M>? = this[key.toName()] -operator fun <M : MetaNode<M>> MetaNode<M>?.get(key: NameToken): MetaItem<M>? = if (this == null) { - null -} else { - this[key.asName()] -} +operator fun <M : MetaNode<M>> M?.get(key: NameToken): MetaItem<M>? = this[key.asName()] /** * Equals, hashcode and to string for any meta 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 4da08ce4..7a379f24 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt @@ -3,7 +3,7 @@ package hep.dataforge.meta import hep.dataforge.names.* import hep.dataforge.values.Value -interface MutableMeta<M : MutableMeta<M>> : MetaNode<M> { +interface MutableMeta<out M : MutableMeta<M>> : MetaNode<M> { override val items: Map<NameToken, MetaItem<M>> operator fun set(name: Name, item: MetaItem<*>?) // fun onChange(owner: Any? = null, action: (Name, MetaItem<*>?, MetaItem<*>?) -> Unit) @@ -54,13 +54,14 @@ abstract class AbstractMutableMeta<M : MutableMeta<M>> : AbstractMetaNode<M>(), 0 -> error("Can't setValue meta item for empty name") 1 -> { val token = name.first()!! - replaceItem(token, get(name), wrapItem(item)) + @Suppress("UNCHECKED_CAST") val oldItem: MetaItem<M>? = get(name) as? MetaItem<M> + replaceItem(token, oldItem, wrapItem(item)) } else -> { val token = name.first()!! //get existing or create new node. Query is ignored for new node - if(items[token] == null){ - replaceItem(token,null, MetaItem.NodeItem(empty())) + if (items[token] == null) { + replaceItem(token, null, MetaItem.NodeItem(empty())) } items[token]?.node!![name.cutFirst()] = item } @@ -71,6 +72,7 @@ abstract class AbstractMutableMeta<M : MutableMeta<M>> : AbstractMetaNode<M>(), @Suppress("NOTHING_TO_INLINE") inline fun MutableMeta<*>.remove(name: Name) = set(name, null) + @Suppress("NOTHING_TO_INLINE") inline fun MutableMeta<*>.remove(name: String) = remove(name.toName()) From 659fded3a5695dc784bbe1db132563828a5829ef Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Tue, 24 Dec 2019 20:06:16 +0300 Subject: [PATCH 19/41] Replace scecification builders by invokations --- .../kotlin/hep/dataforge/data/DataFilter.kt | 2 +- .../hep/dataforge/descriptors/ItemDescriptor.kt | 15 +++++++-------- .../kotlin/hep/dataforge/meta/Specific.kt | 8 ++++---- .../hep/dataforge/descriptors/DescriptorTest.kt | 2 +- .../kotlin/hep/dataforge/meta/MetaDelegateTest.kt | 2 +- .../hep/dataforge/meta/SpecificationTest.kt | 2 +- .../kotlin/hep/dataforge/workspace/TaskBuilder.kt | 2 +- .../kotlin/hep/dataforge/workspace/TaskModel.kt | 2 +- 8 files changed, 17 insertions(+), 18 deletions(-) diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataFilter.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataFilter.kt index a55aac9d..3770c9a8 100644 --- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataFilter.kt +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataFilter.kt @@ -54,4 +54,4 @@ fun <T : Any> DataNode<T>.filter(filter: Meta): DataNode<T> = filter(DataFilter. * Filter data using [DataFilter] builder */ fun <T : Any> DataNode<T>.filter(filterBuilder: DataFilter.() -> Unit): DataNode<T> = - filter(DataFilter.build(filterBuilder)) \ No newline at end of file + filter(DataFilter.invoke(filterBuilder)) \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt index 0658dfd8..79c17e47 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt @@ -86,7 +86,7 @@ class NodeDescriptor(config: Config) : ItemDescriptor(config) { * Add a value descriptor using block for */ fun value(name: String, block: ValueDescriptor.() -> Unit) { - value(name, ValueDescriptor.build { this.name = name }.apply(block)) + value(name, ValueDescriptor { this.name = name }.apply(block)) } /** @@ -105,7 +105,7 @@ class NodeDescriptor(config: Config) : ItemDescriptor(config) { } fun node(name: String, block: NodeDescriptor.() -> Unit) { - node(name, build { this.name = name }.apply(block)) + node(name, invoke { this.name = name }.apply(block)) } val items: Map<String, ItemDescriptor> get() = nodes + values @@ -205,12 +205,11 @@ class ValueDescriptor(config: Config) : ItemDescriptor(config) { override fun wrap(config: Config): ValueDescriptor = ValueDescriptor(config) - inline fun <reified E : Enum<E>> enum(name: String) = - build { - this.name = name - type(ValueType.STRING) - this.allowedValues = enumValues<E>().map { Value.of(it.name) } - } + inline fun <reified E : Enum<E>> enum(name: String) = ValueDescriptor { + this.name = name + type(ValueType.STRING) + this.allowedValues = enumValues<E>().map { Value.of(it.name) } + } // /** // * Build a value descriptor from annotation diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt index d3ec418a..a4ebffe6 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt @@ -25,9 +25,9 @@ interface Specification<T : Specific> { return wrap(config).apply(action) } - fun build(action: T.() -> Unit) = update(Config(), action) + operator fun invoke(action: T.() -> Unit) = update(Config(), action) - fun empty() = build { } + fun empty() = wrap(Config()) /** * Wrap generic configuration producing instance of desired type @@ -67,7 +67,7 @@ fun <C : Specific> Specific.spec( key: Name? = null ): MutableMorphDelegate<Config, C> = MutableMorphDelegate(config, key) { spec.wrap(it) } -fun <T: Specific> MetaItem<*>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(it)} +fun <T : Specific> MetaItem<*>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(it) } @JvmName("configSpec") -fun <T: Specific> MetaItem<Config>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(it)} \ No newline at end of file +fun <T : Specific> MetaItem<Config>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(it) } \ No newline at end of file diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/descriptors/DescriptorTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/descriptors/DescriptorTest.kt index 79800ec5..81d25285 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/descriptors/DescriptorTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/descriptors/DescriptorTest.kt @@ -6,7 +6,7 @@ import kotlin.test.assertEquals class DescriptorTest { - val descriptor = NodeDescriptor.build { + val descriptor = NodeDescriptor { node("aNode") { info = "A root demo node" value("b") { diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt index 997a13e3..162a3852 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt @@ -29,7 +29,7 @@ class MetaDelegateTest { testObject.config["myValue"] = "theString" testObject.enumValue = TestEnum.NO - testObject.inner = innerSpec.build { innerValue = "ddd"} + testObject.inner = innerSpec { innerValue = "ddd" } assertEquals("theString", testObject.myValue) assertEquals(TestEnum.NO, testObject.enumValue) diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SpecificationTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SpecificationTest.kt index 9098cf18..c11c137b 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SpecificationTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SpecificationTest.kt @@ -15,7 +15,7 @@ class SpecificationTest { @Test fun testSpecific(){ - val testObject = TestSpecific.build { + val testObject = TestSpecific { list = emptyList() } assertEquals(emptyList(), testObject.list) diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt index 3ff86325..6ae15fa3 100644 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt @@ -201,7 +201,7 @@ class TaskBuilder<R : Any>(val name: Name, val type: KClass<out R>) { * Use DSL to create a descriptor for this task */ fun description(transform: NodeDescriptor.() -> Unit) { - this.descriptor = NodeDescriptor.build(transform) + this.descriptor = NodeDescriptor(transform) } internal fun build(): GenericTask<R> { 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 71a5c006..45b07fca 100644 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt @@ -105,7 +105,7 @@ fun <T : Any> TaskDependencyContainer.dependsOn( * Add custom data dependency */ fun TaskDependencyContainer.data(action: DataFilter.() -> Unit): DataDependency = - DataDependency(DataFilter.build(action)).also { add(it) } + DataDependency(DataFilter(action)).also { add(it) } /** * User-friendly way to add data dependency From ce8be7854901190b0d2396078f47f93fb4719326 Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Tue, 24 Dec 2019 20:59:14 +0300 Subject: [PATCH 20/41] Replace EmptyName by Name.Empty Composite builders for node descriptor --- .../hep/dataforge/io/TaglessEnvelopeFormat.kt | 6 +- .../jvmMain/kotlin/hep/dataforge/io/fileIO.kt | 2 +- .../dataforge/descriptors/ItemDescriptor.kt | 70 +++++++++++++------ .../kotlin/hep/dataforge/meta/Meta.kt | 2 +- .../hep/dataforge/meta/configDelegates.kt | 4 +- .../hep/dataforge/meta/metaDelegates.kt | 4 +- .../kotlin/hep/dataforge/names/Name.kt | 8 +-- .../hep/dataforge/output/OutputManager.kt | 7 +- .../hep/dataforge/workspace/Dependency.kt | 11 +-- .../hep/dataforge/workspace/TaskBuilder.kt | 5 +- .../hep/dataforge/workspace/TaskModel.kt | 11 ++- .../dataforge/workspace/WorkspaceBuilder.kt | 5 +- 12 files changed, 81 insertions(+), 54 deletions(-) diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt index 5b22ae8c..0ed5214b 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt @@ -72,7 +72,7 @@ class TaglessEnvelopeFormat( properties[key] = value } //If can't read line, return envelope without data - if (eof()) return SimpleEnvelope(Meta.empty, null) + if (eof()) return SimpleEnvelope(Meta.EMPTY, null) line = readUtf8Line() } @@ -135,7 +135,7 @@ class TaglessEnvelopeFormat( line = readUtf8Line() offset += line.toUtf8Bytes().size.toUInt() } catch (ex: EOFException) { - return PartialEnvelope(Meta.empty, offset.toUInt(), 0.toULong()) + return PartialEnvelope(Meta.EMPTY, offset.toUInt(), 0.toULong()) } } @@ -155,7 +155,7 @@ class TaglessEnvelopeFormat( } do { - line = readUtf8Line() ?: return PartialEnvelope(Meta.empty, offset.toUInt(), 0.toULong()) + line = readUtf8Line() ?: return PartialEnvelope(Meta.EMPTY, offset.toUInt(), 0.toULong()) offset += line.toUtf8Bytes().size.toUInt() //returning an Envelope without data if end of input is reached } while (!line.startsWith(dataStart)) diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt index 4e60707f..fb693057 100644 --- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt +++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt @@ -132,7 +132,7 @@ fun IOPlugin.readEnvelopeFile( return formatPeeker(path)?.let { format -> FileEnvelope(path, format) } ?: if (readNonEnvelopes) { // if no format accepts file, read it as binary - SimpleEnvelope(Meta.empty, path.asBinary()) + SimpleEnvelope(Meta.EMPTY, path.asBinary()) } else null } diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt index 79c17e47..3d6e7744 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt @@ -1,8 +1,9 @@ package hep.dataforge.descriptors import hep.dataforge.meta.* +import hep.dataforge.names.Name import hep.dataforge.names.NameToken -import hep.dataforge.names.toName +import hep.dataforge.names.asName import hep.dataforge.values.False import hep.dataforge.values.True import hep.dataforge.values.Value @@ -36,7 +37,7 @@ sealed class ItemDescriptor(override val config: Config) : Specific { * * @return */ - var attributes by node() + var attributes by child() /** * True if the item is required @@ -66,13 +67,54 @@ class NodeDescriptor(config: Config) : ItemDescriptor(config) { * * @return */ - var default: Config? by node() + var default: Config? by child() + + /** + * The map of children node descriptors + */ + val nodes: Map<String, NodeDescriptor> + get() = config.getIndexed(NODE_KEY.asName()).entries.associate { (name, node) -> + name to wrap(node.node ?: error("Node descriptor must be a node")) + } + + + fun node(name: String, descriptor: NodeDescriptor) { + if (items.keys.contains(name)) error("The key $name already exists in descriptor") + val token = NameToken(NODE_KEY, name) + config[token] = descriptor.config + } + + + fun node(name: String, block: NodeDescriptor.() -> Unit) { + val token = NameToken(NODE_KEY, name) + if (config[token] == null) { + config[token] = NodeDescriptor(block) + } else { + NodeDescriptor.update(config[token].node ?: error("Node expected"), block) + } + } + + private fun buildNode(name: Name): NodeDescriptor { + return when (name.length) { + 0 -> this + 1 -> { + val token = NameToken(NODE_KEY, name.toString()) + val config: Config = config[token].node ?: Config().also { config[token] = it } + wrap(config) + } + else -> buildNode(name.first()?.asName()!!).buildNode(name.cutFirst()) + } + } + + fun node(name: Name, block: NodeDescriptor.() -> Unit) { + buildNode(name).apply(block) + } /** * The list of value descriptors */ val values: Map<String, ValueDescriptor> - get() = config.getIndexed(VALUE_KEY.toName()).entries.associate { (name, node) -> + get() = config.getIndexed(VALUE_KEY.asName()).entries.associate { (name, node) -> name to ValueDescriptor.wrap(node.node ?: error("Value descriptor must be a node")) } @@ -89,23 +131,9 @@ class NodeDescriptor(config: Config) : ItemDescriptor(config) { value(name, ValueDescriptor { this.name = name }.apply(block)) } - /** - * The map of children node descriptors - */ - val nodes: Map<String, NodeDescriptor> - get() = config.getIndexed(NODE_KEY.toName()).entries.associate { (name, node) -> - name to wrap(node.node ?: error("Node descriptor must be a node")) - } - - - fun node(name: String, descriptor: NodeDescriptor) { - if (items.keys.contains(name)) error("The key $name already exists in descriptor") - val token = NameToken(NODE_KEY, name) - config[token] = descriptor.config - } - - fun node(name: String, block: NodeDescriptor.() -> Unit) { - node(name, invoke { this.name = name }.apply(block)) + fun value(name: Name, block: ValueDescriptor.() -> Unit) { + require(name.length >= 1) { "Name length for value descriptor must be non-empty" } + buildNode(name.cutLast()).value(name.last().toString()) } val items: Map<String, ItemDescriptor> get() = nodes + values 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 57364593..7b980137 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt @@ -60,7 +60,7 @@ interface Meta : MetaRepr { */ const val VALUE_KEY = "@value" - val empty: EmptyMeta = EmptyMeta + val EMPTY: EmptyMeta = EmptyMeta } } diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configDelegates.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configDelegates.kt index 34fa0e71..e3dbc9aa 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configDelegates.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configDelegates.kt @@ -105,7 +105,7 @@ inline fun <reified E : Enum<E>> Configurable.enum(default: E, key: Name? = null /* Node delegates */ -fun Configurable.node(key: Name? = null): MutableNodeDelegate<Config> = MutableNodeDelegate(config, key) +fun Configurable.child(key: Name? = null): MutableNodeDelegate<Config> = MutableNodeDelegate(config, key) fun <T : Specific> Configurable.spec(spec: Specification<T>, key: Name? = null) = MutableMorphDelegate(config, key) { spec.wrap(it) } @@ -133,5 +133,5 @@ fun Configurable.doubleArray(key: Name? = null): ReadWriteDelegateWrapper<Value? ?: doubleArrayOf() } -fun <T : Configurable> Configurable.node(key: Name? = null, converter: (Meta) -> T) = +fun <T : Configurable> Configurable.child(key: Name? = null, converter: (Meta) -> T) = MutableMorphDelegate(config, key, converter) diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt index 65e49da2..0921eae8 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt @@ -144,7 +144,7 @@ fun Meta.boolean(default: Boolean? = null, key: String? = null) = BooleanDelegat fun Meta.number(default: Number? = null, key: String? = null) = NumberDelegate(this, key, default) -fun Meta.node(key: String? = null) = ChildDelegate(this, key) { it } +fun Meta.child(key: String? = null) = ChildDelegate(this, key) { it } @JvmName("safeString") fun Meta.string(default: String, key: String? = null) = @@ -400,7 +400,7 @@ fun <M : MutableMeta<M>> M.boolean(default: Boolean? = null, key: Name? = null) fun <M : MutableMeta<M>> M.number(default: Number? = null, key: Name? = null) = MutableNumberDelegate(this, key, default) -fun <M : MutableMeta<M>> M.node(key: Name? = null) = +fun <M : MutableMeta<M>> M.child(key: Name? = null) = MutableNodeDelegate(this, key) @JvmName("safeString") 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 084f27df..3c8d93d5 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt @@ -53,6 +53,8 @@ class Name(val tokens: List<NameToken>) { companion object { const val NAME_SEPARATOR = "." + + val EMPTY = Name(emptyList()) } } @@ -87,7 +89,7 @@ data class NameToken(val body: String, val index: String = "") { * This operation is rather heavy so it should be used with care in high performance code. */ fun String.toName(): Name { - if (isBlank()) return EmptyName + if (isBlank()) return Name.EMPTY val tokens = sequence { var bodyBuilder = StringBuilder() var queryBuilder = StringBuilder() @@ -139,7 +141,7 @@ fun String.toName(): Name { * Convert the [String] to a [Name] by simply wrapping it in a single name token without parsing. * The input string could contain dots and braces, but they are just escaped, not parsed. */ -fun String.asName(): Name = if (isBlank()) EmptyName else NameToken(this).asName() +fun String.asName(): Name = if (isBlank()) Name.EMPTY else NameToken(this).asName() operator fun NameToken.plus(other: Name): Name = Name(listOf(this) + other.tokens) @@ -153,8 +155,6 @@ fun Name.appendLeft(other: String): Name = NameToken(other) + this fun NameToken.asName() = Name(listOf(this)) -val EmptyName = Name(emptyList()) - fun Name.isEmpty(): Boolean = this.length == 0 /** diff --git a/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/OutputManager.kt b/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/OutputManager.kt index c05d5bb4..e88b29a5 100644 --- a/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/OutputManager.kt +++ b/dataforge-output/src/commonMain/kotlin/hep/dataforge/output/OutputManager.kt @@ -4,7 +4,6 @@ import hep.dataforge.context.* import hep.dataforge.context.PluginTag.Companion.DATAFORGE_GROUP import hep.dataforge.meta.EmptyMeta import hep.dataforge.meta.Meta -import hep.dataforge.names.EmptyName import hep.dataforge.names.Name import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers @@ -24,7 +23,7 @@ interface OutputManager { operator fun <T : Any> get( type: KClass<out T>, name: Name, - stage: Name = EmptyName, + stage: Name = Name.EMPTY, meta: Meta = EmptyMeta ): Renderer<T> } @@ -39,7 +38,7 @@ val Context.output: OutputManager get() = plugins.get() ?: ConsoleOutputManager( */ inline operator fun <reified T : Any> OutputManager.get( name: Name, - stage: Name = EmptyName, + stage: Name = Name.EMPTY, meta: Meta = EmptyMeta ): Renderer<T> { return get(T::class, name, stage, meta) @@ -48,7 +47,7 @@ inline operator fun <reified T : Any> OutputManager.get( /** * Directly render an object using the most suitable renderer */ -fun OutputManager.render(obj: Any, name: Name, stage: Name = EmptyName, meta: Meta = EmptyMeta) = +fun OutputManager.render(obj: Any, name: Name, stage: Name = Name.EMPTY, meta: Meta = EmptyMeta) = get(obj::class, name, stage).render(obj, meta) /** diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Dependency.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Dependency.kt index ddb53d5d..72402a17 100644 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Dependency.kt +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Dependency.kt @@ -6,7 +6,10 @@ import hep.dataforge.data.filter import hep.dataforge.meta.Meta import hep.dataforge.meta.MetaRepr import hep.dataforge.meta.buildMeta -import hep.dataforge.names.* +import hep.dataforge.names.Name +import hep.dataforge.names.asName +import hep.dataforge.names.isEmpty +import hep.dataforge.names.plus /** * A dependency of the task which allows to lazily create a data tree for single dependency @@ -15,7 +18,7 @@ sealed class Dependency : MetaRepr { abstract fun apply(workspace: Workspace): DataNode<Any> } -class DataDependency(val filter: DataFilter, val placement: Name = EmptyName) : Dependency() { +class DataDependency(val filter: DataFilter, val placement: Name = Name.EMPTY) : Dependency() { override fun apply(workspace: Workspace): DataNode<Any> { val result = workspace.data.filter(filter) return if (placement.isEmpty()) { @@ -31,7 +34,7 @@ class DataDependency(val filter: DataFilter, val placement: Name = EmptyName) : } } -class AllDataDependency(val placement: Name = EmptyName) : Dependency() { +class AllDataDependency(val placement: Name = Name.EMPTY) : Dependency() { override fun apply(workspace: Workspace): DataNode<Any> = if (placement.isEmpty()) { workspace.data } else { @@ -46,7 +49,7 @@ class AllDataDependency(val placement: Name = EmptyName) : Dependency() { abstract class TaskDependency<out T : Any>( val meta: Meta, - val placement: Name = EmptyName + val placement: Name = Name.EMPTY ) : Dependency() { abstract fun resolveTask(workspace: Workspace): Task<T> diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt index 6ae15fa3..87736c01 100644 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt @@ -7,7 +7,6 @@ import hep.dataforge.meta.DFBuilder import hep.dataforge.meta.Meta import hep.dataforge.meta.get import hep.dataforge.meta.string -import hep.dataforge.names.EmptyName import hep.dataforge.names.Name import hep.dataforge.names.isEmpty import hep.dataforge.names.toName @@ -57,7 +56,7 @@ class TaskBuilder<R : Any>(val name: Name, val type: KClass<out R>) { block: TaskEnv.(DataNode<*>) -> DataNode<R> ) { dataTransforms += DataTransformation(from, to) { context, model, data -> - val env = TaskEnv(EmptyName, model.meta, context, data) + val env = TaskEnv(Name.EMPTY, model.meta, context, data) env.block(data) } } @@ -70,7 +69,7 @@ class TaskBuilder<R : Any>(val name: Name, val type: KClass<out R>) { ) { dataTransforms += DataTransformation(from, to) { context, model, data -> data.ensureType(inputType) - val env = TaskEnv(EmptyName, model.meta, context, data) + val env = TaskEnv(Name.EMPTY, model.meta, context, data) env.block(data.cast(inputType)) } } 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 45b07fca..ede9efaa 100644 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt @@ -9,7 +9,6 @@ import hep.dataforge.data.DataFilter import hep.dataforge.data.DataTree import hep.dataforge.data.DataTreeBuilder import hep.dataforge.meta.* -import hep.dataforge.names.EmptyName import hep.dataforge.names.Name import hep.dataforge.names.asName import hep.dataforge.names.toName @@ -68,21 +67,21 @@ interface TaskDependencyContainer { */ fun TaskDependencyContainer.dependsOn( name: Name, - placement: Name = EmptyName, + placement: Name = Name.EMPTY, meta: Meta = defaultMeta ): WorkspaceTaskDependency = WorkspaceTaskDependency(name, meta, placement).also { add(it) } fun TaskDependencyContainer.dependsOn( name: String, - placement: Name = EmptyName, + placement: Name = Name.EMPTY, meta: Meta = defaultMeta ): WorkspaceTaskDependency = dependsOn(name.toName(), placement, meta) fun <T : Any> TaskDependencyContainer.dependsOn( task: Task<T>, - placement: Name = EmptyName, + placement: Name = Name.EMPTY, meta: Meta = defaultMeta ): DirectTaskDependency<T> = DirectTaskDependency(task, meta, placement).also { add(it) } @@ -96,7 +95,7 @@ fun <T : Any> TaskDependencyContainer.dependsOn( fun <T : Any> TaskDependencyContainer.dependsOn( task: Task<T>, - placement: Name = EmptyName, + placement: Name = Name.EMPTY, metaBuilder: MetaBuilder.() -> Unit ): DirectTaskDependency<T> = dependsOn(task, placement, buildMeta(metaBuilder)) @@ -120,7 +119,7 @@ fun TaskDependencyContainer.data(pattern: String? = null, from: String? = null, /** * Add all data as root node */ -fun TaskDependencyContainer.allData(to: Name = EmptyName) = AllDataDependency(to).also { add(it) } +fun TaskDependencyContainer.allData(to: Name = Name.EMPTY) = AllDataDependency(to).also { add(it) } /** * A builder for [TaskModel] diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt index b8f3ffa0..7b831a50 100644 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt @@ -5,7 +5,6 @@ import hep.dataforge.context.ContextBuilder import hep.dataforge.data.DataNode import hep.dataforge.data.DataTreeBuilder import hep.dataforge.meta.* -import hep.dataforge.names.EmptyName import hep.dataforge.names.Name import hep.dataforge.names.isEmpty import hep.dataforge.names.toName @@ -32,7 +31,7 @@ fun WorkspaceBuilder.context(name: String = "WORKSPACE", block: ContextBuilder.( } inline fun <reified T : Any> WorkspaceBuilder.data( - name: Name = EmptyName, + name: Name = Name.EMPTY, noinline block: DataTreeBuilder<T>.() -> Unit ): DataNode<T> { val node = DataTreeBuilder(T::class).apply(block) @@ -47,7 +46,7 @@ inline fun <reified T : Any> WorkspaceBuilder.data( @JvmName("rawData") fun WorkspaceBuilder.data( - name: Name = EmptyName, + name: Name = Name.EMPTY, block: DataTreeBuilder<Any>.() -> Unit ): DataNode<Any> = data<Any>(name, block) From e532e8358ed9c2dc892839e6ed24a37c44de5af0 Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Tue, 24 Dec 2019 22:10:48 +0300 Subject: [PATCH 21/41] Remove name from descriptors. It is never used --- .../kotlin/hep/dataforge/io/JsonMetaFormat.kt | 14 ++++++-------- .../hep/dataforge/descriptors/ItemDescriptor.kt | 12 ++---------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt index 9b78cbb3..d27b0a97 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt @@ -72,7 +72,7 @@ fun Value.toJson(descriptor: ValueDescriptor? = null): JsonElement { //Use these methods to customize JSON key mapping private fun NameToken.toJsonKey(descriptor: ItemDescriptor?) = toString() -private fun NodeDescriptor?.getDescriptor(key: String) = this?.items?.get(key) +//private fun NodeDescriptor?.getDescriptor(key: String) = this?.items?.get(key) fun Meta.toJson(descriptor: NodeDescriptor? = null): JsonObject { @@ -141,15 +141,13 @@ class JsonMeta(val json: JsonObject, val descriptor: NodeDescriptor? = null) : M @Suppress("UNCHECKED_CAST") private operator fun MutableMap<String, MetaItem<JsonMeta>>.set(key: String, value: JsonElement): Unit { - val itemDescriptor = descriptor.getDescriptor(key) - //use name from descriptor in case descriptor name differs from json key - val name = itemDescriptor?.name ?: key + val itemDescriptor = descriptor?.items?.get(key) return when (value) { is JsonPrimitive -> { - this[name] = MetaItem.ValueItem(value.toValue(itemDescriptor as? ValueDescriptor)) as MetaItem<JsonMeta> + this[key] = MetaItem.ValueItem(value.toValue(itemDescriptor as? ValueDescriptor)) as MetaItem<JsonMeta> } is JsonObject -> { - this[name] = MetaItem.NodeItem(JsonMeta(value, itemDescriptor as? NodeDescriptor)) + this[key] = MetaItem.NodeItem(JsonMeta(value, itemDescriptor as? NodeDescriptor)) } is JsonArray -> { when { @@ -160,10 +158,10 @@ class JsonMeta(val json: JsonObject, val descriptor: NodeDescriptor? = null) : M (it as JsonPrimitive).toValue(itemDescriptor as? ValueDescriptor) } ) - this[name] = MetaItem.ValueItem(listValue) as MetaItem<JsonMeta> + this[key] = MetaItem.ValueItem(listValue) as MetaItem<JsonMeta> } else -> value.forEachIndexed { index, jsonElement -> - this["$name[$index]"] = jsonElement.toMetaItem(itemDescriptor) + this["$key[$index]"] = jsonElement.toMetaItem(itemDescriptor) } } } diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt index 3d6e7744..93765dd4 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt @@ -11,13 +11,6 @@ import hep.dataforge.values.ValueType sealed class ItemDescriptor(override val config: Config) : Specific { - /** - * The name of this item - * - * @return - */ - var name: String by string { error("Anonymous descriptors are not allowed") } - /** * True if same name siblings with this name are allowed * @@ -128,12 +121,12 @@ class NodeDescriptor(config: Config) : ItemDescriptor(config) { * Add a value descriptor using block for */ fun value(name: String, block: ValueDescriptor.() -> Unit) { - value(name, ValueDescriptor { this.name = name }.apply(block)) + value(name, ValueDescriptor(block)) } fun value(name: Name, block: ValueDescriptor.() -> Unit) { require(name.length >= 1) { "Name length for value descriptor must be non-empty" } - buildNode(name.cutLast()).value(name.last().toString()) + buildNode(name.cutLast()).value(name.last().toString(), block) } val items: Map<String, ItemDescriptor> get() = nodes + values @@ -234,7 +227,6 @@ class ValueDescriptor(config: Config) : ItemDescriptor(config) { override fun wrap(config: Config): ValueDescriptor = ValueDescriptor(config) inline fun <reified E : Enum<E>> enum(name: String) = ValueDescriptor { - this.name = name type(ValueType.STRING) this.allowedValues = enumValues<E>().map { Value.of(it.name) } } From 1b879eccc7f7afd2b5edc6551e895b33c4a32771 Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Sat, 28 Dec 2019 16:06:32 +0300 Subject: [PATCH 22/41] Specialized non-null delegates for spec --- build.gradle.kts | 2 +- .../hep/dataforge/descriptors/Described.kt | 6 ++--- .../kotlin/hep/dataforge/meta/Specific.kt | 25 ++++++++++++++++--- .../hep/dataforge/meta/metaDelegates.kt | 4 +-- 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 0dbaef48..cfa22253 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("scientifik.publish") version toolsVersion apply false } -val dataforgeVersion by extra("0.1.5-dev-5") +val dataforgeVersion by extra("0.1.5-dev-6") val bintrayRepo by extra("dataforge") val githubProject by extra("dataforge-core") diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/Described.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/Described.kt index f355c828..f2ab0c00 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/Described.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/Described.kt @@ -1,7 +1,7 @@ package hep.dataforge.descriptors import hep.dataforge.descriptors.Described.Companion.DESCRIPTOR_NODE -import hep.dataforge.meta.Meta +import hep.dataforge.meta.MetaRepr import hep.dataforge.meta.get import hep.dataforge.meta.node @@ -19,11 +19,11 @@ interface Described { /** * If meta node supplies explicit descriptor, return it, otherwise try to use descriptor node from meta itself */ -val Meta.descriptor: NodeDescriptor? +val MetaRepr.descriptor: NodeDescriptor? get() { return if (this is Described) { descriptor } else { - get(DESCRIPTOR_NODE).node?.let { NodeDescriptor.wrap(it) } + toMeta()[DESCRIPTOR_NODE].node?.let { NodeDescriptor.wrap(it) } } } \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt index a4ebffe6..850365a5 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt @@ -1,7 +1,10 @@ package hep.dataforge.meta import hep.dataforge.names.Name +import hep.dataforge.names.asName import kotlin.jvm.JvmName +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty /** * Marker interface for classes with specifications @@ -61,13 +64,27 @@ fun <C : Specific, S : Specification<C>> Specific.update(spec: S, action: C.() - fun <C : Specific, S : Specification<C>> S.createStyle(action: C.() -> Unit): Meta = Config().also { update(it, action) } +class SpecDelegate<T : Specific, S : Specification<T>>( + val target: Specific, + val spec: S, + val key: Name? = null +) : ReadWriteProperty<Any?, T> { + override fun getValue(thisRef: Any?, property: KProperty<*>): T { + return target.config[key ?: property.name.asName()]?.node?.let { spec.wrap(it) } ?: spec.empty() + } -fun <C : Specific> Specific.spec( - spec: Specification<C>, + override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { + target.config[key ?: property.name.asName()] = value.config + } +} + +fun <T : Specific, S : Specification<T>> Specific.spec( + spec: S, key: Name? = null -): MutableMorphDelegate<Config, C> = MutableMorphDelegate(config, key) { spec.wrap(it) } +): SpecDelegate<T, S> = SpecDelegate(this, spec, key) fun <T : Specific> MetaItem<*>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(it) } @JvmName("configSpec") -fun <T : Specific> MetaItem<Config>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(it) } \ No newline at end of file +fun <T : Specific> MetaItem<Config>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(it) } + diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt index 0921eae8..02caa9fa 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt @@ -350,8 +350,8 @@ class MutableNodeDelegate<M : MutableMeta<M>>( } } -class MutableMorphDelegate<M : MutableMeta<M>, T : Configurable>( - val meta: M, +class MutableMorphDelegate<T : Configurable>( + val meta: MutableMeta<*>, private val key: Name? = null, private val converter: (Meta) -> T ) : ReadWriteProperty<Any?, T?> { From 736ec621b0982ba30ee5a215a74002c3ae91c0d6 Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Sat, 28 Dec 2019 21:24:16 +0300 Subject: [PATCH 23/41] Invoke mechanism for defining specific and fix to attach config child --- .../kotlin/hep/dataforge/meta/Specific.kt | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt index 850365a5..865114e5 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt @@ -15,6 +15,13 @@ interface Specific : Configurable operator fun Specific.get(name: String): MetaItem<*>? = config[name] +/** + * Editor for specific objects + */ +inline operator fun <S : Specific> S.invoke(block: S.() -> Unit): Unit { + run(block) +} + /** * Allows to apply custom configuration in a type safe way to simple untyped configuration. * By convention [Specific] companion should inherit this class @@ -69,8 +76,12 @@ class SpecDelegate<T : Specific, S : Specification<T>>( val spec: S, val key: Name? = null ) : ReadWriteProperty<Any?, T> { + override fun getValue(thisRef: Any?, property: KProperty<*>): T { - return target.config[key ?: property.name.asName()]?.node?.let { spec.wrap(it) } ?: spec.empty() + val name = key ?: property.name.asName() + return target.config[name]?.node?.let { spec.wrap(it) } ?: (spec.empty().also { + target.config[name] = it.config + }) } override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { From b83821af51e5b66557787e365cd6853abd6d30f6 Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Wed, 8 Jan 2020 21:41:27 +0300 Subject: [PATCH 24/41] Configurable and Scheme revision --- README.md | 5 +- build.gradle.kts | 2 +- .../kotlin/hep/dataforge/data/DataFilter.kt | 6 +- .../io/serialization/MetaSerializer.kt | 2 +- .../hep/dataforge/descriptors/Described.kt | 29 +-- .../dataforge/descriptors/DescriptorMeta.kt | 28 +++ .../dataforge/descriptors/ItemDescriptor.kt | 36 +-- .../kotlin/hep/dataforge/meta/Config.kt | 20 +- .../kotlin/hep/dataforge/meta/Configurable.kt | 44 ++++ .../kotlin/hep/dataforge/meta/Laminate.kt | 18 +- .../kotlin/hep/dataforge/meta/Meta.kt | 29 ++- .../kotlin/hep/dataforge/meta/MutableMeta.kt | 2 +- .../kotlin/hep/dataforge/meta/Scheme.kt | 78 ++++++ .../kotlin/hep/dataforge/meta/Specific.kt | 101 -------- .../hep/dataforge/meta/Specification.kt | 60 +++++ .../kotlin/hep/dataforge/meta/Styled.kt | 72 ------ .../hep/dataforge/meta/configDelegates.kt | 137 ---------- .../dataforge/meta/configurableDelegates.kt | 236 ++++++++++++++++++ .../hep/dataforge/meta/metaDelegates.kt | 18 -- .../hep/dataforge/values/valueExtensions.kt | 1 + .../hep/dataforge/meta/MetaDelegateTest.kt | 7 +- .../hep/dataforge/meta/MutableMetaTest.kt | 2 +- .../meta/{StyledTest.kt => SchemeTest.kt} | 13 +- .../hep/dataforge/meta/SpecificationTest.kt | 15 +- 24 files changed, 548 insertions(+), 413 deletions(-) create mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/DescriptorMeta.kt create mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Configurable.kt create mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Scheme.kt delete mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt create mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specification.kt delete mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Styled.kt delete mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configDelegates.kt create mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configurableDelegates.kt rename dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/{StyledTest.kt => SchemeTest.kt} (71%) diff --git a/README.md b/README.md index 626ee576..a6e6ed99 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,11 @@ [](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub) +[](https://zenodo.org/badge/latestdoi/148831678) + + [  ](https://bintray.com/mipt-npm/dataforge/dataforge-meta/_latestVersion) -[](https://zenodo.org/badge/latestdoi/148831678) + # Questions and Answers # diff --git a/build.gradle.kts b/build.gradle.kts index cfa22253..53a45364 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("scientifik.publish") version toolsVersion apply false } -val dataforgeVersion by extra("0.1.5-dev-6") +val dataforgeVersion by extra("0.1.5-dev-7") val bintrayRepo by extra("dataforge") val githubProject by extra("dataforge-core") diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataFilter.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataFilter.kt index 3770c9a8..08a0ea87 100644 --- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataFilter.kt +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataFilter.kt @@ -4,7 +4,7 @@ import hep.dataforge.meta.* import hep.dataforge.names.toName -class DataFilter(override val config: Config) : Specific { +class DataFilter : Scheme() { /** * A source node for the filter */ @@ -22,9 +22,7 @@ class DataFilter(override val config: Config) : Specific { fun isEmpty(): Boolean = config.isEmpty() - companion object : Specification<DataFilter> { - override fun wrap(config: Config): DataFilter = DataFilter(config) - } + companion object : SchemeSpec<DataFilter>(::DataFilter) } /** diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt index 31925c9f..a1ac33a5 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt @@ -133,7 +133,7 @@ object ConfigSerializer : KSerializer<Config> { override val descriptor: SerialDescriptor = MetaSerializer.descriptor override fun deserialize(decoder: Decoder): Config { - return MetaSerializer.deserialize(decoder).toConfig() + return MetaSerializer.deserialize(decoder).asConfig() } override fun serialize(encoder: Encoder, obj: Config) { diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/Described.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/Described.kt index f2ab0c00..4bf6c9dd 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/Described.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/Described.kt @@ -1,29 +1,24 @@ package hep.dataforge.descriptors -import hep.dataforge.descriptors.Described.Companion.DESCRIPTOR_NODE -import hep.dataforge.meta.MetaRepr -import hep.dataforge.meta.get -import hep.dataforge.meta.node - /** * An object which provides its descriptor */ interface Described { - val descriptor: NodeDescriptor + val descriptor: NodeDescriptor? companion object { const val DESCRIPTOR_NODE = "@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) } - } - } \ No newline at end of file +///** +// * 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) } +// } +// } \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/DescriptorMeta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/DescriptorMeta.kt new file mode 100644 index 00000000..49aef28e --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/DescriptorMeta.kt @@ -0,0 +1,28 @@ +package hep.dataforge.descriptors + +import hep.dataforge.meta.MetaBase +import hep.dataforge.meta.MetaItem +import hep.dataforge.names.NameToken +import hep.dataforge.values.Null + +class DescriptorMeta(val descriptor: NodeDescriptor) : MetaBase() { + override val items: Map<NameToken, MetaItem<*>> + get() = descriptor.items.entries.associate { entry -> + NameToken(entry.key) to entry.value.defaultItem() + } +} + +fun NodeDescriptor.defaultItem(): MetaItem.NodeItem<*> = + MetaItem.NodeItem(default ?: DescriptorMeta(this)) + +fun ValueDescriptor.defaultItem(): MetaItem.ValueItem = MetaItem.ValueItem(default ?: Null) + +/** + * Build a default [MetaItem] from descriptor. + */ +fun ItemDescriptor.defaultItem(): MetaItem<*> { + return when (this) { + is ValueDescriptor -> defaultItem() + is NodeDescriptor -> defaultItem() + } +} \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt index 93765dd4..99b3e850 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt @@ -4,12 +4,13 @@ import hep.dataforge.meta.* import hep.dataforge.names.Name import hep.dataforge.names.NameToken import hep.dataforge.names.asName +import hep.dataforge.names.isEmpty import hep.dataforge.values.False import hep.dataforge.values.True import hep.dataforge.values.Value import hep.dataforge.values.ValueType -sealed class ItemDescriptor(override val config: Config) : Specific { +sealed class ItemDescriptor : Scheme() { /** * True if same name siblings with this name are allowed @@ -30,7 +31,7 @@ sealed class ItemDescriptor(override val config: Config) : Specific { * * @return */ - var attributes by child() + var attributes by config() /** * True if the item is required @@ -46,7 +47,7 @@ sealed class ItemDescriptor(override val config: Config) : Specific { * * @author Alexander Nozik */ -class NodeDescriptor(config: Config) : ItemDescriptor(config) { +class NodeDescriptor : ItemDescriptor() { /** * True if the node is required @@ -60,7 +61,7 @@ class NodeDescriptor(config: Config) : ItemDescriptor(config) { * * @return */ - var default: Config? by child() + var default: Config? by nullableConfig() /** * The map of children node descriptors @@ -134,18 +135,28 @@ class NodeDescriptor(config: Config) : ItemDescriptor(config) { //override val descriptor: NodeDescriptor = empty("descriptor") - companion object : Specification<NodeDescriptor> { + companion object : SchemeSpec<NodeDescriptor>(::NodeDescriptor) { // const val ITEM_KEY = "item" const val NODE_KEY = "node" const val VALUE_KEY = "value" - override fun wrap(config: Config): NodeDescriptor = NodeDescriptor(config) + //override fun wrap(config: Config): NodeDescriptor = NodeDescriptor(config) //TODO infer descriptor from spec } } +/** + * Get a descriptor item associated with given name or null if item for given name not provided + */ +operator fun ItemDescriptor.get(name: Name): ItemDescriptor? { + if (name.isEmpty()) return this + return when (this) { + is ValueDescriptor -> null // empty name already checked + is NodeDescriptor -> items[name.first()!!.toString()]?.get(name.cutFirst()) + } +} /** * A descriptor for meta value @@ -154,7 +165,7 @@ class NodeDescriptor(config: Config) : ItemDescriptor(config) { * * @author Alexander Nozik */ -class ValueDescriptor(config: Config) : ItemDescriptor(config) { +class ValueDescriptor : ItemDescriptor() { /** @@ -180,8 +191,8 @@ class ValueDescriptor(config: Config) : ItemDescriptor(config) { * * @return */ - var type: List<ValueType> by value { - it?.list?.map { v -> ValueType.valueOf(v.string) } ?: emptyList() + var type: List<ValueType> by item { + it?.value?.list?.map { v -> ValueType.valueOf(v.string) } ?: emptyList() } fun type(vararg t: ValueType) { @@ -222,10 +233,7 @@ class ValueDescriptor(config: Config) : ItemDescriptor(config) { this.allowedValues = v.map { Value.of(it) } } - companion object : Specification<ValueDescriptor> { - - override fun wrap(config: Config): ValueDescriptor = ValueDescriptor(config) - + companion object : SchemeSpec<ValueDescriptor>(::ValueDescriptor) { inline fun <reified E : Enum<E>> enum(name: String) = ValueDescriptor { type(ValueType.STRING) this.allowedValues = enumValues<E>().map { Value.of(it.name) } @@ -289,4 +297,4 @@ class ValueDescriptor(config: Config) : ItemDescriptor(config) { // return ValueDescriptor(Laminate(primary.meta, secondary.meta)) // } } -} +} \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt index f47d3bcd..087cb077 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt @@ -40,7 +40,7 @@ class Config : AbstractMutableMeta<Config>() { override fun replaceItem(key: NameToken, oldItem: MetaItem<Config>?, newItem: MetaItem<Config>?) { if (newItem == null) { _items.remove(key) - if(oldItem!= null && oldItem is MetaItem.NodeItem<Config>) { + if (oldItem != null && oldItem is MetaItem.NodeItem<Config>) { oldItem.node.removeListener(this) } } else { @@ -57,7 +57,7 @@ class Config : AbstractMutableMeta<Config>() { /** * Attach configuration node instead of creating one */ - override fun wrapNode(meta: Meta): Config = meta.toConfig() + override fun wrapNode(meta: Meta): Config = meta.asConfig() override fun empty(): Config = Config() @@ -68,22 +68,12 @@ class Config : AbstractMutableMeta<Config>() { operator fun Config.get(token: NameToken): MetaItem<Config>? = items[token] -fun Meta.toConfig(): Config = this as? Config ?: Config().also { builder -> +fun Meta.asConfig(): Config = this as? Config ?: Config().also { builder -> this.items.mapValues { entry -> val item = entry.value builder[entry.key.asName()] = when (item) { is MetaItem.ValueItem -> item.value - is MetaItem.NodeItem -> MetaItem.NodeItem(item.node.toConfig()) + is MetaItem.NodeItem -> MetaItem.NodeItem(item.node.asConfig()) } } -} - -interface Configurable { - val config: Config -} - -fun <T : Configurable> T.configure(meta: Meta): T = this.apply { config.update(meta) } - -fun <T : Configurable> T.configure(action: MetaBuilder.() -> Unit): T = configure(buildMeta(action)) - -open class SimpleConfigurable(override val config: Config) : Configurable \ No newline at end of file +} \ 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 new file mode 100644 index 00000000..c13a6214 --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Configurable.kt @@ -0,0 +1,44 @@ +package hep.dataforge.meta + +import hep.dataforge.names.Name +import hep.dataforge.names.toName + +/** + * A container that holds a [Config] and a default item provider. + * Default item provider could be use for example to reference parent configuration. + * 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 { + /** + * Backing config + */ + val config: Config + + /** + * Default meta item provider + */ + fun getDefaultItem(name: Name): MetaItem<*>? = null +} + +/** + * Get a property with default + */ +fun Configurable.getProperty(name: Name): MetaItem<*>? = config[name] ?: getDefaultItem(name) + +fun Configurable.getProperty(key: String) = getProperty(key.toName()) + +/** + * Set a configurable property + */ +fun Configurable.setProperty(name: Name, item: MetaItem<*>?) { + config[name] = item +} + +fun Configurable.setProperty(key: String, item: MetaItem<*>?) { + setProperty(key.toName(), item) +} + +fun <T : Configurable> T.configure(meta: Meta): T = this.apply { config.update(meta) } + +fun <T : Configurable> T.configure(action: Config.() -> Unit): T = apply { config.apply(action) } diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt index b403544c..c6d69967 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt @@ -1,9 +1,10 @@ package hep.dataforge.meta +import hep.dataforge.names.Name import hep.dataforge.names.NameToken /** - * A meta laminate consisting of multiple immutable meta layers. For mutable front layer, use [Styled]. + * A meta laminate consisting of multiple immutable meta layers. For mutable front layer, use [Scheme]. */ class Laminate(layers: List<Meta>) : MetaBase() { @@ -17,10 +18,11 @@ class Laminate(layers: List<Meta>) : MetaBase() { constructor(vararg layers: Meta?) : this(layers.filterNotNull()) - override val items: Map<NameToken, MetaItem<Meta>> - get() = layers.map { it.items.keys }.flatten().associateWith { key -> + override val items: Map<NameToken, MetaItem<Meta>> by lazy { + layers.map { it.items.keys }.flatten().associateWith { key -> layers.asSequence().map { it.items[key] }.filterNotNull().let(replaceRule) } + } /** * Generate sealed meta using [mergeRule] @@ -77,6 +79,16 @@ class Laminate(layers: List<Meta>) : MetaBase() { } } +/** + * Performance optimized version of get method + */ +fun Laminate.getFirst(name: Name): MetaItem<*>? { + layers.forEach { layer -> + layer[name]?.let { return it } + } + return null +} + /** * Create a new [Laminate] adding given layer to the top */ 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 7b980137..d917559d 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt @@ -5,6 +5,7 @@ import hep.dataforge.meta.MetaItem.NodeItem import hep.dataforge.meta.MetaItem.ValueItem import hep.dataforge.names.* import hep.dataforge.values.EnumValue +import hep.dataforge.values.Null import hep.dataforge.values.Value import hep.dataforge.values.boolean @@ -22,6 +23,17 @@ sealed class MetaItem<out M : Meta> { data class NodeItem<M : Meta>(val node: M) : MetaItem<M>() { override fun toString(): String = node.toString() } + + companion object { + fun of(arg: Any?): MetaItem<*> { + return when (arg) { + null -> ValueItem(Null) + is MetaItem<*> -> arg + is Meta -> NodeItem(arg) + else -> ValueItem(Value.of(arg)) + } + } + } } /** @@ -45,7 +57,7 @@ interface Meta : MetaRepr { */ val items: Map<NameToken, MetaItem<*>> - override fun toMeta(): Meta = this + override fun toMeta(): Meta = seal() override fun equals(other: Any?): Boolean @@ -69,7 +81,7 @@ interface Meta : MetaRepr { /** * Perform recursive item search using given [name]. Each [NameToken] is treated as a name in [Meta.items] of a parent node. * - * If [name] is empty reture current [Meta] as a [NodeItem] + * If [name] is empty return current [Meta] as a [NodeItem] */ operator fun Meta?.get(name: Name): MetaItem<*>? { if (this == null) return null @@ -129,17 +141,8 @@ interface MetaNode<out M : MetaNode<M>> : Meta { /** * The same as [Meta.get], but with specific node type */ -operator fun <M : MetaNode<M>> M?.get(name: Name): MetaItem<M>? { - 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) - } - } -} +@Suppress("UNCHECKED_CAST") +operator fun <M : MetaNode<M>> M?.get(name: Name): MetaItem<M>? = (this as Meta)[name] as MetaItem<M>? operator fun <M : MetaNode<M>> M?.get(key: String): MetaItem<M>? = this[key.toName()] 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 7a379f24..950df7d6 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt @@ -105,7 +105,7 @@ operator fun MutableMeta<*>.set(name: Name, value: Any?) { null -> remove(name) is MetaItem<*> -> setItem(name, value) is Meta -> setNode(name, value) - is Specific -> setNode(name, value.config) + is Configurable -> setNode(name, value.config) else -> setValue(name, Value.of(value)) } } diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Scheme.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Scheme.kt new file mode 100644 index 00000000..2de46dc0 --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Scheme.kt @@ -0,0 +1,78 @@ +package hep.dataforge.meta + +import hep.dataforge.descriptors.* +import hep.dataforge.names.Name +import hep.dataforge.names.NameToken +import hep.dataforge.names.plus + +open class Scheme() : Configurable, Described { + constructor(config: Config, defaultProvider: (Name) -> MetaItem<*>?) : this() { + this.config = config + this.defaultProvider = defaultProvider + } + + //constructor(config: Config, default: Meta) : this(config, { default[it] }) + constructor(config: Config) : this(config, { null }) + + final override lateinit var config: Config + internal set + + lateinit var defaultProvider: (Name) -> MetaItem<*>? + internal set + + override val descriptor: NodeDescriptor? = null + + override fun getDefaultItem(name: Name): MetaItem<*>? { + return defaultProvider(name) ?: descriptor?.get(name)?.defaultItem() + } + + /** + * Provide a default layer which returns items from [defaultProvider] and falls back to descriptor + * values if default value is unavailable. + * Values from [defaultProvider] completely replace + */ + open val defaultLayer: Meta get() = DefaultLayer(Name.EMPTY) + + private inner class DefaultLayer(val path: Name) : MetaBase() { + override val items: Map<NameToken, MetaItem<*>> = + (descriptor?.get(path) as? NodeDescriptor)?.items?.entries?.associate { (key, descriptor) -> + val token = NameToken(key) + val fullName = path + token + val item: MetaItem<*> = when (descriptor) { + is ValueDescriptor -> getDefaultItem(fullName) ?: descriptor.defaultItem() + is NodeDescriptor -> MetaItem.NodeItem(DefaultLayer(fullName)) + } + token to item + } ?: emptyMap() + } + +} + +/** + * A specification for simplified generation of wrappers + */ +open class SchemeSpec<T : Scheme>(val builder: () -> T) : Specification<T> { + override fun wrap(config: Config, defaultProvider: (Name) -> MetaItem<*>?): T { + return builder().apply { + this.config = config + this.defaultProvider = defaultProvider + } + } +} + +open class MetaScheme( + val meta: Meta, + override val descriptor: NodeDescriptor? = null, + config: Config = Config() +) : Scheme(config, meta::get) { + override val defaultLayer: Meta get() = meta +} + +fun Meta.toScheme() = MetaScheme(this) + +fun <T : Configurable> Meta.toScheme(spec: Specification<T>, block: T.() -> Unit) = spec.wrap(this).apply(block) + +/** + * Create a snapshot laminate + */ +fun Scheme.toMeta(): Laminate = Laminate(config, defaultLayer) \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt deleted file mode 100644 index 865114e5..00000000 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specific.kt +++ /dev/null @@ -1,101 +0,0 @@ -package hep.dataforge.meta - -import hep.dataforge.names.Name -import hep.dataforge.names.asName -import kotlin.jvm.JvmName -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KProperty - -/** - * Marker interface for classes with specifications - */ -interface Specific : Configurable - -//TODO separate mutable config from immutable meta to allow free wrapping of meta - -operator fun Specific.get(name: String): MetaItem<*>? = config[name] - -/** - * Editor for specific objects - */ -inline operator fun <S : Specific> S.invoke(block: S.() -> Unit): Unit { - run(block) -} - -/** - * Allows to apply custom configuration in a type safe way to simple untyped configuration. - * By convention [Specific] companion should inherit this class - * - */ -interface Specification<T : Specific> { - /** - * Update given configuration using given type as a builder - */ - fun update(config: Config, action: T.() -> Unit): T { - return wrap(config).apply(action) - } - - operator fun invoke(action: T.() -> Unit) = update(Config(), action) - - fun empty() = wrap(Config()) - - /** - * Wrap generic configuration producing instance of desired type - */ - fun wrap(config: Config): T - - //TODO replace by free wrapper - fun wrap(meta: Meta): T = wrap(meta.toConfig()) -} - -fun <T : Specific> specification(wrapper: (Config) -> T): Specification<T> = - object : Specification<T> { - override fun wrap(config: Config): T = wrapper(config) - } - -/** - * Apply specified configuration to configurable - */ -fun <T : Configurable, C : Specific, S : Specification<C>> T.configure(spec: S, action: C.() -> Unit) = - apply { spec.update(config, action) } - -/** - * Update configuration using given specification - */ -fun <C : Specific, S : Specification<C>> Specific.update(spec: S, action: C.() -> Unit) = - apply { spec.update(config, action) } - -/** - * Create a style based on given specification - */ -fun <C : Specific, S : Specification<C>> S.createStyle(action: C.() -> Unit): Meta = - Config().also { update(it, action) } - -class SpecDelegate<T : Specific, S : Specification<T>>( - val target: Specific, - val spec: S, - val key: Name? = null -) : ReadWriteProperty<Any?, T> { - - override fun getValue(thisRef: Any?, property: KProperty<*>): T { - val name = key ?: property.name.asName() - return target.config[name]?.node?.let { spec.wrap(it) } ?: (spec.empty().also { - target.config[name] = it.config - }) - } - - override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { - target.config[key ?: property.name.asName()] = value.config - } -} - -fun <T : Specific, S : Specification<T>> Specific.spec( - spec: S, - key: Name? = null -): SpecDelegate<T, S> = SpecDelegate(this, spec, key) - -fun <T : Specific> MetaItem<*>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(it) } - -@JvmName("configSpec") -fun <T : Specific> MetaItem<Config>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(it) } - diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specification.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specification.kt new file mode 100644 index 00000000..9c8b740a --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specification.kt @@ -0,0 +1,60 @@ +package hep.dataforge.meta + +import hep.dataforge.names.Name +import kotlin.jvm.JvmName + +/** + * Allows to apply custom configuration in a type safe way to simple untyped configuration. + * By convention [Scheme] companion should inherit this class + * + */ +interface Specification<T : Configurable> { + /** + * Update given configuration using given type as a builder + */ + fun update(config: Config, action: T.() -> Unit): T { + return wrap(config).apply(action) + } + + operator fun invoke(action: T.() -> Unit) = update(Config(), action) + + fun empty() = wrap() + + /** + * Wrap generic configuration producing instance of desired type + */ + fun wrap(config: Config = Config(), defaultProvider: (Name) -> MetaItem<*>? = { null }): T + + /** + * Wrap a configuration using static meta as default + */ + fun wrap(config: Config = Config(), default: Meta): T = wrap(config){default[it]} + + /** + * Wrap a configuration using static meta as default + */ + fun wrap(default: Meta): T = wrap(Config()){default[it]} +} + +/** + * Apply specified configuration to configurable + */ +fun <T : Configurable, C : Configurable, S : Specification<C>> T.configure(spec: S, action: C.() -> Unit) = + apply { spec.update(config, action) } + +/** + * Update configuration using given specification + */ +fun <C : Configurable, S : Specification<C>> Configurable.update(spec: S, action: C.() -> Unit) = + apply { spec.update(config, action) } + +/** + * Create a style based on given specification + */ +fun <C : Configurable, S : Specification<C>> S.createStyle(action: C.() -> Unit): Meta = + Config().also { update(it, action) } + +fun <T : Configurable> MetaItem<*>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(Config(), it) } + +@JvmName("configSpec") +fun <T : Configurable> MetaItem<Config>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(it) } diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Styled.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Styled.kt deleted file mode 100644 index 55d652aa..00000000 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Styled.kt +++ /dev/null @@ -1,72 +0,0 @@ -package hep.dataforge.meta - -import hep.dataforge.names.Name -import hep.dataforge.names.NameToken -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KProperty - - -/** - * A meta object with read-only meta base and changeable configuration on top of it - * @param base - unchangeable base - * @param style - the style - */ -class Styled(val base: Meta, val style: Config = Config().empty()) : AbstractMutableMeta<Styled>() { - override fun wrapNode(meta: Meta): Styled = Styled(meta) - - override fun empty(): Styled = Styled(EmptyMeta) - - override val items: Map<NameToken, MetaItem<Styled>> - get() = (base.items.keys + style.items.keys).associate { key -> - val value = base.items[key] - val styleValue = style[key] - val item: MetaItem<Styled> = when (value) { - null -> when (styleValue) { - null -> error("Should be unreachable") - is MetaItem.NodeItem -> MetaItem.NodeItem(Styled(style.empty(), styleValue.node)) - is MetaItem.ValueItem -> styleValue - } - is MetaItem.ValueItem -> value - is MetaItem.NodeItem -> MetaItem.NodeItem( - Styled(value.node, styleValue?.node ?: Config.empty()) - ) - } - key to item - } - - override fun set(name: Name, item: MetaItem<*>?) { - if (item == null) { - style.remove(name) - } else { - style[name] = item - } - } - - fun onChange(owner: Any?, action: (Name, before: MetaItem<*>?, after: MetaItem<*>?) -> Unit) { - //TODO test correct behavior - style.onChange(owner) { name, before, after -> action(name, before ?: base[name], after ?: base[name]) } - } - - fun removeListener(owner: Any?) { - style.removeListener(owner) - } -} - -fun Styled.configure(meta: Meta) = apply { style.update(meta) } - -fun Meta.withStyle(style: Meta = EmptyMeta) = if (this is Styled) { - this.apply { this.configure(style) } -} else { - Styled(this, style.toConfig()) -} - -class StyledNodeDelegate(val owner: Styled, val key: String?) : ReadWriteProperty<Any?, Meta> { - override fun getValue(thisRef: Any?, property: KProperty<*>): Meta { - return owner[key ?: property.name]?.node ?: EmptyMeta - } - - override fun setValue(thisRef: Any?, property: KProperty<*>, value: Meta) { - owner.style[key ?: property.name] = value - } - -} \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configDelegates.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configDelegates.kt deleted file mode 100644 index e3dbc9aa..00000000 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configDelegates.kt +++ /dev/null @@ -1,137 +0,0 @@ -package hep.dataforge.meta - -import hep.dataforge.names.Name -import hep.dataforge.values.DoubleArrayValue -import hep.dataforge.values.Null -import hep.dataforge.values.Value -import kotlin.jvm.JvmName - - -//Configurable delegates - -/** - * A property delegate that uses custom key - */ -fun Configurable.value(default: Any = Null, key: Name? = null): MutableValueDelegate<Config> = - MutableValueDelegate(config, key, Value.of(default)) - -fun <T> Configurable.value( - default: T? = null, - key: Name? = null, - writer: (T) -> Value = { Value.of(it) }, - reader: (Value?) -> T -): ReadWriteDelegateWrapper<Value?, T> = - MutableValueDelegate(config, key, default?.let { Value.of(it) }).transform(reader = reader, writer = writer) - -fun Configurable.string(default: String? = null, key: Name? = null): MutableStringDelegate<Config> = - MutableStringDelegate(config, key, default) - -fun Configurable.boolean(default: Boolean? = null, key: Name? = null): MutableBooleanDelegate<Config> = - MutableBooleanDelegate(config, key, default) - -fun Configurable.number(default: Number? = null, key: Name? = null): MutableNumberDelegate<Config> = - MutableNumberDelegate(config, key, default) - -/* Number delegates*/ - -fun Configurable.int(default: Int? = null, key: Name? = null) = - number(default, key).int - -fun Configurable.double(default: Double? = null, key: Name? = null) = - number(default, key).double - -fun Configurable.long(default: Long? = null, key: Name? = null) = - number(default, key).long - -fun Configurable.short(default: Short? = null, key: Name? = null) = - number(default, key).short - -fun Configurable.float(default: Float? = null, key: Name? = null) = - number(default, key).float - - -@JvmName("safeString") -fun Configurable.string(default: String, key: Name? = null) = - MutableSafeStringDelegate(config, key) { default } - -@JvmName("safeBoolean") -fun Configurable.boolean(default: Boolean, key: Name? = null) = - MutableSafeBooleanDelegate(config, key) { default } - -@JvmName("safeNumber") -fun Configurable.number(default: Number, key: Name? = null) = - MutableSafeNumberDelegate(config, key) { default } - -@JvmName("safeString") -fun Configurable.string(key: Name? = null, default: () -> String) = - MutableSafeStringDelegate(config, key, default) - -@JvmName("safeBoolean") -fun Configurable.boolean(key: Name? = null, default: () -> Boolean) = - MutableSafeBooleanDelegate(config, key, default) - -@JvmName("safeNumber") -fun Configurable.number(key: Name? = null, default: () -> Number) = - MutableSafeNumberDelegate(config, key, default) - - -/* Safe number delegates*/ - -@JvmName("safeInt") -fun Configurable.int(default: Int, key: Name? = null) = - number(default, key).int - -@JvmName("safeDouble") -fun Configurable.double(default: Double, key: Name? = null) = - number(default, key).double - -@JvmName("safeLong") -fun Configurable.long(default: Long, key: Name? = null) = - number(default, key).long - -@JvmName("safeShort") -fun Configurable.short(default: Short, key: Name? = null) = - number(default, key).short - -@JvmName("safeFloat") -fun Configurable.float(default: Float, key: Name? = null) = - number(default, key).float - -/** - * Enum delegate - */ -inline fun <reified E : Enum<E>> Configurable.enum(default: E, key: Name? = null) = - MutableSafeEnumvDelegate(config, key, default) { enumValueOf(it) } - -/* Node delegates */ - -fun Configurable.child(key: Name? = null): MutableNodeDelegate<Config> = MutableNodeDelegate(config, key) - -fun <T : Specific> Configurable.spec(spec: Specification<T>, key: Name? = null) = - MutableMorphDelegate(config, key) { spec.wrap(it) } - -fun <T : Specific> Configurable.spec(builder: (Config) -> T, key: Name? = null) = - MutableMorphDelegate(config, key) { specification(builder).wrap(it) } - -/* - * Extra delegates for special cases - */ - -fun Configurable.stringList(vararg strings: String, key: Name? = null): ReadWriteDelegateWrapper<Value?, List<String>> = - value(strings.asList(), key) { it?.list?.map { value -> value.string } ?: emptyList() } - -fun Configurable.numberList(vararg numbers: Number, key: Name? = null): ReadWriteDelegateWrapper<Value?, List<Number>> = - value(numbers.asList(), key) { it?.list?.map { value -> value.number } ?: emptyList() } - -/** - * A special delegate for double arrays - */ -fun Configurable.doubleArray(key: Name? = null): ReadWriteDelegateWrapper<Value?, DoubleArray> = - value(doubleArrayOf(), key) { - (it as? DoubleArrayValue)?.value - ?: it?.list?.map { value -> value.number.toDouble() }?.toDoubleArray() - ?: doubleArrayOf() - } - -fun <T : Configurable> Configurable.child(key: Name? = null, converter: (Meta) -> T) = - MutableMorphDelegate(config, key, converter) diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configurableDelegates.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configurableDelegates.kt new file mode 100644 index 00000000..313ddd0b --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configurableDelegates.kt @@ -0,0 +1,236 @@ +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<Any?, MetaItem<*>?> { + + 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) + } + + fun <T> transform( + writer: (T) -> MetaItem<*>? = { MetaItem.of(it) }, + reader: (MetaItem<*>?) -> T + ): ReadWriteProperty<Any?, T> = object : ReadWriteProperty<Any?, T> { + override fun getValue(thisRef: Any?, property: KProperty<*>): T { + return reader(this@ConfigurableDelegate.getValue(thisRef, property)) + } + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { + this@ConfigurableDelegate.setValue(thisRef, property, writer(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?, key: Name? = null): ConfigurableDelegate = + ConfigurableDelegate(this, key, MetaItem.of(default)) + +/** + * 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 <T> Configurable.item( + default: T? = null, + key: Name? = null, + writer: (T) -> MetaItem<*>? = { MetaItem.of(it) }, + reader: (MetaItem<*>?) -> T +): ReadWriteProperty<Any?, T> = + ConfigurableDelegate(this, key, default?.let { MetaItem.of(it) }).transform(reader = reader, writer = writer) + +fun Configurable.value(default: Any? = null, key: Name? = null): ReadWriteProperty<Any?, Value?> = + item(default, key).transform { it.value } + +fun <T> Configurable.value( + default: T? = null, + key: Name? = null, + writer: (T) -> Value? = { Value.of(it) }, + reader: (Value?) -> T +): ReadWriteProperty<Any?, T> = + ConfigurableDelegate(this, key, default?.let { MetaItem.of(it) }).transform( + reader = { reader(it.value) }, + writer = { writer(it)?.let { MetaItem.ValueItem(it) } } + ) + +fun Configurable.string(default: String? = null, key: Name? = null): ReadWriteProperty<Any?, String?> = + item(default, key).transform { it.value?.string } + +fun Configurable.boolean(default: Boolean? = null, key: Name? = null): ReadWriteProperty<Any?, Boolean?> = + item(default, key).transform { it.value?.boolean } + +fun Configurable.number(default: Number? = null, key: Name? = null): ReadWriteProperty<Any?, Number?> = + item(default, key).transform { it.value?.number } + +/* Number delegates*/ + +fun Configurable.int(default: Int? = null, key: Name? = null): ReadWriteProperty<Any?, Int?> = + item(default, key).transform { it.value?.int } + +fun Configurable.double(default: Double? = null, key: Name? = null): ReadWriteProperty<Any?, Double?> = + item(default, key).transform { it.value?.double } + +fun Configurable.long(default: Long? = null, key: Name? = null): ReadWriteProperty<Any?, Long?> = + item(default, key).transform { it.value?.long } + +fun Configurable.short(default: Short? = null, key: Name? = null): ReadWriteProperty<Any?, Short?> = + item(default, key).transform { it.value?.short } + +fun Configurable.float(default: Float? = null, key: Name? = null): ReadWriteProperty<Any?, Float?> = + item(default, key).transform { it.value?.float } + + +@JvmName("safeString") +fun Configurable.string(default: String, key: Name? = null): ReadWriteProperty<Any?, String> = + item(default, key).transform { it.value!!.string } + +@JvmName("safeBoolean") +fun Configurable.boolean(default: Boolean, key: Name? = null): ReadWriteProperty<Any?, Boolean> = + item(default, key).transform { it.value!!.boolean } + +@JvmName("safeNumber") +fun Configurable.number(default: Number, key: Name? = null): ReadWriteProperty<Any?, Number> = + item(default, key).transform { it.value!!.number } + +/* Lazy initializers for values */ + +@JvmName("lazyString") +fun Configurable.string(key: Name? = null, default: () -> String): ReadWriteProperty<Any?, String> = + lazyItem(key, default).transform { it.value!!.string } + +@JvmName("lazyBoolean") +fun Configurable.boolean(key: Name? = null, default: () -> Boolean): ReadWriteProperty<Any?, Boolean> = + lazyItem(key, default).transform { it.value!!.boolean } + +@JvmName("lazyNumber") +fun Configurable.number(key: Name? = null, default: () -> Number): ReadWriteProperty<Any?, Number> = + lazyItem(key, default).transform { it.value!!.number } + +/* Safe number delegates*/ + +@JvmName("safeInt") +fun Configurable.int(default: Int, key: Name? = null): ReadWriteProperty<Any?, Int> = + item(default, key).transform { it.value!!.int } + +@JvmName("safeDouble") +fun Configurable.double(default: Double, key: Name? = null): ReadWriteProperty<Any?, Double> = + item(default, key).transform { it.value!!.double } + +@JvmName("safeLong") +fun Configurable.long(default: Long, key: Name? = null): ReadWriteProperty<Any?, Long> = + item(default, key).transform { it.value!!.long } + +@JvmName("safeShort") +fun Configurable.short(default: Short, key: Name? = null): ReadWriteProperty<Any?, Short> = + item(default, key).transform { it.value!!.short } + +@JvmName("safeFloat") +fun Configurable.float(default: Float, key: Name? = null): ReadWriteProperty<Any?, Float> = + item(default, key).transform { it.value!!.float } + +/** + * Enum delegate + */ +inline fun <reified E : Enum<E>> Configurable.enum(default: E, key: Name? = null): ReadWriteProperty<Any?, E?> = + item(default, key).transform { it.enum<E>() } + +/* + * Extra delegates for special cases + */ + +fun Configurable.stringList(vararg strings: String, key: Name? = null): ReadWriteProperty<Any?, List<String>> = + item(listOf(*strings), key) { + it?.value?.stringList ?: emptyList() + } + +fun Configurable.numberList(vararg numbers: Number, key: Name? = null): ReadWriteProperty<Any?, List<Number>> = + 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<Any?, DoubleArray> = + item(doubleArrayOf(*doubles), key) { + (it.value as? DoubleArrayValue)?.value + ?: it?.value?.list?.map { value -> value.number.toDouble() }?.toDoubleArray() + ?: doubleArrayOf() + } + + +/* Node delegates */ + +fun Configurable.nullableConfig(key: Name? = null): ReadWriteProperty<Any?, Config?> = + object : ReadWriteProperty<Any?, Config?> { + override fun getValue(thisRef: Any?, property: KProperty<*>): Config? { + val name = key ?: property.name.asName() + return config[name].node + } + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: Config?) { + val name = key ?: property.name.asName() + config[name] = value + } + } + +fun Configurable.config(key: Name? = null, default: Config.() -> Unit = {}): ReadWriteProperty<Any?, Config> = + object : ReadWriteProperty<Any?, Config> { + override fun getValue(thisRef: Any?, property: KProperty<*>): Config { + val name = key ?: property.name.asName() + return config[name].node ?: Config().apply(default).also { config[name] = it } + } + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: Config) { + val name = key ?: property.name.asName() + config[name] = value + } + } + +fun <T : Configurable> Configurable.spec(spec: Specification<T>, key: Name? = null): ReadWriteProperty<Any?, T?> = + object : ReadWriteProperty<Any?, T?> { + override fun getValue(thisRef: Any?, property: KProperty<*>): T? { + val name = key ?: property.name.asName() + return 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 + } + + } + diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt index 02caa9fa..7c8d2026 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt @@ -350,24 +350,6 @@ class MutableNodeDelegate<M : MutableMeta<M>>( } } -class MutableMorphDelegate<T : Configurable>( - val meta: MutableMeta<*>, - private val key: Name? = null, - private val converter: (Meta) -> T -) : ReadWriteProperty<Any?, T?> { - override fun getValue(thisRef: Any?, property: KProperty<*>): T? { - return meta[key ?: property.name.asName()]?.node?.let(converter) - } - - override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) { - if (value == null) { - meta.remove(key ?: property.name.asName()) - } else { - meta[key ?: property.name.asName()] = value.config - } - } -} - class ReadWriteDelegateWrapper<T, R>( val delegate: ReadWriteProperty<Any?, T>, val reader: (T) -> R, diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/valueExtensions.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/valueExtensions.kt index a63d5ec1..f9137dca 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/valueExtensions.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/valueExtensions.kt @@ -22,6 +22,7 @@ val Value.boolean val Value.int get() = number.toInt() val Value.double get() = number.toDouble() val Value.float get() = number.toFloat() +val Value.short get() = number.toShort() val Value.long get() = number.toLong() val Value.stringList: List<String> get() = list.map { it.string } diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt index 162a3852..93aad8e5 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt @@ -13,14 +13,13 @@ class MetaDelegateTest { @Test fun delegateTest() { - class InnerSpec(override val config: Config) : Specific { + class InnerSpec : Scheme() { var innerValue by string() } - val innerSpec = specification(::InnerSpec) + val innerSpec = object : SchemeSpec<InnerSpec>(::InnerSpec){} - val testObject = object : Specific { - override val config: Config = Config() + val testObject = object : Scheme(Config()) { var myValue by string() var safeValue by double(2.2) var enumValue by enum(TestEnum.YES) diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MutableMetaTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MutableMetaTest.kt index 9057782f..194c77e3 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MutableMetaTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MutableMetaTest.kt @@ -14,7 +14,7 @@ class MutableMetaTest{ "b" put 22 "c" put "StringValue" } - }.toConfig() + }.asConfig() meta.remove("aNode.c") assertEquals(meta["aNode.c"], null) diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/StyledTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt similarity index 71% rename from dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/StyledTest.kt rename to dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt index a4cbe18e..c7703a47 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/StyledTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt @@ -4,19 +4,22 @@ import kotlin.test.Test import kotlin.test.assertEquals -class StyledTest{ +class SchemeTest{ @Test - fun testSNS(){ - val meta = buildMeta { + fun testMetaScheme(){ + val styled = buildMeta { repeat(10){ "b.a[$it]" put { "d" put it } } - }.seal().withStyle() + }.toScheme() + + val meta = styled.toMeta() + assertEquals(10, meta.values().count()) - val bNode = meta["b"].node + val bNode = styled.getProperty("b").node val aNodes = bNode?.getIndexed("a") diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SpecificationTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SpecificationTest.kt index c11c137b..f21c5b2c 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SpecificationTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SpecificationTest.kt @@ -1,21 +1,26 @@ package hep.dataforge.meta +import hep.dataforge.names.Name import kotlin.test.Test import kotlin.test.assertEquals class SpecificationTest { - class TestSpecific(override val config: Config) : Specific { + class TestStyled(config: Config, defaultProvider: (Name) -> MetaItem<*>?) : + Scheme(config, defaultProvider) { var list by numberList(1, 2, 3) - companion object : Specification<TestSpecific> { - override fun wrap(config: Config): TestSpecific = TestSpecific(config) + companion object : Specification<TestStyled> { + override fun wrap( + config: Config, + defaultProvider: (Name) -> MetaItem<*>? + ): TestStyled = TestStyled(config, defaultProvider) } } @Test - fun testSpecific(){ - val testObject = TestSpecific { + fun testSpecific() { + val testObject = TestStyled { list = emptyList() } assertEquals(emptyList(), testObject.list) From fe6760eee62a0d7733f54a60d84cac3a707ccf5d Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Fri, 10 Jan 2020 11:14:18 +0300 Subject: [PATCH 25/41] Refactor Meta delegates --- .../dataforge/descriptors/ItemDescriptor.kt | 2 +- .../kotlin/hep/dataforge/meta/Configurable.kt | 11 +- ...leDelegates.kt => ConfigurableDelegate.kt} | 45 +- .../kotlin/hep/dataforge/meta/Laminate.kt | 1 + .../kotlin/hep/dataforge/meta/MetaDelegate.kt | 98 +++++ .../hep/dataforge/meta/MutableMetaDelegate.kt | 108 +++++ .../kotlin/hep/dataforge/meta/Scheme.kt | 20 +- .../kotlin/hep/dataforge/meta/mapMeta.kt | 8 - .../hep/dataforge/meta/metaDelegates.kt | 414 ------------------ .../kotlin/hep/dataforge/meta/metaMatcher.kt | 19 +- .../kotlin/hep/dataforge/meta/SchemeTest.kt | 2 +- 11 files changed, 241 insertions(+), 487 deletions(-) rename dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/{configurableDelegates.kt => ConfigurableDelegate.kt} (80%) create mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaDelegate.kt create mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMetaDelegate.kt delete mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt index 99b3e850..514349fe 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt @@ -61,7 +61,7 @@ class NodeDescriptor : ItemDescriptor() { * * @return */ - var default: Config? by nullableConfig() + var default: Config? by config() /** * The map of children node descriptors 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 c13a6214..e95b5564 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Configurable.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Configurable.kt @@ -1,5 +1,9 @@ package hep.dataforge.meta +import hep.dataforge.descriptors.Described +import hep.dataforge.descriptors.NodeDescriptor +import hep.dataforge.descriptors.defaultItem +import hep.dataforge.descriptors.get import hep.dataforge.names.Name import hep.dataforge.names.toName @@ -9,7 +13,7 @@ import hep.dataforge.names.toName * 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 { +interface Configurable : Described { /** * Backing config */ @@ -19,12 +23,15 @@ interface Configurable { * Default meta item provider */ fun getDefaultItem(name: Name): MetaItem<*>? = null + + override val descriptor: NodeDescriptor? get() = null } /** * Get a property with default */ -fun Configurable.getProperty(name: Name): MetaItem<*>? = config[name] ?: getDefaultItem(name) +fun Configurable.getProperty(name: Name): MetaItem<*>? = + config[name] ?: getDefaultItem(name) ?: descriptor?.get(name)?.defaultItem() fun Configurable.getProperty(key: String) = getProperty(key.toName()) diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configurableDelegates.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/ConfigurableDelegate.kt similarity index 80% rename from dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configurableDelegates.kt rename to dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/ConfigurableDelegate.kt index 313ddd0b..31cc2c9c 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/configurableDelegates.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/ConfigurableDelegate.kt @@ -28,19 +28,6 @@ open class ConfigurableDelegate( val name = key ?: property.name.asName() owner.setProperty(name, value) } - - fun <T> transform( - writer: (T) -> MetaItem<*>? = { MetaItem.of(it) }, - reader: (MetaItem<*>?) -> T - ): ReadWriteProperty<Any?, T> = object : ReadWriteProperty<Any?, T> { - override fun getValue(thisRef: Any?, property: KProperty<*>): T { - return reader(this@ConfigurableDelegate.getValue(thisRef, property)) - } - - override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { - this@ConfigurableDelegate.setValue(thisRef, property, writer(value)) - } - } } class LazyConfigurableDelegate( @@ -55,7 +42,7 @@ class LazyConfigurableDelegate( * A property delegate that uses custom key */ fun Configurable.item(default: Any?, key: Name? = null): ConfigurableDelegate = - ConfigurableDelegate(this, key, MetaItem.of(default)) + ConfigurableDelegate(this, key, default?.let { MetaItem.of(it) }) /** * Generation of item delegate with lazy default. @@ -70,7 +57,7 @@ fun <T> Configurable.item( writer: (T) -> MetaItem<*>? = { MetaItem.of(it) }, reader: (MetaItem<*>?) -> T ): ReadWriteProperty<Any?, T> = - ConfigurableDelegate(this, key, default?.let { MetaItem.of(it) }).transform(reader = reader, writer = writer) + ConfigurableDelegate(this, key, default?.let { MetaItem.of(it) }).map(reader = reader, writer = writer) fun Configurable.value(default: Any? = null, key: Name? = null): ReadWriteProperty<Any?, Value?> = item(default, key).transform { it.value } @@ -81,7 +68,7 @@ fun <T> Configurable.value( writer: (T) -> Value? = { Value.of(it) }, reader: (Value?) -> T ): ReadWriteProperty<Any?, T> = - ConfigurableDelegate(this, key, default?.let { MetaItem.of(it) }).transform( + ConfigurableDelegate(this, key, default?.let { MetaItem.of(it) }).map( reader = { reader(it.value) }, writer = { writer(it)?.let { MetaItem.ValueItem(it) } } ) @@ -194,31 +181,9 @@ fun Configurable.doubleArray(vararg doubles: Double, key: Name? = null): ReadWri /* Node delegates */ -fun Configurable.nullableConfig(key: Name? = null): ReadWriteProperty<Any?, Config?> = - object : ReadWriteProperty<Any?, Config?> { - override fun getValue(thisRef: Any?, property: KProperty<*>): Config? { - val name = key ?: property.name.asName() - return config[name].node - } +fun Configurable.config(key: Name? = null): ReadWriteProperty<Any?, Config?> = + config.node(key) - override fun setValue(thisRef: Any?, property: KProperty<*>, value: Config?) { - val name = key ?: property.name.asName() - config[name] = value - } - } - -fun Configurable.config(key: Name? = null, default: Config.() -> Unit = {}): ReadWriteProperty<Any?, Config> = - object : ReadWriteProperty<Any?, Config> { - override fun getValue(thisRef: Any?, property: KProperty<*>): Config { - val name = key ?: property.name.asName() - return config[name].node ?: Config().apply(default).also { config[name] = it } - } - - override fun setValue(thisRef: Any?, property: KProperty<*>, value: Config) { - val name = key ?: property.name.asName() - config[name] = value - } - } fun <T : Configurable> Configurable.spec(spec: Specification<T>, key: Name? = null): ReadWriteProperty<Any?, T?> = object : ReadWriteProperty<Any?, T?> { diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt index c6d69967..beda1732 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt @@ -5,6 +5,7 @@ import hep.dataforge.names.NameToken /** * A meta laminate consisting of multiple immutable meta layers. For mutable front layer, use [Scheme]. + * If [layers] list contains a [Laminate] it is flat-mapped. */ class Laminate(layers: List<Meta>) : MetaBase() { diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaDelegate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaDelegate.kt new file mode 100644 index 00000000..50764a7c --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaDelegate.kt @@ -0,0 +1,98 @@ +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<Any?, MetaItem<*>?> { + 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<T, R>( + val delegate: ReadOnlyProperty<Any?, T>, + val reader: (T) -> R +) : ReadOnlyProperty<Any?, R> { + override fun getValue(thisRef: Any?, property: KProperty<*>): R { + return reader(delegate.getValue(thisRef, property)) + } +} + +fun <T, R> ReadOnlyProperty<Any?, T>.map(reader: (T) -> R): DelegateWrapper<T, R> = + 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 <reified E : Enum<E>> Meta.enum(default: E, key: Name? = null) = + item(default, key).map { it.enum<E>()!! } \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMetaDelegate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMetaDelegate.kt new file mode 100644 index 00000000..5ab6ba0c --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMetaDelegate.kt @@ -0,0 +1,108 @@ +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<M : MutableMeta<M>>( + override val owner: M, + key: Name? = null, + default: MetaItem<*>? = null +) : MetaDelegate(owner, key, default), ReadWriteProperty<Any?, MetaItem<*>?> { + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: MetaItem<*>?) { + val name = key ?: property.name.asName() + owner.setItem(name, value) + } +} + +class LazyMutableMetaDelegate<M : MutableMeta<M>>( + owner: M, + key: Name? = null, + defaultProvider: () -> MetaItem<*>? = { null } +) : MutableMetaDelegate<M>(owner, key) { + override val default by lazy(defaultProvider) +} + +class ReadWriteDelegateWrapper<T, R>( + val delegate: ReadWriteProperty<Any?, T>, + val reader: (T) -> R, + val writer: (R) -> T +) : ReadWriteProperty<Any?, R> { + override fun getValue(thisRef: Any?, property: KProperty<*>): R { + return reader(delegate.getValue(thisRef, property)) + } + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: R) { + delegate.setValue(thisRef, property, writer(value)) + } +} + +fun <T, R> ReadWriteProperty<Any?, T>.map(reader: (T) -> R, writer: (R) -> T): ReadWriteDelegateWrapper<T, R> = + ReadWriteDelegateWrapper(this, reader, writer) + +fun <R> ReadWriteProperty<Any?, MetaItem<*>?>.transform(reader: (MetaItem<*>?) -> R): ReadWriteProperty<Any?, R> = + map(reader = reader, writer = { MetaItem.of(it) }) + +fun <R> ReadWriteProperty<Any?, Value?>.transform(reader: (Value?) -> R) = + map(reader = reader, writer = { Value.of(it) }) + + +fun <M : MutableMeta<M>> M.item(default: Any? = null, key: Name? = null): MutableMetaDelegate<M> = + MutableMetaDelegate(this, key, default?.let { MetaItem.of(it) }) + +fun <M : MutableMeta<M>> M.lazyItem(key: Name? = null, defaultProvider: () -> Any?): LazyMutableMetaDelegate<M> = + LazyMutableMetaDelegate(this, key) { defaultProvider()?.let { MetaItem.of(it) } } + +//Read-write delegates + +/** + * A property delegate that uses custom key + */ +fun <M : MutableMeta<M>> M.value(default: Value? = null, key: Name? = null): ReadWriteProperty<Any?, Value?> = + item(default, key).transform { it.value } + +fun <M : MutableMeta<M>> M.string(default: String? = null, key: Name? = null): ReadWriteProperty<Any?, String?> = + item(default, key).transform { it.string } + +fun <M : MutableMeta<M>> M.boolean(default: Boolean? = null, key: Name? = null): ReadWriteProperty<Any?, Boolean?> = + item(default, key).transform { it.boolean } + +fun <M : MutableMeta<M>> M.number(default: Number? = null, key: Name? = null): ReadWriteProperty<Any?, Number?> = + item(default, key).transform { it.number } + +inline fun <reified M : MutableMeta<M>> M.node(key: Name? = null) = + item(this, key).transform { it.node as? M } + +@JvmName("safeString") +fun <M : MutableMeta<M>> M.string(default: String, key: Name? = null) = + item(default, key).transform { it.string!! } + +@JvmName("safeBoolean") +fun <M : MutableMeta<M>> M.boolean(default: Boolean, key: Name? = null) = + item(default, key).transform { it.boolean!! } + +@JvmName("safeNumber") +fun <M : MutableMeta<M>> M.number(default: Number, key: Name? = null) = + item(default, key).transform { it.number!! } + +@JvmName("lazyString") +fun <M : MutableMeta<M>> M.string(key: Name? = null, default: () -> String) = + lazyItem(key, default).transform { it.string!! } + +@JvmName("safeBoolean") +fun <M : MutableMeta<M>> M.boolean(key: Name? = null, default: () -> Boolean) = + lazyItem(key, default).transform { it.boolean!! } + +@JvmName("safeNumber") +fun <M : MutableMeta<M>> M.number(key: Name? = null, default: () -> Number) = + lazyItem(key, default).transform { it.number!! } + + +inline fun <M : MutableMeta<M>, reified E : Enum<E>> M.enum(default: E, key: Name? = null) = + item(default, key).transform { it.enum<E>()!! } \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Scheme.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Scheme.kt index 2de46dc0..84b350f1 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Scheme.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Scheme.kt @@ -5,6 +5,9 @@ import hep.dataforge.names.Name import hep.dataforge.names.NameToken import hep.dataforge.names.plus +/** + * A base for delegate-based or descriptor-based scheme. [Scheme] has an empty constructor to simplify usage from [Specification]. + */ open class Scheme() : Configurable, Described { constructor(config: Config, defaultProvider: (Name) -> MetaItem<*>?) : this() { this.config = config @@ -14,13 +17,14 @@ open class Scheme() : Configurable, Described { //constructor(config: Config, default: Meta) : this(config, { default[it] }) constructor(config: Config) : this(config, { null }) - final override lateinit var config: Config + final override var config: Config = Config() internal set lateinit var defaultProvider: (Name) -> MetaItem<*>? internal set - override val descriptor: NodeDescriptor? = null + final override var descriptor: NodeDescriptor? = null + internal set override fun getDefaultItem(name: Name): MetaItem<*>? { return defaultProvider(name) ?: descriptor?.get(name)?.defaultItem() @@ -60,15 +64,21 @@ open class SchemeSpec<T : Scheme>(val builder: () -> T) : Specification<T> { } } +/** + * A scheme that uses [Meta] as a default layer + */ open class MetaScheme( val meta: Meta, - override val descriptor: NodeDescriptor? = null, + descriptor: NodeDescriptor? = null, config: Config = Config() ) : Scheme(config, meta::get) { - override val defaultLayer: Meta get() = meta + init { + this.descriptor = descriptor + } + override val defaultLayer: Meta get() = Laminate(meta, descriptor?.defaultItem().node) } -fun Meta.toScheme() = MetaScheme(this) +fun Meta.asScheme() = MetaScheme(this) fun <T : Configurable> Meta.toScheme(spec: Specification<T>, block: T.() -> Unit) = spec.wrap(this).apply(block) diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/mapMeta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/mapMeta.kt index b0d8162c..ab68212c 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/mapMeta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/mapMeta.kt @@ -3,14 +3,6 @@ package hep.dataforge.meta import hep.dataforge.descriptors.NodeDescriptor import hep.dataforge.values.Value -///** -// * Find all elements with given body -// */ -//private fun Meta.byBody(body: String): Map<String, MetaItem<*>> = -// items.filter { it.key.body == body }.mapKeys { it.key.index } -// -//private fun Meta.distinctNames() = items.keys.map { it.body }.distinct() - /** * Convert meta to map of maps */ diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt deleted file mode 100644 index 7c8d2026..00000000 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaDelegates.kt +++ /dev/null @@ -1,414 +0,0 @@ -package hep.dataforge.meta - -import hep.dataforge.names.Name -import hep.dataforge.names.asName -import hep.dataforge.values.Null -import hep.dataforge.values.Value -import hep.dataforge.values.asValue -import kotlin.jvm.JvmName -import kotlin.properties.ReadOnlyProperty -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KProperty - -/* Meta delegates */ - -//TODO add caching for sealed nodes - -class ValueDelegate(val meta: Meta, private val key: String? = null, private val default: Value? = null) : - ReadOnlyProperty<Any?, Value?> { - override fun getValue(thisRef: Any?, property: KProperty<*>): Value? { - return meta[key ?: property.name]?.value ?: default - } -} - -class StringDelegate(val meta: Meta, private val key: String? = null, private val default: String? = null) : - ReadOnlyProperty<Any?, String?> { - override fun getValue(thisRef: Any?, property: KProperty<*>): String? { - return meta[key ?: property.name]?.string ?: default - } -} - -class BooleanDelegate( - val meta: Meta, - private val key: String? = null, - private val default: Boolean? = null -) : ReadOnlyProperty<Any?, Boolean?> { - override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean? { - return meta[key ?: property.name]?.boolean ?: default - } -} - -class NumberDelegate( - val meta: Meta, - private val key: String? = null, - private val default: Number? = null -) : ReadOnlyProperty<Any?, Number?> { - override fun getValue(thisRef: Any?, property: KProperty<*>): Number? { - return meta[key ?: property.name]?.number ?: default - } - - //delegates for number transformation - - val double get() = DelegateWrapper(this) { it?.toDouble() } - val int get() = DelegateWrapper(this) { it?.toInt() } - val short get() = DelegateWrapper(this) { it?.toShort() } - val long get() = DelegateWrapper(this) { it?.toLong() } -} - -class DelegateWrapper<T, R>(val delegate: ReadOnlyProperty<Any?, T>, val reader: (T) -> R) : - ReadOnlyProperty<Any?, R> { - override fun getValue(thisRef: Any?, property: KProperty<*>): R { - return reader(delegate.getValue(thisRef, property)) - } -} - -//Delegates with non-null values - -class SafeStringDelegate( - val meta: Meta, - private val key: String? = null, - default: () -> String -) : ReadOnlyProperty<Any?, String> { - - private val default: String by lazy(default) - - override fun getValue(thisRef: Any?, property: KProperty<*>): String { - return meta[key ?: property.name]?.string ?: default - } -} - -class SafeBooleanDelegate( - val meta: Meta, - private val key: String? = null, - default: () -> Boolean -) : ReadOnlyProperty<Any?, Boolean> { - - private val default: Boolean by lazy(default) - - override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean { - return meta[key ?: property.name]?.boolean ?: default - } -} - -class SafeNumberDelegate( - val meta: Meta, - private val key: String? = null, - default: () -> Number -) : ReadOnlyProperty<Any?, Number> { - - private val default: Number by lazy(default) - - override fun getValue(thisRef: Any?, property: KProperty<*>): Number { - return meta[key ?: property.name]?.number ?: default - } - - val double get() = DelegateWrapper(this) { it.toDouble() } - val int get() = DelegateWrapper(this) { it.toInt() } - val short get() = DelegateWrapper(this) { it.toShort() } - val long get() = DelegateWrapper(this) { it.toLong() } -} - -class SafeEnumDelegate<E : Enum<E>>( - val meta: Meta, - private val key: String? = null, - private val default: E, - private val resolver: (String) -> E -) : ReadOnlyProperty<Any?, E> { - override fun getValue(thisRef: Any?, property: KProperty<*>): E { - return (meta[key ?: property.name]?.string)?.let { resolver(it) } ?: default - } -} - -//Child node delegate - -class ChildDelegate<T>( - val meta: Meta, - private val key: String? = null, - private val converter: (Meta) -> T -) : ReadOnlyProperty<Any?, T?> { - override fun getValue(thisRef: Any?, property: KProperty<*>): T? { - return meta[key ?: property.name]?.node?.let { converter(it) } - } -} - -//Read-only delegates for Metas - -/** - * A property delegate that uses custom key - */ -fun Meta.value(default: Value = Null, key: String? = null) = ValueDelegate(this, key, default) - -fun Meta.string(default: String? = null, key: String? = null) = StringDelegate(this, key, default) - -fun Meta.boolean(default: Boolean? = null, key: String? = null) = BooleanDelegate(this, key, default) - -fun Meta.number(default: Number? = null, key: String? = null) = NumberDelegate(this, key, default) - -fun Meta.child(key: String? = null) = ChildDelegate(this, key) { it } - -@JvmName("safeString") -fun Meta.string(default: String, key: String? = null) = - SafeStringDelegate(this, key) { default } - -@JvmName("safeBoolean") -fun Meta.boolean(default: Boolean, key: String? = null) = - SafeBooleanDelegate(this, key) { default } - -@JvmName("safeNumber") -fun Meta.number(default: Number, key: String? = null) = - SafeNumberDelegate(this, key) { default } - -@JvmName("safeString") -fun Meta.string(key: String? = null, default: () -> String) = - SafeStringDelegate(this, key, default) - -@JvmName("safeBoolean") -fun Meta.boolean(key: String? = null, default: () -> Boolean) = - SafeBooleanDelegate(this, key, default) - -@JvmName("safeNumber") -fun Meta.number(key: String? = null, default: () -> Number) = - SafeNumberDelegate(this, key, default) - - -inline fun <reified E : Enum<E>> Meta.enum(default: E, key: String? = null) = - SafeEnumDelegate(this, key, default) { enumValueOf(it) } - -/* Read-write delegates */ - -class MutableValueDelegate<M : MutableMeta<M>>( - val meta: M, - private val key: Name? = null, - private val default: Value? = null -) : ReadWriteProperty<Any?, Value?> { - override fun getValue(thisRef: Any?, property: KProperty<*>): Value? { - return meta[key ?: property.name.asName()]?.value ?: default - } - - override fun setValue(thisRef: Any?, property: KProperty<*>, value: Value?) { - val name = key ?: property.name.asName() - if (value == null) { - meta.remove(name) - } else { - meta.setValue(name, value) - } - } - - fun <T> transform(writer: (T) -> Value? = { Value.of(it) }, reader: (Value?) -> T) = - ReadWriteDelegateWrapper(this, reader, writer) -} - -class MutableStringDelegate<M : MutableMeta<M>>( - val meta: M, - private val key: Name? = null, - private val default: String? = null -) : ReadWriteProperty<Any?, String?> { - override fun getValue(thisRef: Any?, property: KProperty<*>): String? { - return meta[key ?: property.name.asName()]?.string ?: default - } - - override fun setValue(thisRef: Any?, property: KProperty<*>, value: String?) { - val name = key ?: property.name.asName() - if (value == null) { - meta.remove(name) - } else { - meta.setValue(name, value.asValue()) - } - } -} - -class MutableBooleanDelegate<M : MutableMeta<M>>( - val meta: M, - private val key: Name? = null, - private val default: Boolean? = null -) : ReadWriteProperty<Any?, Boolean?> { - override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean? { - return meta[key ?: property.name.asName()]?.boolean ?: default - } - - override fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean?) { - val name = key ?: property.name.asName() - if (value == null) { - meta.remove(name) - } else { - meta.setValue(name, value.asValue()) - } - } -} - -class MutableNumberDelegate<M : MutableMeta<M>>( - val meta: M, - private val key: Name? = null, - private val default: Number? = null -) : ReadWriteProperty<Any?, Number?> { - override fun getValue(thisRef: Any?, property: KProperty<*>): Number? { - return meta[key ?: property.name.asName()]?.number ?: default - } - - override fun setValue(thisRef: Any?, property: KProperty<*>, value: Number?) { - val name = key ?: property.name.asName() - if (value == null) { - meta.remove(name) - } else { - meta.setValue(name, value.asValue()) - } - } - - val double get() = ReadWriteDelegateWrapper(this, reader = { it?.toDouble() }, writer = { it }) - val float get() = ReadWriteDelegateWrapper(this, reader = { it?.toFloat() }, writer = { it }) - val int get() = ReadWriteDelegateWrapper(this, reader = { it?.toInt() }, writer = { it }) - val short get() = ReadWriteDelegateWrapper(this, reader = { it?.toShort() }, writer = { it }) - val long get() = ReadWriteDelegateWrapper(this, reader = { it?.toLong() }, writer = { it }) -} - -//Delegates with non-null values - -class MutableSafeStringDelegate<M : MutableMeta<M>>( - val meta: M, - private val key: Name? = null, - default: () -> String -) : ReadWriteProperty<Any?, String> { - - private val default: String by lazy(default) - - override fun getValue(thisRef: Any?, property: KProperty<*>): String { - return meta[key ?: property.name.asName()]?.string ?: default - } - - override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { - meta.setValue(key ?: property.name.asName(), value.asValue()) - } -} - -class MutableSafeBooleanDelegate<M : MutableMeta<M>>( - val meta: M, - private val key: Name? = null, - default: () -> Boolean -) : ReadWriteProperty<Any?, Boolean> { - - private val default: Boolean by lazy(default) - - override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean { - return meta[key ?: property.name.asName()]?.boolean ?: default - } - - override fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) { - meta.setValue(key ?: property.name.asName(), value.asValue()) - } -} - -class MutableSafeNumberDelegate<M : MutableMeta<M>>( - val meta: M, - private val key: Name? = null, - default: () -> Number -) : ReadWriteProperty<Any?, Number> { - - private val default: Number by lazy(default) - - override fun getValue(thisRef: Any?, property: KProperty<*>): Number { - return meta[key ?: property.name.asName()]?.number ?: default - } - - override fun setValue(thisRef: Any?, property: KProperty<*>, value: Number) { - meta.setValue(key ?: property.name.asName(), value.asValue()) - } - - val double get() = ReadWriteDelegateWrapper(this, reader = { it.toDouble() }, writer = { it }) - val float get() = ReadWriteDelegateWrapper(this, reader = { it.toFloat() }, writer = { it }) - val int get() = ReadWriteDelegateWrapper(this, reader = { it.toInt() }, writer = { it }) - val short get() = ReadWriteDelegateWrapper(this, reader = { it.toShort() }, writer = { it }) - val long get() = ReadWriteDelegateWrapper(this, reader = { it.toLong() }, writer = { it }) -} - -class MutableSafeEnumvDelegate<M : MutableMeta<M>, E : Enum<E>>( - val meta: M, - private val key: Name? = null, - private val default: E, - private val resolver: (String) -> E -) : ReadWriteProperty<Any?, E> { - override fun getValue(thisRef: Any?, property: KProperty<*>): E { - return (meta[key ?: property.name.asName()]?.string)?.let { resolver(it) } ?: default - } - - override fun setValue(thisRef: Any?, property: KProperty<*>, value: E) { - meta.setValue(key ?: property.name.asName(), value.name.asValue()) - } -} - -//Child node delegate - -class MutableNodeDelegate<M : MutableMeta<M>>( - val meta: M, - private val key: Name? = null -) : ReadWriteProperty<Any?, M?> { - override fun getValue(thisRef: Any?, property: KProperty<*>): M? { - return meta[key ?: property.name.asName()]?.node - } - - override fun setValue(thisRef: Any?, property: KProperty<*>, value: M?) { - meta[key ?: property.name.asName()] = value - } -} - -class ReadWriteDelegateWrapper<T, R>( - val delegate: ReadWriteProperty<Any?, T>, - val reader: (T) -> R, - val writer: (R) -> T -) : ReadWriteProperty<Any?, R> { - override fun getValue(thisRef: Any?, property: KProperty<*>): R { - return reader(delegate.getValue(thisRef, property)) - } - - override fun setValue(thisRef: Any?, property: KProperty<*>, value: R) { - delegate.setValue(thisRef, property, writer(value)) - } -} - - -//Read-write delegates - -/** - * A property delegate that uses custom key - */ -fun <M : MutableMeta<M>> M.value(default: Value = Null, key: Name? = null) = - MutableValueDelegate(this, key, default) - -fun <M : MutableMeta<M>> M.string(default: String? = null, key: Name? = null) = - MutableStringDelegate(this, key, default) - -fun <M : MutableMeta<M>> M.boolean(default: Boolean? = null, key: Name? = null) = - MutableBooleanDelegate(this, key, default) - -fun <M : MutableMeta<M>> M.number(default: Number? = null, key: Name? = null) = - MutableNumberDelegate(this, key, default) - -fun <M : MutableMeta<M>> M.child(key: Name? = null) = - MutableNodeDelegate(this, key) - -@JvmName("safeString") -fun <M : MutableMeta<M>> M.string(default: String, key: Name? = null) = - MutableSafeStringDelegate(this, key) { default } - -@JvmName("safeBoolean") -fun <M : MutableMeta<M>> M.boolean(default: Boolean, key: Name? = null) = - MutableSafeBooleanDelegate(this, key) { default } - -@JvmName("safeNumber") -fun <M : MutableMeta<M>> M.number(default: Number, key: Name? = null) = - MutableSafeNumberDelegate(this, key) { default } - -@JvmName("safeString") -fun <M : MutableMeta<M>> M.string(key: Name? = null, default: () -> String) = - MutableSafeStringDelegate(this, key, default) - -@JvmName("safeBoolean") -fun <M : MutableMeta<M>> M.boolean(key: Name? = null, default: () -> Boolean) = - MutableSafeBooleanDelegate(this, key, default) - -@JvmName("safeNumber") -fun <M : MutableMeta<M>> M.number(key: Name? = null, default: () -> Number) = - MutableSafeNumberDelegate(this, key, default) - - -inline fun <M : MutableMeta<M>, reified E : Enum<E>> M.enum(default: E, key: Name? = null) = - MutableSafeEnumvDelegate(this, key, default) { enumValueOf(it) } \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaMatcher.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaMatcher.kt index 6f16f537..263483a2 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaMatcher.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaMatcher.kt @@ -26,26 +26,13 @@ fun Meta.getIndexed(name: Name): Map<String, MetaItem<*>> { @DFExperimental fun Meta.getIndexed(name: String): Map<String, MetaItem<*>> = this@getIndexed.getIndexed(name.toName()) - /** * Get all items matching given name. */ +@Suppress("UNCHECKED_CAST") @DFExperimental -fun <M : MetaNode<M>> M.getIndexed(name: Name): Map<String, MetaItem<M>> { - val root: MetaNode<M>? = when (name.length) { - 0 -> error("Can't use empty name for that") - 1 -> this - else -> (this[name.cutLast()] as? MetaItem.NodeItem<M>)?.node - } - - val (body, index) = name.last()!! - val regex = index.toRegex() - - return root?.items - ?.filter { it.key.body == body && (index.isEmpty() || regex.matches(it.key.index)) } - ?.mapKeys { it.key.index } - ?: emptyMap() -} +fun <M : MetaNode<M>> M.getIndexed(name: Name): Map<String, MetaItem<M>> = + (this as Meta).getIndexed(name) as Map<String, MetaItem<M>> @DFExperimental fun <M : MetaNode<M>> M.getIndexed(name: String): Map<String, MetaItem<M>> = getIndexed(name.toName()) \ 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 c7703a47..0246fb10 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt @@ -13,7 +13,7 @@ class SchemeTest{ "d" put it } } - }.toScheme() + }.asScheme() val meta = styled.toMeta() From f906fbdb0a07e9ccbb09ad4fc8bdc6af09493615 Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Wed, 29 Jan 2020 21:34:51 +0300 Subject: [PATCH 26/41] Tables basics A lot of refactoring --- build.gradle.kts | 2 +- .../hep/dataforge/descriptors/annotations.kt | 130 +++++++++++++++ .../descriptors/reflectiveDescriptors.kt | 65 ++++++++ .../kotlin/hep/dataforge/data/DataFilter.kt | 3 + .../hep/dataforge/io/yaml/YamlMetaFormat.kt | 3 +- .../hep/dataforge/io/BinaryMetaFormat.kt | 2 +- .../kotlin/hep/dataforge/io/JsonMetaFormat.kt | 6 +- .../kotlin/hep/dataforge/io/MetaFormat.kt | 2 +- .../io/functions/RemoteFunctionClient.kt | 1 + .../io/functions/RemoteFunctionServer.kt | 1 + .../io/serialization/MetaSerializer.kt | 2 +- .../kotlin/hep/dataforge/io/MultipartTest.kt | 1 + .../jvmMain/kotlin/hep/dataforge/io/fileIO.kt | 2 +- .../hep/dataforge/descriptors/annotations.kt | 149 ------------------ .../kotlin/hep/dataforge/meta/Config.kt | 11 +- .../kotlin/hep/dataforge/meta/MutableMeta.kt | 5 +- .../{ => meta}/descriptors/Described.kt | 2 +- .../{ => meta}/descriptors/DescriptorMeta.kt | 2 +- .../{ => meta}/descriptors/ItemDescriptor.kt | 55 +++++-- .../kotlin/hep/dataforge/meta/mapMeta.kt | 2 +- .../meta/{ => scheme}/Configurable.kt | 22 ++- .../meta/{ => scheme}/ConfigurableDelegate.kt | 37 ++++- .../hep/dataforge/meta/{ => scheme}/Scheme.kt | 25 ++- .../meta/{ => scheme}/Specification.kt | 10 +- .../MetaTransformation.kt | 39 +++-- .../hep/dataforge/meta/MetaDelegateTest.kt | 1 + .../kotlin/hep/dataforge/meta/SchemeTest.kt | 3 + .../hep/dataforge/meta/SpecificationTest.kt | 3 + .../{ => meta}/descriptors/DescriptorTest.kt | 10 +- .../dataforge-output-html}/build.gradle.kts | 0 .../hep/dataforge/output/html/HtmlRenderer.kt | 0 .../hep/dataforge/scripting/BuildersKtTest.kt | 1 + dataforge-tables/build.gradle | 25 --- dataforge-tables/build.gradle.kts | 14 ++ .../hep/dataforge/tables/ColumnScheme.kt | 8 + .../hep/dataforge/tables/ColumnTable.kt | 35 ++++ .../kotlin/hep/dataforge/tables/ListColumn.kt | 28 ++++ .../kotlin/hep/dataforge/tables/MapRow.kt | 19 +++ .../hep/dataforge/tables/NumberColumn.kt | 94 +++++++++++ .../kotlin/hep/dataforge/tables/RowTable.kt | 26 +++ .../kotlin/hep/dataforge/tables/Table.kt | 30 ++++ .../hep/dataforge/tables/TableAccessor.kt | 37 +++++ .../hep/dataforge/workspace/GenericTask.kt | 2 +- .../kotlin/hep/dataforge/workspace/Task.kt | 2 +- .../hep/dataforge/workspace/TaskBuilder.kt | 2 +- .../workspace/SimpleWorkspaceTest.kt | 1 + settings.gradle.kts | 3 +- 47 files changed, 664 insertions(+), 259 deletions(-) create mode 100644 dataforge-context/src/jvmMain/kotlin/hep/dataforge/descriptors/annotations.kt create mode 100644 dataforge-context/src/jvmMain/kotlin/hep/dataforge/descriptors/reflectiveDescriptors.kt delete mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/annotations.kt rename dataforge-meta/src/commonMain/kotlin/hep/dataforge/{ => meta}/descriptors/Described.kt (92%) rename dataforge-meta/src/commonMain/kotlin/hep/dataforge/{ => meta}/descriptors/DescriptorMeta.kt (95%) rename dataforge-meta/src/commonMain/kotlin/hep/dataforge/{ => meta}/descriptors/ItemDescriptor.kt (85%) rename dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/{ => scheme}/Configurable.kt (71%) rename dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/{ => scheme}/ConfigurableDelegate.kt (88%) rename dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/{ => scheme}/Scheme.kt (81%) rename dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/{ => scheme}/Specification.kt (90%) rename dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/{ => transformations}/MetaTransformation.kt (83%) rename dataforge-meta/src/commonTest/kotlin/hep/dataforge/{ => meta}/descriptors/DescriptorTest.kt (77%) rename {dataforge-output-html => dataforge-output/dataforge-output-html}/build.gradle.kts (100%) rename {dataforge-output-html => dataforge-output/dataforge-output-html}/src/commonMain/kotlin/hep/dataforge/output/html/HtmlRenderer.kt (100%) delete mode 100644 dataforge-tables/build.gradle create mode 100644 dataforge-tables/build.gradle.kts create mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnScheme.kt create mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt create mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ListColumn.kt create mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt create mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/NumberColumn.kt create mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt create mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt create mode 100644 dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/TableAccessor.kt diff --git a/build.gradle.kts b/build.gradle.kts index 53a45364..14ccd3e9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("scientifik.publish") version toolsVersion apply false } -val dataforgeVersion by extra("0.1.5-dev-7") +val dataforgeVersion by extra("0.1.5-dev-8") val bintrayRepo by extra("dataforge") val githubProject by extra("dataforge-core") diff --git a/dataforge-context/src/jvmMain/kotlin/hep/dataforge/descriptors/annotations.kt b/dataforge-context/src/jvmMain/kotlin/hep/dataforge/descriptors/annotations.kt new file mode 100644 index 00000000..cadd4231 --- /dev/null +++ b/dataforge-context/src/jvmMain/kotlin/hep/dataforge/descriptors/annotations.kt @@ -0,0 +1,130 @@ +/* + * Copyright 2018 Alexander Nozik. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package hep.dataforge.descriptors + +import hep.dataforge.meta.DFExperimental +import hep.dataforge.values.ValueType +import kotlin.reflect.KClass + +@MustBeDocumented +annotation class Attribute( + val key: String, + val value: String +) + +@MustBeDocumented +annotation class Attributes( + val attrs: Array<Attribute> +) + +@MustBeDocumented +annotation class ItemDef( + val info: String = "", + val multiple: Boolean = false, + val required: Boolean = false +) + +@Target(AnnotationTarget.PROPERTY) +@MustBeDocumented +annotation class ValueDef( + val type: Array<ValueType> = [ValueType.STRING], + val def: String = "", + val allowed: Array<String> = [], + val enumeration: KClass<*> = Any::class +) + +///** +// * Description text for meta property, node or whole object +// */ +//@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY) +//@Retention(AnnotationRetention.RUNTIME) +//@MustBeDocumented +//annotation class Description(val value: String) +// +///** +// * Annotation for value property which states that lists are expected +// */ +//@Target(AnnotationTarget.PROPERTY) +//@Retention(AnnotationRetention.RUNTIME) +//@MustBeDocumented +//annotation class Multiple +// +///** +// * Descriptor target +// * The DataForge path to the resource containing the description. Following targets are supported: +// * 1. resource +// * 1. file +// * 1. class +// * 1. method +// * 1. property +// * +// * +// * Does not work if [type] is provided +// */ +//@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER) +//@Retention(AnnotationRetention.RUNTIME) +//@MustBeDocumented +//annotation class Descriptor(val value: String) +// +// +///** +// * Aggregator class for descriptor nodes +// */ +//@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER) +//@Retention(AnnotationRetention.RUNTIME) +//@MustBeDocumented +//annotation class DescriptorNodes(vararg val nodes: NodeDef) +// +///** +// * Aggregator class for descriptor values +// */ +//@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER) +//@Retention(AnnotationRetention.RUNTIME) +//@MustBeDocumented +//annotation class DescriptorValues(vararg val nodes: ValueDef) +// +///** +// * Alternative name for property descriptor declaration +// */ +//@Target(AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER) +//@Retention(AnnotationRetention.RUNTIME) +//@MustBeDocumented +//annotation class DescriptorName(val name: String) +// +//@Target(AnnotationTarget.PROPERTY) +//@Retention(AnnotationRetention.RUNTIME) +//@MustBeDocumented +//annotation class DescriptorValue(val def: ValueDef) +////TODO enter fields directly? +// +//@Target(AnnotationTarget.PROPERTY) +//@Retention(AnnotationRetention.RUNTIME) +//@MustBeDocumented +//annotation class ValueProperty( +// val name: String = "", +// val type: Array<ValueType> = arrayOf(ValueType.STRING), +// val multiple: Boolean = false, +// val def: String = "", +// val enumeration: KClass<*> = Any::class, +// val tags: Array<String> = emptyArray() +//) +// +// +//@Target(AnnotationTarget.PROPERTY) +//@Retention(AnnotationRetention.RUNTIME) +//@MustBeDocumented +//annotation class NodeProperty(val name: String = "") diff --git a/dataforge-context/src/jvmMain/kotlin/hep/dataforge/descriptors/reflectiveDescriptors.kt b/dataforge-context/src/jvmMain/kotlin/hep/dataforge/descriptors/reflectiveDescriptors.kt new file mode 100644 index 00000000..0015436c --- /dev/null +++ b/dataforge-context/src/jvmMain/kotlin/hep/dataforge/descriptors/reflectiveDescriptors.kt @@ -0,0 +1,65 @@ +package hep.dataforge.descriptors + +import hep.dataforge.meta.* +import hep.dataforge.meta.descriptors.ItemDescriptor +import hep.dataforge.meta.descriptors.NodeDescriptor +import hep.dataforge.meta.descriptors.attributes +import hep.dataforge.meta.scheme.ConfigurableDelegate +import hep.dataforge.meta.scheme.Scheme +import hep.dataforge.values.parseValue +import kotlin.reflect.KProperty1 +import kotlin.reflect.full.findAnnotation +import kotlin.reflect.full.isSubclassOf +import kotlin.reflect.full.memberProperties + + +//inline fun <reified T : Scheme> T.buildDescriptor(): NodeDescriptor = NodeDescriptor { +// T::class.apply { +// findAnnotation<ItemDef>()?.let { def -> +// info = def.info +// required = def.required +// multiple = def.multiple +// } +// findAnnotation<Attribute>()?.let { attr -> +// attributes { +// this[attr.key] = attr.value.parseValue() +// } +// } +// findAnnotation<Attributes>()?.attrs?.forEach { attr -> +// attributes { +// this[attr.key] = attr.value.parseValue() +// } +// } +// } +// T::class.memberProperties.forEach { property -> +// val delegate = property.getDelegate(this@buildDescriptor) +// +// val descriptor: ItemDescriptor = when (delegate) { +// is ConfigurableDelegate -> buildPropertyDescriptor(property, delegate) +// is ReadWriteDelegateWrapper<*, *> -> { +// if (delegate.delegate is ConfigurableDelegate) { +// buildPropertyDescriptor(property, delegate.delegate as ConfigurableDelegate) +// } else { +// return@forEach +// } +// } +// else -> return@forEach +// } +// defineItem(property.name, descriptor) +// } +//} + +//inline fun <T : Scheme, reified V : Any?> buildPropertyDescriptor( +// property: KProperty1<T, V>, +// delegate: ConfigurableDelegate +//): ItemDescriptor { +// when { +// V::class.isSubclassOf(Scheme::class) -> NodeDescriptor { +// default = delegate.default.node +// } +// V::class.isSubclassOf(Meta::class) -> NodeDescriptor { +// default = delegate.default.node +// } +// +// } +//} diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataFilter.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataFilter.kt index 08a0ea87..bc9caaec 100644 --- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataFilter.kt +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataFilter.kt @@ -1,6 +1,9 @@ package hep.dataforge.data import hep.dataforge.meta.* +import hep.dataforge.meta.scheme.Scheme +import hep.dataforge.meta.scheme.SchemeSpec +import hep.dataforge.meta.scheme.string import hep.dataforge.names.toName diff --git a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt index acd19652..d0543fd0 100644 --- a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt +++ b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt @@ -1,12 +1,13 @@ package hep.dataforge.io.yaml import hep.dataforge.context.Context -import hep.dataforge.descriptors.NodeDescriptor +import hep.dataforge.meta.descriptors.NodeDescriptor import hep.dataforge.io.MetaFormat import hep.dataforge.io.MetaFormatFactory import hep.dataforge.meta.DFExperimental import hep.dataforge.meta.Meta import hep.dataforge.meta.toMap +import hep.dataforge.meta.scheme.toMeta import hep.dataforge.meta.toMeta import kotlinx.io.Input import kotlinx.io.Output diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/BinaryMetaFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/BinaryMetaFormat.kt index 1182bfbc..c86fe883 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/BinaryMetaFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/BinaryMetaFormat.kt @@ -1,7 +1,7 @@ package hep.dataforge.io import hep.dataforge.context.Context -import hep.dataforge.descriptors.NodeDescriptor +import hep.dataforge.meta.descriptors.NodeDescriptor import hep.dataforge.meta.* import hep.dataforge.values.* import kotlinx.io.* diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt index d27b0a97..8ec244a5 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt @@ -3,9 +3,9 @@ package hep.dataforge.io import hep.dataforge.context.Context -import hep.dataforge.descriptors.ItemDescriptor -import hep.dataforge.descriptors.NodeDescriptor -import hep.dataforge.descriptors.ValueDescriptor +import hep.dataforge.meta.descriptors.ItemDescriptor +import hep.dataforge.meta.descriptors.NodeDescriptor +import hep.dataforge.meta.descriptors.ValueDescriptor import hep.dataforge.meta.Meta import hep.dataforge.meta.MetaBase import hep.dataforge.meta.MetaItem diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/MetaFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/MetaFormat.kt index d22359c8..4776e015 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/MetaFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/MetaFormat.kt @@ -1,7 +1,7 @@ package hep.dataforge.io import hep.dataforge.context.Context -import hep.dataforge.descriptors.NodeDescriptor +import hep.dataforge.meta.descriptors.NodeDescriptor import hep.dataforge.io.MetaFormatFactory.Companion.META_FORMAT_TYPE import hep.dataforge.meta.Meta import hep.dataforge.names.Name diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/functions/RemoteFunctionClient.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/functions/RemoteFunctionClient.kt index 7c294891..1df4b42d 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/functions/RemoteFunctionClient.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/functions/RemoteFunctionClient.kt @@ -6,6 +6,7 @@ import hep.dataforge.io.* import hep.dataforge.meta.Meta import hep.dataforge.meta.get import hep.dataforge.meta.int +import hep.dataforge.meta.scheme.int import kotlin.reflect.KClass class RemoteFunctionClient(override val context: Context, val responder: Responder) : FunctionServer, ContextAware { diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/functions/RemoteFunctionServer.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/functions/RemoteFunctionServer.kt index 8252b1d3..efd4351e 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/functions/RemoteFunctionServer.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/functions/RemoteFunctionServer.kt @@ -8,6 +8,7 @@ import hep.dataforge.io.Responder import hep.dataforge.io.type import hep.dataforge.meta.get import hep.dataforge.meta.int +import hep.dataforge.meta.scheme.int class RemoteFunctionServer( override val context: Context, diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt index a1ac33a5..ef989869 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt @@ -18,7 +18,7 @@ object ValueSerializer : KSerializer<Value> { private val valueTypeSerializer = EnumSerializer(ValueType::class) private val listSerializer by lazy { ArrayListSerializer(ValueSerializer) } - override val descriptor: SerialDescriptor = descriptor("hep.dataforge.values.Value") { + override val descriptor: SerialDescriptor = descriptor("Value") { boolean("isList") enum<ValueType>("valueType") element("value", null) diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt index 0f60c077..04d097aa 100644 --- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt +++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MultipartTest.kt @@ -3,6 +3,7 @@ package hep.dataforge.io import hep.dataforge.meta.DFExperimental import hep.dataforge.meta.get import hep.dataforge.meta.int +import hep.dataforge.meta.scheme.int import kotlinx.io.text.writeRawString import kotlinx.io.text.writeUtf8String diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt index fb693057..1fe9d59d 100644 --- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt +++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt @@ -1,6 +1,6 @@ package hep.dataforge.io -import hep.dataforge.descriptors.NodeDescriptor +import hep.dataforge.meta.descriptors.NodeDescriptor import hep.dataforge.meta.DFExperimental import hep.dataforge.meta.EmptyMeta import hep.dataforge.meta.Meta diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/annotations.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/annotations.kt deleted file mode 100644 index d65a0e79..00000000 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/annotations.kt +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright 2018 Alexander Nozik. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package hep.dataforge.descriptors - -import hep.dataforge.values.ValueType -import kotlin.reflect.KClass - -@Target(AnnotationTarget.PROPERTY) -@MustBeDocumented -annotation class ValueDef( - val key: String, - val type: Array<ValueType> = arrayOf(ValueType.STRING), - val multiple: Boolean = false, - val def: String = "", - val info: String = "", - val required: Boolean = true, - val allowed: Array<String> = emptyArray(), - val enumeration: KClass<*> = Any::class, - val tags: Array<String> = emptyArray() -) - -@MustBeDocumented -annotation class NodeDef( - val key: String, - val info: String = "", - val multiple: Boolean = false, - val required: Boolean = false, - val tags: Array<String> = emptyArray(), - /** - * A list of child value descriptors - */ - val values: Array<ValueDef> = emptyArray(), - /** - * A target class for this node to describe - * @return - */ - val type: KClass<*> = Any::class, - /** - * The DataForge path to the resource containing the description. Following targets are supported: - * - * 1. resource - * 1. file - * 1. class - * 1. method - * 1. property - * - * - * Does not work if [type] is provided - * - * @return - */ - val descriptor: String = "" -) - -/** - * Description text for meta property, node or whole object - */ -@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY) -@Retention(AnnotationRetention.RUNTIME) -@MustBeDocumented -annotation class Description(val value: String) - -/** - * Annotation for value property which states that lists are expected - */ -@Target(AnnotationTarget.PROPERTY) -@Retention(AnnotationRetention.RUNTIME) -@MustBeDocumented -annotation class Multiple - -/** - * Descriptor target - * The DataForge path to the resource containing the description. Following targets are supported: - * 1. resource - * 1. file - * 1. class - * 1. method - * 1. property - * - * - * Does not work if [type] is provided - */ -@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER) -@Retention(AnnotationRetention.RUNTIME) -@MustBeDocumented -annotation class Descriptor(val value: String) - - -/** - * Aggregator class for descriptor nodes - */ -@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER) -@Retention(AnnotationRetention.RUNTIME) -@MustBeDocumented -annotation class DescriptorNodes(vararg val nodes: NodeDef) - -/** - * Aggregator class for descriptor values - */ -@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER) -@Retention(AnnotationRetention.RUNTIME) -@MustBeDocumented -annotation class DescriptorValues(vararg val nodes: ValueDef) - -/** - * Alternative name for property descriptor declaration - */ -@Target(AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER) -@Retention(AnnotationRetention.RUNTIME) -@MustBeDocumented -annotation class DescriptorName(val name: String) - -@Target(AnnotationTarget.PROPERTY) -@Retention(AnnotationRetention.RUNTIME) -@MustBeDocumented -annotation class DescriptorValue(val def: ValueDef) -//TODO enter fields directly? - -@Target(AnnotationTarget.PROPERTY) -@Retention(AnnotationRetention.RUNTIME) -@MustBeDocumented -annotation class ValueProperty( - val name: String = "", - val type: Array<ValueType> = arrayOf(ValueType.STRING), - val multiple: Boolean = false, - val def: String = "", - val enumeration: KClass<*> = Any::class, - val tags: Array<String> = emptyArray() -) - - -@Target(AnnotationTarget.PROPERTY) -@Retention(AnnotationRetention.RUNTIME) -@MustBeDocumented -annotation class NodeProperty(val name: String = "") diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt index 087cb077..833117b5 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt @@ -12,10 +12,15 @@ data class MetaListener( val action: (name: Name, oldItem: MetaItem<*>?, newItem: MetaItem<*>?) -> Unit ) +interface ObservableMeta : Meta { + fun onChange(owner: Any?, action: (Name, MetaItem<*>?, MetaItem<*>?) -> Unit) + fun removeListener(owner: Any?) +} + /** * Mutable meta representing object state */ -class Config : AbstractMutableMeta<Config>() { +class Config : AbstractMutableMeta<Config>(), ObservableMeta { private val listeners = HashSet<MetaListener>() @@ -26,14 +31,14 @@ class Config : AbstractMutableMeta<Config>() { /** * Add change listener to this meta. Owner is declared to be able to remove listeners later. Listener without owner could not be removed */ - fun onChange(owner: Any?, action: (Name, MetaItem<*>?, MetaItem<*>?) -> Unit) { + override fun onChange(owner: Any?, action: (Name, MetaItem<*>?, MetaItem<*>?) -> Unit) { listeners.add(MetaListener(owner, action)) } /** * Remove all listeners belonging to given owner */ - fun removeListener(owner: Any?) { + override fun removeListener(owner: Any?) { listeners.removeAll { it.owner === owner } } 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 950df7d6..88819f2c 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt @@ -1,5 +1,6 @@ package hep.dataforge.meta +import hep.dataforge.meta.scheme.Configurable import hep.dataforge.names.* import hep.dataforge.values.Value @@ -160,7 +161,7 @@ operator fun MutableMeta<*>.set(name: String, metas: Iterable<Meta>): Unit = set /** * Append the node with a same-name-sibling, automatically generating numerical index */ -fun MutableMeta<*>.append(name: Name, value: Any?) { +fun <M: MutableMeta<M>> M.append(name: Name, value: Any?) { require(!name.isEmpty()) { "Name could not be empty for append operation" } val newIndex = name.last()!!.index if (newIndex.isNotEmpty()) { @@ -171,4 +172,4 @@ fun MutableMeta<*>.append(name: Name, value: Any?) { } } -fun MutableMeta<*>.append(name: String, value: Any?) = append(name.toName(), value) \ No newline at end of file +fun <M: MutableMeta<M>> M.append(name: String, value: Any?) = append(name.toName(), value) \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/Described.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/Described.kt similarity index 92% rename from dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/Described.kt rename to dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/Described.kt index 4bf6c9dd..0a09a0fb 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/Described.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/Described.kt @@ -1,4 +1,4 @@ -package hep.dataforge.descriptors +package hep.dataforge.meta.descriptors /** * An object which provides its descriptor diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/DescriptorMeta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/DescriptorMeta.kt similarity index 95% rename from dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/DescriptorMeta.kt rename to dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/DescriptorMeta.kt index 49aef28e..f95069d6 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/DescriptorMeta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/DescriptorMeta.kt @@ -1,4 +1,4 @@ -package hep.dataforge.descriptors +package hep.dataforge.meta.descriptors import hep.dataforge.meta.MetaBase import hep.dataforge.meta.MetaItem diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/ItemDescriptor.kt similarity index 85% rename from dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt rename to dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/ItemDescriptor.kt index 514349fe..9c5cf15f 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/descriptors/ItemDescriptor.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/ItemDescriptor.kt @@ -1,6 +1,7 @@ -package hep.dataforge.descriptors +package hep.dataforge.meta.descriptors import hep.dataforge.meta.* +import hep.dataforge.meta.scheme.* import hep.dataforge.names.Name import hep.dataforge.names.NameToken import hep.dataforge.names.asName @@ -41,6 +42,25 @@ sealed class ItemDescriptor : Scheme() { abstract var required: Boolean } +/** + * Configure attributes of the descriptor + */ +fun ItemDescriptor.attributes(block: Config.() -> Unit) { + (attributes ?: Config().also { this.config = it }).apply(block) +} + +/** + * Check if given item suits the descriptor + */ +fun ItemDescriptor.validateItem(item: MetaItem<*>?): Boolean { + return when (this) { + is ValueDescriptor -> isAllowedValue(item.value ?: return false) + is NodeDescriptor -> items.all { (key, d) -> + d.validateItem(item.node[key]) + } + } +} + /** * Descriptor for meta node. Could contain additional information for viewing * and editing. @@ -61,7 +81,7 @@ class NodeDescriptor : ItemDescriptor() { * * @return */ - var default: Config? by config() + var default by node() /** * The map of children node descriptors @@ -71,15 +91,21 @@ class NodeDescriptor : ItemDescriptor() { name to wrap(node.node ?: error("Node descriptor must be a node")) } - - fun node(name: String, descriptor: NodeDescriptor) { + /** + * Define a child item descriptor for this node + */ + fun defineItem(name: String, descriptor: ItemDescriptor) { if (items.keys.contains(name)) error("The key $name already exists in descriptor") - val token = NameToken(NODE_KEY, name) + val token = when (descriptor) { + is NodeDescriptor -> NameToken(NODE_KEY, name) + is ValueDescriptor -> NameToken(VALUE_KEY, name) + } config[token] = descriptor.config + } - fun node(name: String, block: NodeDescriptor.() -> Unit) { + fun defineNode(name: String, block: NodeDescriptor.() -> Unit) { val token = NameToken(NODE_KEY, name) if (config[token] == null) { config[token] = NodeDescriptor(block) @@ -100,7 +126,7 @@ class NodeDescriptor : ItemDescriptor() { } } - fun node(name: Name, block: NodeDescriptor.() -> Unit) { + fun defineNode(name: Name, block: NodeDescriptor.() -> Unit) { buildNode(name).apply(block) } @@ -112,28 +138,23 @@ class NodeDescriptor : ItemDescriptor() { name to ValueDescriptor.wrap(node.node ?: error("Value descriptor must be a node")) } - fun value(name: String, descriptor: ValueDescriptor) { - if (items.keys.contains(name)) error("The key $name already exists in descriptor") - val token = NameToken(VALUE_KEY, name) - config[token] = descriptor.config - } /** * Add a value descriptor using block for */ - fun value(name: String, block: ValueDescriptor.() -> Unit) { - value(name, ValueDescriptor(block)) + fun defineValue(name: String, block: ValueDescriptor.() -> Unit) { + defineItem(name, ValueDescriptor(block)) } - fun value(name: Name, block: ValueDescriptor.() -> Unit) { + fun defineValue(name: Name, block: ValueDescriptor.() -> Unit) { require(name.length >= 1) { "Name length for value descriptor must be non-empty" } - buildNode(name.cutLast()).value(name.last().toString(), block) + buildNode(name.cutLast()).defineValue(name.last().toString(), block) } val items: Map<String, ItemDescriptor> get() = nodes + values - //override val descriptor: NodeDescriptor = empty("descriptor") +//override val descriptor: NodeDescriptor = empty("descriptor") companion object : SchemeSpec<NodeDescriptor>(::NodeDescriptor) { diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/mapMeta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/mapMeta.kt index ab68212c..ddf049e3 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/mapMeta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/mapMeta.kt @@ -1,6 +1,6 @@ package hep.dataforge.meta -import hep.dataforge.descriptors.NodeDescriptor +import hep.dataforge.meta.descriptors.NodeDescriptor import hep.dataforge.values.Value /** diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Configurable.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Configurable.kt similarity index 71% rename from dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Configurable.kt rename to dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Configurable.kt index e95b5564..42aaeb07 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Configurable.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Configurable.kt @@ -1,9 +1,7 @@ -package hep.dataforge.meta +package hep.dataforge.meta.scheme -import hep.dataforge.descriptors.Described -import hep.dataforge.descriptors.NodeDescriptor -import hep.dataforge.descriptors.defaultItem -import hep.dataforge.descriptors.get +import hep.dataforge.meta.* +import hep.dataforge.meta.descriptors.* import hep.dataforge.names.Name import hep.dataforge.names.toName @@ -24,6 +22,14 @@ interface Configurable : Described { */ fun getDefaultItem(name: Name): MetaItem<*>? = null + /** + * Check if property with given [name] could be assigned to [value] + */ + fun validateItem(name: Name, item: MetaItem<*>?): Boolean { + val descriptor = descriptor?.get(name) + return descriptor?.validateItem(item) ?: true + } + override val descriptor: NodeDescriptor? get() = null } @@ -39,7 +45,11 @@ fun Configurable.getProperty(key: String) = getProperty(key.toName()) * Set a configurable property */ fun Configurable.setProperty(name: Name, item: MetaItem<*>?) { - config[name] = item + if(validateItem(name,item)) { + config[name] = item + } else { + error("Validation failed for property $name with value $item") + } } fun Configurable.setProperty(key: String, item: MetaItem<*>?) { diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/ConfigurableDelegate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt similarity index 88% rename from dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/ConfigurableDelegate.kt rename to dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt index 31cc2c9c..9b86abba 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/ConfigurableDelegate.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt @@ -1,5 +1,6 @@ -package hep.dataforge.meta +package hep.dataforge.meta.scheme +import hep.dataforge.meta.* import hep.dataforge.names.Name import hep.dataforge.names.asName import hep.dataforge.values.* @@ -41,23 +42,35 @@ class LazyConfigurableDelegate( /** * A property delegate that uses custom key */ -fun Configurable.item(default: Any?, key: Name? = null): ConfigurableDelegate = - ConfigurableDelegate(this, key, default?.let { MetaItem.of(it) }) +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) } } + LazyConfigurableDelegate(this, key) { + default()?.let { + MetaItem.of(it) + } + } fun <T> Configurable.item( default: T? = null, key: Name? = null, - writer: (T) -> MetaItem<*>? = { MetaItem.of(it) }, + writer: (T) -> MetaItem<*>? = { + MetaItem.of(it) + }, reader: (MetaItem<*>?) -> T ): ReadWriteProperty<Any?, T> = - ConfigurableDelegate(this, key, default?.let { MetaItem.of(it) }).map(reader = reader, writer = writer) + ConfigurableDelegate( + this, + key, + default?.let { MetaItem.of(it) }).map(reader = reader, writer = writer) fun Configurable.value(default: Any? = null, key: Name? = null): ReadWriteProperty<Any?, Value?> = item(default, key).transform { it.value } @@ -68,9 +81,13 @@ fun <T> Configurable.value( writer: (T) -> Value? = { Value.of(it) }, reader: (Value?) -> T ): ReadWriteProperty<Any?, T> = - ConfigurableDelegate(this, key, default?.let { MetaItem.of(it) }).map( + ConfigurableDelegate( + this, + key, + default?.let { MetaItem.of(it) } + ).map( reader = { reader(it.value) }, - writer = { writer(it)?.let { MetaItem.ValueItem(it) } } + writer = { value -> writer(value)?.let { MetaItem.ValueItem(it) } } ) fun Configurable.string(default: String? = null, key: Name? = null): ReadWriteProperty<Any?, String?> = @@ -184,6 +201,10 @@ fun Configurable.doubleArray(vararg doubles: Double, key: Name? = null): ReadWri fun Configurable.config(key: Name? = null): ReadWriteProperty<Any?, Config?> = config.node(key) +fun Configurable.node(key: Name? = null): ReadWriteProperty<Any?, Meta?> = item().map( + reader = { it.node }, + writer = { it?.let { MetaItem.NodeItem(it) } } +) fun <T : Configurable> Configurable.spec(spec: Specification<T>, key: Name? = null): ReadWriteProperty<Any?, T?> = object : ReadWriteProperty<Any?, T?> { diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Scheme.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt similarity index 81% rename from dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Scheme.kt rename to dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt index 84b350f1..c730eed2 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Scheme.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt @@ -1,6 +1,7 @@ -package hep.dataforge.meta +package hep.dataforge.meta.scheme -import hep.dataforge.descriptors.* +import hep.dataforge.meta.* +import hep.dataforge.meta.descriptors.* import hep.dataforge.names.Name import hep.dataforge.names.NameToken import hep.dataforge.names.plus @@ -8,7 +9,7 @@ import hep.dataforge.names.plus /** * A base for delegate-based or descriptor-based scheme. [Scheme] has an empty constructor to simplify usage from [Specification]. */ -open class Scheme() : Configurable, Described { +open class Scheme() : Configurable, Described, MetaRepr { constructor(config: Config, defaultProvider: (Name) -> MetaItem<*>?) : this() { this.config = config this.defaultProvider = defaultProvider @@ -17,7 +18,8 @@ open class Scheme() : Configurable, Described { //constructor(config: Config, default: Meta) : this(config, { default[it] }) constructor(config: Config) : this(config, { null }) - final override var config: Config = Config() + final override var config: Config = + Config() internal set lateinit var defaultProvider: (Name) -> MetaItem<*>? @@ -37,6 +39,8 @@ open class Scheme() : Configurable, Described { */ open val defaultLayer: Meta get() = DefaultLayer(Name.EMPTY) + override fun toMeta(): Meta = config.seal() + private inner class DefaultLayer(val path: Name) : MetaBase() { override val items: Map<NameToken, MetaItem<*>> = (descriptor?.get(path) as? NodeDescriptor)?.items?.entries?.associate { (key, descriptor) -> @@ -55,7 +59,8 @@ open class Scheme() : Configurable, Described { /** * A specification for simplified generation of wrappers */ -open class SchemeSpec<T : Scheme>(val builder: () -> T) : Specification<T> { +open class SchemeSpec<T : Scheme>(val builder: () -> T) : + Specification<T> { override fun wrap(config: Config, defaultProvider: (Name) -> MetaItem<*>?): T { return builder().apply { this.config = config @@ -75,14 +80,18 @@ open class MetaScheme( init { this.descriptor = descriptor } - override val defaultLayer: Meta get() = Laminate(meta, descriptor?.defaultItem().node) + + override val defaultLayer: Meta + get() = Laminate(meta, descriptor?.defaultItem().node) } -fun Meta.asScheme() = MetaScheme(this) +fun Meta.asScheme() = + MetaScheme(this) fun <T : Configurable> Meta.toScheme(spec: Specification<T>, block: T.() -> Unit) = spec.wrap(this).apply(block) /** * Create a snapshot laminate */ -fun Scheme.toMeta(): Laminate = Laminate(config, defaultLayer) \ No newline at end of file +fun Scheme.toMeta(): Laminate = + Laminate(config, defaultLayer) \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specification.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Specification.kt similarity index 90% rename from dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specification.kt rename to dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Specification.kt index 9c8b740a..4aa7bf02 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Specification.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Specification.kt @@ -1,5 +1,6 @@ -package hep.dataforge.meta +package hep.dataforge.meta.scheme +import hep.dataforge.meta.* import hep.dataforge.names.Name import kotlin.jvm.JvmName @@ -33,7 +34,9 @@ interface Specification<T : Configurable> { /** * Wrap a configuration using static meta as default */ - fun wrap(default: Meta): T = wrap(Config()){default[it]} + fun wrap(default: Meta): T = wrap( + Config() + ){default[it]} } /** @@ -54,7 +57,8 @@ fun <C : Configurable, S : Specification<C>> Configurable.update(spec: S, action fun <C : Configurable, S : Specification<C>> S.createStyle(action: C.() -> Unit): Meta = Config().also { update(it, action) } -fun <T : Configurable> MetaItem<*>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(Config(), it) } +fun <T : Configurable> MetaItem<*>.spec(spec: Specification<T>): T? = node?.let { spec.wrap( + Config(), it) } @JvmName("configSpec") fun <T : Configurable> MetaItem<Config>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(it) } diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaTransformation.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaTransformation.kt similarity index 83% rename from dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaTransformation.kt rename to dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaTransformation.kt index 8deada19..4dad4466 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaTransformation.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaTransformation.kt @@ -1,5 +1,6 @@ -package hep.dataforge.meta +package hep.dataforge.meta.transformations +import hep.dataforge.meta.* import hep.dataforge.names.Name /** @@ -8,7 +9,7 @@ import hep.dataforge.names.Name interface TransformationRule { /** - * Check if this transformation + * Check if this transformation should be applied to a node with given name and value */ fun matches(name: Name, item: MetaItem<*>?): Boolean @@ -29,7 +30,8 @@ interface TransformationRule { /** * A transformation which keeps all elements, matching [selector] unchanged. */ -data class KeepTransformationRule(val selector: (Name) -> Boolean) : TransformationRule { +data class KeepTransformationRule(val selector: (Name) -> Boolean) : + TransformationRule { override fun matches(name: Name, item: MetaItem<*>?): Boolean { return selector(name) } @@ -87,25 +89,27 @@ inline class MetaTransformation(val transformations: Collection<TransformationRu /** * Produce new meta using only those items that match transformation rules */ - fun transform(source: Meta): Meta = buildMeta { - transformations.forEach { rule -> - rule.selectItems(source).forEach { name -> - rule.transformItem(name, source[name], this) + fun transform(source: Meta): Meta = + buildMeta { + transformations.forEach { rule -> + rule.selectItems(source).forEach { name -> + rule.transformItem(name, source[name], this) + } } } - } /** * Transform a meta, replacing all elements found in rules with transformed entries */ - fun apply(source: Meta): Meta = buildMeta(source) { - transformations.forEach { rule -> - rule.selectItems(source).forEach { name -> - remove(name) - rule.transformItem(name, source[name], this) + fun apply(source: Meta): Meta = + buildMeta(source) { + transformations.forEach { rule -> + rule.selectItems(source).forEach { name -> + remove(name) + rule.transformItem(name, source[name], this) + } } } - } /** * Listens for changes in the source node and translates them into second node if transformation set contains a corresponding rule. @@ -150,9 +154,10 @@ class MetaTransformationBuilder { * Keep nodes by regex */ fun keep(regex: String) { - transformations.add(RegexItemTransformationRule(regex.toRegex()) { name, _, metaItem -> - setItem(name, metaItem) - }) + transformations.add( + RegexItemTransformationRule(regex.toRegex()) { name, _, metaItem -> + setItem(name, metaItem) + }) } /** diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt index 93aad8e5..2461c363 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt @@ -1,5 +1,6 @@ package hep.dataforge.meta +import hep.dataforge.meta.scheme.* import kotlin.test.Test import kotlin.test.assertEquals 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 0246fb10..09a3e03c 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt @@ -1,5 +1,8 @@ package hep.dataforge.meta +import hep.dataforge.meta.scheme.asScheme +import hep.dataforge.meta.scheme.getProperty +import hep.dataforge.meta.scheme.toMeta import kotlin.test.Test import kotlin.test.assertEquals diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SpecificationTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SpecificationTest.kt index f21c5b2c..6d99101a 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SpecificationTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SpecificationTest.kt @@ -1,5 +1,8 @@ package hep.dataforge.meta +import hep.dataforge.meta.scheme.Scheme +import hep.dataforge.meta.scheme.Specification +import hep.dataforge.meta.scheme.numberList import hep.dataforge.names.Name import kotlin.test.Test import kotlin.test.assertEquals diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/descriptors/DescriptorTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/descriptors/DescriptorTest.kt similarity index 77% rename from dataforge-meta/src/commonTest/kotlin/hep/dataforge/descriptors/DescriptorTest.kt rename to dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/descriptors/DescriptorTest.kt index 81d25285..1fa07382 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/descriptors/DescriptorTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/descriptors/DescriptorTest.kt @@ -1,4 +1,4 @@ -package hep.dataforge.descriptors +package hep.dataforge.meta.descriptors import hep.dataforge.values.ValueType import kotlin.test.Test @@ -7,14 +7,14 @@ import kotlin.test.assertEquals class DescriptorTest { val descriptor = NodeDescriptor { - node("aNode") { + defineNode("aNode") { info = "A root demo node" - value("b") { + defineValue("b") { info = "b number value" type(ValueType.NUMBER) } - node("otherNode") { - value("otherValue") { + defineNode("otherNode") { + defineValue("otherValue") { type(ValueType.BOOLEAN) default(false) info = "default value" diff --git a/dataforge-output-html/build.gradle.kts b/dataforge-output/dataforge-output-html/build.gradle.kts similarity index 100% rename from dataforge-output-html/build.gradle.kts rename to dataforge-output/dataforge-output-html/build.gradle.kts diff --git a/dataforge-output-html/src/commonMain/kotlin/hep/dataforge/output/html/HtmlRenderer.kt b/dataforge-output/dataforge-output-html/src/commonMain/kotlin/hep/dataforge/output/html/HtmlRenderer.kt similarity index 100% rename from dataforge-output-html/src/commonMain/kotlin/hep/dataforge/output/html/HtmlRenderer.kt rename to dataforge-output/dataforge-output-html/src/commonMain/kotlin/hep/dataforge/output/html/HtmlRenderer.kt diff --git a/dataforge-scripting/src/jvmTest/kotlin/hep/dataforge/scripting/BuildersKtTest.kt b/dataforge-scripting/src/jvmTest/kotlin/hep/dataforge/scripting/BuildersKtTest.kt index 6dd61105..9fb1c919 100644 --- a/dataforge-scripting/src/jvmTest/kotlin/hep/dataforge/scripting/BuildersKtTest.kt +++ b/dataforge-scripting/src/jvmTest/kotlin/hep/dataforge/scripting/BuildersKtTest.kt @@ -3,6 +3,7 @@ package hep.dataforge.scripting import hep.dataforge.context.Global import hep.dataforge.meta.get import hep.dataforge.meta.int +import hep.dataforge.meta.scheme.int import hep.dataforge.workspace.SimpleWorkspaceBuilder import hep.dataforge.workspace.context import hep.dataforge.workspace.target diff --git a/dataforge-tables/build.gradle b/dataforge-tables/build.gradle deleted file mode 100644 index a0011494..00000000 --- a/dataforge-tables/build.gradle +++ /dev/null @@ -1,25 +0,0 @@ -plugins { - id "org.jetbrains.kotlin.multiplatform" -} - -repositories { - jcenter() -} - -kotlin { - targets { - fromPreset(presets.jvm, 'jvm') - //fromPreset(presets.js, 'js') - // For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64 - // For Linux, preset should be changed to e.g. presets.linuxX64 - // For MacOS, preset should be changed to e.g. presets.macosX64 - //fromPreset(presets.iosX64, 'ios') - } - sourceSets { - commonMain { - dependencies { - api project(":dataforge-context") - } - } - } -} \ No newline at end of file diff --git a/dataforge-tables/build.gradle.kts b/dataforge-tables/build.gradle.kts new file mode 100644 index 00000000..6c0a1e53 --- /dev/null +++ b/dataforge-tables/build.gradle.kts @@ -0,0 +1,14 @@ +plugins { + id("scientifik.mpp") +} + +kotlin { + sourceSets { + val commonMain by getting{ + dependencies { + api(project(":dataforge-context")) + api(project(":dataforge-io")) + } + } + } +} \ No newline at end of file diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnScheme.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnScheme.kt new file mode 100644 index 00000000..aa0d2aec --- /dev/null +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnScheme.kt @@ -0,0 +1,8 @@ +package hep.dataforge.tables + +import hep.dataforge.meta.scheme.Scheme +import hep.dataforge.meta.scheme.SchemeSpec + +class ColumnScheme : Scheme() { + companion object : SchemeSpec<ColumnScheme>(::ColumnScheme) +} \ No newline at end of file diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt new file mode 100644 index 00000000..ce7b7dae --- /dev/null +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt @@ -0,0 +1,35 @@ +package hep.dataforge.tables + +import kotlin.reflect.KClass + +class ColumnTable(override val columns: Map<String, Column<*>>) : + Table { + private val rowsNum = columns.values.first().size + + init { + require(columns.values.all { it.size == rowsNum }) { "All columns must be of the same size" } + } + + override val rows: List<Row> + get() = (0 until rowsNum).map { VirtualRow(it) } + + @Suppress("UNCHECKED_CAST") + override fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? { + val value = columns[column]?.get(row) + return when { + value == null -> null + type.isInstance(value) -> value as T + else -> error("Expected type is $type, but found ${value::class}") + } + } + + private inner class VirtualRow(val index: Int) : Row { + override fun <T : Any> getValue(column: String, type: KClass<out T>): T? = getValue(index, column, type) + } +} + +class ColumnTableBuilder { + private val columns = ArrayList<Column<*>>() + + fun build() = ColumnTable(columns.associateBy { it.name }) +} \ No newline at end of file diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ListColumn.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ListColumn.kt new file mode 100644 index 00000000..a926a2ad --- /dev/null +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ListColumn.kt @@ -0,0 +1,28 @@ +package hep.dataforge.tables + +import hep.dataforge.meta.Meta +import kotlin.reflect.KClass + +class ListColumn<T : Any>( + override val name: String, + private val data: List<T?>, + override val type: KClass<out T>, + override val meta: Meta +) : Column<T> { + override val size: Int get() = data.size + + override fun get(index: Int): T? = data[index] + + companion object { + inline operator fun <reified T : Any> invoke( + name: String, + data: List<T>, + noinline metaBuilder: ColumnScheme.() -> Unit + ): ListColumn<T> = ListColumn(name, data, T::class, ColumnScheme(metaBuilder).toMeta()) + } +} + +inline fun <T : Any, reified R : Any> Column<T>.map(meta: Meta = this.meta, noinline block: (T?) -> R): Column<R> { + val data = List(size) { block(get(it)) } + return ListColumn(name, data, R::class, meta) +} \ No newline at end of file diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt new file mode 100644 index 00000000..0a78ca9e --- /dev/null +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt @@ -0,0 +1,19 @@ +package hep.dataforge.tables + +import kotlin.reflect.KClass + +class MapRow(val values: Map<String, Any>) : Row { + @Suppress("UNCHECKED_CAST") + override fun <T : Any> getValue(column: String, type: KClass<out T>): T? { + val value = values[column] + return when { + value == null -> null + type.isInstance(value) -> { + value as T? + } + else -> { + error("Expected type is $type, but found ${value::class}") + } + } + } +} \ No newline at end of file diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/NumberColumn.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/NumberColumn.kt new file mode 100644 index 00000000..1bd8d6a3 --- /dev/null +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/NumberColumn.kt @@ -0,0 +1,94 @@ +package hep.dataforge.tables + +import hep.dataforge.meta.Meta +import kotlin.reflect.KClass + +//interface NumberColumn<N : Number> : Column<N> + +data class RealColumn( + override val name: String, + val data: DoubleArray, + override val meta: Meta = Meta.EMPTY +) : Column<Double> { + override val type: KClass<out Double> get() = Double::class + + override val size: Int get() = data.size + + @Suppress("OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") + override inline fun get(index: Int): Double = data[index] + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is RealColumn) return false + + if (name != other.name) return false + if (!data.contentEquals(other.data)) return false + if (meta != other.meta) return false + + return true + } + + override fun hashCode(): Int { + var result = name.hashCode() + result = 31 * result + data.contentHashCode() + result = 31 * result + meta.hashCode() + return result + } + + companion object { + inline operator fun <reified T : Any> invoke( + name: String, + data: DoubleArray, + noinline metaBuilder: ColumnScheme.() -> Unit + ): RealColumn = RealColumn(name, data, ColumnScheme(metaBuilder).toMeta()) + } +} + +fun <T : Any> Column<T>.map(meta: Meta = this.meta, block: (T?) -> Double): RealColumn { + val data = DoubleArray(size) { block(get(it)) } + return RealColumn(name, data, meta) +} + +data class IntColumn( + override val name: String, + val data: IntArray, + override val meta: Meta = Meta.EMPTY +) : Column<Int> { + override val type: KClass<out Int> get() = Int::class + + override val size: Int get() = data.size + + @Suppress("OVERRIDE_BY_INLINE", "NOTHING_TO_INLINE") + override inline fun get(index: Int): Int = data[index] + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is IntColumn) return false + + if (name != other.name) return false + if (!data.contentEquals(other.data)) return false + if (meta != other.meta) return false + + return true + } + + override fun hashCode(): Int { + var result = name.hashCode() + result = 31 * result + data.contentHashCode() + result = 31 * result + meta.hashCode() + return result + } + + companion object { + inline operator fun <reified T : Any> invoke( + name: String, + data: IntArray, + noinline metaBuilder: ColumnScheme.() -> Unit + ): IntColumn = IntColumn(name, data, ColumnScheme(metaBuilder).toMeta()) + } +} + +fun <T : Any> Column<T>.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-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt new file mode 100644 index 00000000..fbcec0b7 --- /dev/null +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt @@ -0,0 +1,26 @@ +package hep.dataforge.tables + +import hep.dataforge.meta.Meta +import kotlin.reflect.KClass + +data class ColumnDef<T : Any>(val name: String, val type: KClass<T>, val meta: Meta) + +class RowTable<R : Row>(override val rows: List<R>, private val columnDefs: List<ColumnDef<*>>) : Table { + override fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? = + rows[row].getValue(column, type) + + override val columns: Map<String, Column<*>> + get() = columnDefs.associate { it.name to VirtualColumn(it) } + + private inner class VirtualColumn<T : Any>(val def: ColumnDef<T>) : + Column<T> { + + override val name: String get() = def.name + override val type: KClass<out T> get() = def.type + override val meta: Meta get() = def.meta + override val size: Int get() = rows.size + + override fun get(index: Int): T? = rows[index].getValue(name, type) + } +} + diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt new file mode 100644 index 00000000..52171625 --- /dev/null +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt @@ -0,0 +1,30 @@ +package hep.dataforge.tables + +import hep.dataforge.meta.Meta +import kotlin.reflect.KClass + +interface Table { + fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? + val columns: Map<String, Column<*>> + val rows: List<Row> +} + +interface Column<out T : Any> { + val name: String + val type: KClass<out T> + val meta: Meta + val size: Int + operator fun get(index: Int): T? +} + +val Column<*>.indices get() = (0 until size) + +operator fun <T : Any> Column<T>.iterator() = iterator { + for (i in indices){ + yield(get(i)) + } +} + +interface Row { + fun <T : Any> getValue(column: String, type: KClass<out T>): T? +} \ No newline at end of file diff --git a/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/TableAccessor.kt b/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/TableAccessor.kt new file mode 100644 index 00000000..21453426 --- /dev/null +++ b/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/TableAccessor.kt @@ -0,0 +1,37 @@ +package hep.dataforge.tables + +import hep.dataforge.meta.Meta +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KClass +import kotlin.reflect.KProperty +import kotlin.reflect.full.cast +import kotlin.reflect.full.isSubclassOf + +class TableAccessor(val table: Table) : Table by table { + inline fun <reified T : Any> column() = ColumnProperty(table, T::class) +} + +@Suppress("UNCHECKED_CAST") +fun <T : Any> Column<*>.cast(type: KClass<T>): Column<T> { + return if (type.isSubclassOf(this.type)) { + this as Column<T> + } else { + ColumnWrapper(this, type) + } +} + +class ColumnWrapper<T : Any>(val column: Column<*>, override val type: KClass<T>) : Column<T> { + override val name: String get() = column.name + override val meta: Meta get() = column.meta + override val size: Int get() = column.size + + + override fun get(index: Int): T? = type.cast(column[index]) +} + +class ColumnProperty<T : Any>(val table: Table, val type: KClass<T>) : ReadOnlyProperty<Any?, Column<T>?> { + override fun getValue(thisRef: Any?, property: KProperty<*>): Column<T>? { + val name = property.name + return table.columns[name]?.cast(type) + } +} \ No newline at end of file diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/GenericTask.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/GenericTask.kt index ff499888..4e0ca715 100644 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/GenericTask.kt +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/GenericTask.kt @@ -1,7 +1,7 @@ package hep.dataforge.workspace import hep.dataforge.data.DataNode -import hep.dataforge.descriptors.NodeDescriptor +import hep.dataforge.meta.descriptors.NodeDescriptor import hep.dataforge.meta.Meta import hep.dataforge.meta.get import hep.dataforge.meta.node diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Task.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Task.kt index 0b11a7d5..7511bda9 100644 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Task.kt +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Task.kt @@ -2,7 +2,7 @@ package hep.dataforge.workspace import hep.dataforge.context.Named import hep.dataforge.data.DataNode -import hep.dataforge.descriptors.Described +import hep.dataforge.meta.descriptors.Described import hep.dataforge.meta.Meta import hep.dataforge.provider.Type import hep.dataforge.workspace.Task.Companion.TYPE diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt index 87736c01..7f359914 100644 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskBuilder.kt @@ -2,7 +2,7 @@ package hep.dataforge.workspace import hep.dataforge.context.Context import hep.dataforge.data.* -import hep.dataforge.descriptors.NodeDescriptor +import hep.dataforge.meta.descriptors.NodeDescriptor import hep.dataforge.meta.DFBuilder import hep.dataforge.meta.Meta import hep.dataforge.meta.get diff --git a/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/SimpleWorkspaceTest.kt b/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/SimpleWorkspaceTest.kt index a4df6a4b..8bd02c35 100644 --- a/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/SimpleWorkspaceTest.kt +++ b/dataforge-workspace/src/jvmTest/kotlin/hep/dataforge/workspace/SimpleWorkspaceTest.kt @@ -6,6 +6,7 @@ import hep.dataforge.meta.boolean import hep.dataforge.meta.builder import hep.dataforge.meta.get import hep.dataforge.meta.int +import hep.dataforge.meta.scheme.int import hep.dataforge.names.plus import kotlin.test.Test import kotlin.test.assertEquals diff --git a/settings.gradle.kts b/settings.gradle.kts index 42033ac1..e0d250bf 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -28,7 +28,8 @@ include( ":dataforge-context", ":dataforge-data", ":dataforge-output", - ":dataforge-output-html", + ":dataforge-output:dataforge-output-html", + ":dataforge-tables", ":dataforge-workspace", ":dataforge-scripting" ) From 819ab3ab20a8bd9fd8658b1ba92878416abac6e6 Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Wed, 29 Jan 2020 21:50:49 +0300 Subject: [PATCH 27/41] Fix scheme to meta transformation --- .../src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt index c730eed2..7ac49de0 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt @@ -39,7 +39,7 @@ open class Scheme() : Configurable, Described, MetaRepr { */ open val defaultLayer: Meta get() = DefaultLayer(Name.EMPTY) - override fun toMeta(): Meta = config.seal() + override fun toMeta(): Meta = Laminate(config, defaultLayer) private inner class DefaultLayer(val path: Name) : MetaBase() { override val items: Map<NameToken, MetaItem<*>> = From 061c570407ad8dd8c8b4dd8eca5618d896a71386 Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Sat, 8 Feb 2020 21:36:27 +0300 Subject: [PATCH 28/41] API for tables --- .../kotlin/hep/dataforge/tables/ColumnDef.kt | 10 ++++ .../hep/dataforge/tables/ColumnTable.kt | 30 ++++------ .../dataforge/tables/ColumnTableBuilder.kt | 58 +++++++++++++++++++ .../kotlin/hep/dataforge/tables/ListColumn.kt | 13 ++++- .../kotlin/hep/dataforge/tables/MapRow.kt | 11 +--- .../kotlin/hep/dataforge/tables/MetaQuery.kt | 11 ++++ .../kotlin/hep/dataforge/tables/RowTable.kt | 17 +++--- .../kotlin/hep/dataforge/tables/Table.kt | 45 +++++++++++--- .../kotlin/hep/dataforge/tables/CastColumn.kt | 36 ++++++++++++ .../hep/dataforge/tables/TableAccessor.kt | 37 ------------ 10 files changed, 182 insertions(+), 86 deletions(-) create mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnDef.kt create mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTableBuilder.kt create mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MetaQuery.kt create mode 100644 dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt delete mode 100644 dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/TableAccessor.kt diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnDef.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnDef.kt new file mode 100644 index 00000000..2df51d41 --- /dev/null +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnDef.kt @@ -0,0 +1,10 @@ +package hep.dataforge.tables + +import hep.dataforge.meta.Meta +import kotlin.reflect.KClass + +data class ColumnDef<out T : Any>( + override val name: String, + override val type: KClass<out T>, + override val meta: Meta +): ColumnHeader<T> \ No newline at end of file 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 ce7b7dae..07ea5d94 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt @@ -2,34 +2,26 @@ package hep.dataforge.tables import kotlin.reflect.KClass -class ColumnTable(override val columns: Map<String, Column<*>>) : - Table { - private val rowsNum = columns.values.first().size +/** + * @param C bottom type for all columns in the table + */ +class ColumnTable<C: Any>(override val columns: Collection<Column<C>>) : Table { + private val rowsNum = columns.first().size init { - require(columns.values.all { it.size == rowsNum }) { "All columns must be of the same size" } + require(columns.all { it.size == rowsNum }) { "All columns must be of the same size" } } override val rows: List<Row> - get() = (0 until rowsNum).map { VirtualRow(it) } + get() = (0 until rowsNum).map { VirtualRow(this, it) } - @Suppress("UNCHECKED_CAST") override fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? { val value = columns[column]?.get(row) - return when { - value == null -> null - type.isInstance(value) -> value as T - else -> error("Expected type is $type, but found ${value::class}") - } - } - - private inner class VirtualRow(val index: Int) : Row { - override fun <T : Any> getValue(column: String, type: KClass<out T>): T? = getValue(index, column, type) + return type.cast(value) } } -class ColumnTableBuilder { - private val columns = ArrayList<Column<*>>() +internal class VirtualRow(val table: Table, val index: Int) : Row { + override fun <T : Any> getValue(column: String, type: KClass<out T>): T? = table.getValue(index, column, type) +} - fun build() = ColumnTable(columns.associateBy { it.name }) -} \ No newline at end of file diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTableBuilder.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTableBuilder.kt new file mode 100644 index 00000000..b453c957 --- /dev/null +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTableBuilder.kt @@ -0,0 +1,58 @@ +package hep.dataforge.tables + +import hep.dataforge.meta.Meta +import kotlin.reflect.KClass + +class ColumnTableBuilder(val size: Int) : Table { + private val _columns = ArrayList<Column<*>>() + + override val columns: List<Column<*>> get() = _columns + override val rows: List<Row> get() = (0 until size).map { + VirtualRow(this, it) + } + + override fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? { + val value = columns[column]?.get(row) + return type.cast(value) + } + + /** + * Add a fixed column to the end of the table + */ + fun add(column: Column<*>) { + require(column.size == this.size) { "Required column size $size, but found ${column.size}" } + _columns.add(column) + } + + /** + * Insert a column at [index] + */ + fun insert(index: Int, column: Column<*>) { + require(column.size == this.size) { "Required column size $size, but found ${column.size}" } + _columns.add(index, column) + } +} + +class MapColumn<T : Any, R : Any>( + val source: Column<T>, + override val type: KClass<out R>, + override val name: String, + override val meta: Meta = source.meta, + val mapper: (T?) -> R? +) : Column<R> { + override val size: Int get() = source.size + + override fun get(index: Int): R? = mapper(source[index]) +} + +class CachedMapColumn<T : Any, R : Any>( + val source: Column<T>, + override val type: KClass<out R>, + override val name: String, + override val meta: Meta = source.meta, + val mapper: (T?) -> R? +) : Column<R> { + override val size: Int get() = source.size + private val values: HashMap<Int, R?> = 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/ListColumn.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ListColumn.kt index a926a2ad..fc7f03ea 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ListColumn.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ListColumn.kt @@ -16,9 +16,16 @@ class ListColumn<T : Any>( companion object { inline operator fun <reified T : Any> invoke( name: String, - data: List<T>, - noinline metaBuilder: ColumnScheme.() -> Unit - ): ListColumn<T> = ListColumn(name, data, T::class, ColumnScheme(metaBuilder).toMeta()) + def: ColumnScheme, + data: List<T?> + ): ListColumn<T> = ListColumn(name, data, T::class, def.toMeta()) + + inline operator fun <reified T : Any> invoke( + name: String, + def: ColumnScheme, + size: Int, + dataBuilder: (Int) -> T? + ): ListColumn<T> = invoke(name, def, List(size, dataBuilder)) } } diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt index 0a78ca9e..76002e9d 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt @@ -3,17 +3,8 @@ package hep.dataforge.tables import kotlin.reflect.KClass class MapRow(val values: Map<String, Any>) : Row { - @Suppress("UNCHECKED_CAST") override fun <T : Any> getValue(column: String, type: KClass<out T>): T? { val value = values[column] - return when { - value == null -> null - type.isInstance(value) -> { - value as T? - } - else -> { - error("Expected type is $type, but found ${value::class}") - } - } + return type.cast(value) } } \ No newline at end of file diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MetaQuery.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MetaQuery.kt new file mode 100644 index 00000000..cf07a5c8 --- /dev/null +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MetaQuery.kt @@ -0,0 +1,11 @@ +package hep.dataforge.tables + +import hep.dataforge.meta.scheme.Scheme +import hep.dataforge.meta.scheme.SchemeSpec +import hep.dataforge.meta.scheme.string + +class MetaQuery : Scheme() { + var field by string() + + companion object : SchemeSpec<MetaQuery>(::MetaQuery) +} \ No newline at end of file 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 fbcec0b7..c640aade 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt @@ -3,24 +3,21 @@ package hep.dataforge.tables import hep.dataforge.meta.Meta import kotlin.reflect.KClass -data class ColumnDef<T : Any>(val name: String, val type: KClass<T>, val meta: Meta) -class RowTable<R : Row>(override val rows: List<R>, private val columnDefs: List<ColumnDef<*>>) : Table { +class RowTable<R : Row>(override val rows: List<R>, override val header: TableHeader) : Table { override fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? = rows[row].getValue(column, type) - override val columns: Map<String, Column<*>> - get() = columnDefs.associate { it.name to VirtualColumn(it) } + override val columns: List<Column<*>> get() = header.map { RotTableColumn(it) } - private inner class VirtualColumn<T : Any>(val def: ColumnDef<T>) : - Column<T> { - - override val name: String get() = def.name - override val type: KClass<out T> get() = def.type - override val meta: Meta get() = def.meta + private inner class RotTableColumn<T : Any>(val header: ColumnHeader<T>) : Column<T> { + override val name: String get() = header.name + override val type: KClass<out T> get() = header.type + override val meta: Meta get() = header.meta override val size: Int get() = rows.size override fun get(index: Int): T? = rows[index].getValue(name, type) } + } 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 52171625..4961f9bb 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt @@ -3,16 +3,44 @@ package hep.dataforge.tables import hep.dataforge.meta.Meta import kotlin.reflect.KClass -interface Table { - fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? - val columns: Map<String, Column<*>> - val rows: List<Row> +//TODO to be removed in 1.3.70 +@Suppress("UNCHECKED_CAST") +internal fun <T : Any> KClass<T>.cast(value: Any?): T? { + return when { + value == null -> null + !isInstance(value) -> error("Expected type is $this, but found ${value::class}") + else -> value as T + } } -interface Column<out T : Any> { +typealias TableHeader = List<ColumnHeader<*>> + + +interface Table { + fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? + val columns: Collection<Column<*>> + val header: TableHeader get() = columns.toList() + val rows: List<Row> + + /** + * Apply query to a table and return lazy Flow + */ + //fun find(query: Any): Flow<Row> +} + +operator fun Collection<Column<*>>.get(name: String): Column<*>? = find { it.name == name } + +inline operator fun <reified T : Any> Table.get(row: Int, column: String): T? = getValue(row, column, T::class) + +interface ColumnHeader<out T : Any> { val name: String val type: KClass<out T> val meta: Meta +} + +operator fun <T : Any> Table.get(row: Int, column: Column<T>): T? = getValue(row, column.name, column.type) + +interface Column<out T : Any> : ColumnHeader<T> { val size: Int operator fun get(index: Int): T? } @@ -20,11 +48,14 @@ interface Column<out T : Any> { val Column<*>.indices get() = (0 until size) operator fun <T : Any> Column<T>.iterator() = iterator { - for (i in indices){ + for (i in indices) { yield(get(i)) } } interface Row { fun <T : Any> getValue(column: String, type: KClass<out T>): T? -} \ No newline at end of file +} + +inline operator fun <reified T : Any> Row.get(column: String): T? = getValue(column, T::class) +operator fun <T : Any> Row.get(column: Column<T>): T? = getValue(column.name, column.type) \ No newline at end of file diff --git a/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt b/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt new file mode 100644 index 00000000..9b9801c8 --- /dev/null +++ b/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt @@ -0,0 +1,36 @@ +package hep.dataforge.tables + +import hep.dataforge.meta.Meta +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KClass +import kotlin.reflect.KProperty +import kotlin.reflect.full.cast +import kotlin.reflect.full.isSubclassOf + +@Suppress("UNCHECKED_CAST") +fun <T : Any> Column<*>.cast(type: KClass<T>): Column<T> { + return if (type.isSubclassOf(this.type)) { + this as Column<T> + } else { + CastColumn(this, type) + } +} + +class CastColumn<T : Any>(val origin: Column<*>, override val type: KClass<T>) : Column<T> { + override val name: String get() = origin.name + override val meta: Meta get() = origin.meta + override val size: Int get() = origin.size + + + override fun get(index: Int): T? = type.cast(origin[index]) +} + +class ColumnProperty<T : Any>(val table: Table, val type: KClass<T>) : ReadOnlyProperty<Any?, Column<T>> { + override fun getValue(thisRef: Any?, property: KProperty<*>): Column<T> { + val name = property.name + return (table.columns[name] ?: error("Column with name $name not found in the table")).cast(type) + } +} + +operator fun <T : Any> Collection<Column<*>>.get(header: ColumnHeader<T>): Column<T>? = + find { it.name == header.name }?.cast(header.type) diff --git a/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/TableAccessor.kt b/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/TableAccessor.kt deleted file mode 100644 index 21453426..00000000 --- a/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/TableAccessor.kt +++ /dev/null @@ -1,37 +0,0 @@ -package hep.dataforge.tables - -import hep.dataforge.meta.Meta -import kotlin.properties.ReadOnlyProperty -import kotlin.reflect.KClass -import kotlin.reflect.KProperty -import kotlin.reflect.full.cast -import kotlin.reflect.full.isSubclassOf - -class TableAccessor(val table: Table) : Table by table { - inline fun <reified T : Any> column() = ColumnProperty(table, T::class) -} - -@Suppress("UNCHECKED_CAST") -fun <T : Any> Column<*>.cast(type: KClass<T>): Column<T> { - return if (type.isSubclassOf(this.type)) { - this as Column<T> - } else { - ColumnWrapper(this, type) - } -} - -class ColumnWrapper<T : Any>(val column: Column<*>, override val type: KClass<T>) : Column<T> { - override val name: String get() = column.name - override val meta: Meta get() = column.meta - override val size: Int get() = column.size - - - override fun get(index: Int): T? = type.cast(column[index]) -} - -class ColumnProperty<T : Any>(val table: Table, val type: KClass<T>) : ReadOnlyProperty<Any?, Column<T>?> { - override fun getValue(thisRef: Any?, property: KProperty<*>): Column<T>? { - val name = property.name - return table.columns[name]?.cast(type) - } -} \ No newline at end of file From 126e59f81a134723c8717393c45e384a968ceef9 Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Sat, 8 Feb 2020 22:01:56 +0300 Subject: [PATCH 29/41] Bottom type check for tables --- .../kotlin/hep/dataforge/tables/ColumnTable.kt | 4 ++-- .../kotlin/hep/dataforge/tables/ColumnTableBuilder.kt | 10 +++++----- .../commonMain/kotlin/hep/dataforge/tables/RowTable.kt | 6 +++--- .../commonMain/kotlin/hep/dataforge/tables/Table.kt | 8 ++++---- .../jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) 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 07ea5d94..c48b30f8 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt @@ -5,7 +5,7 @@ import kotlin.reflect.KClass /** * @param C bottom type for all columns in the table */ -class ColumnTable<C: Any>(override val columns: Collection<Column<C>>) : Table { +class ColumnTable<C : Any>(override val columns: Collection<Column<C>>) : Table<C> { private val rowsNum = columns.first().size init { @@ -21,7 +21,7 @@ class ColumnTable<C: Any>(override val columns: Collection<Column<C>>) : Table { } } -internal class VirtualRow(val table: Table, val index: Int) : Row { +internal class VirtualRow<C : Any>(val table: Table<C>, val index: Int) : Row { override fun <T : Any> getValue(column: String, type: KClass<out T>): T? = table.getValue(index, column, type) } diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTableBuilder.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTableBuilder.kt index b453c957..78a32366 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTableBuilder.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTableBuilder.kt @@ -3,10 +3,10 @@ package hep.dataforge.tables import hep.dataforge.meta.Meta import kotlin.reflect.KClass -class ColumnTableBuilder(val size: Int) : Table { - private val _columns = ArrayList<Column<*>>() +class ColumnTableBuilder<C: Any>(val size: Int) : Table<C> { + private val _columns = ArrayList<Column<C>>() - override val columns: List<Column<*>> get() = _columns + override val columns: List<Column<C>> get() = _columns override val rows: List<Row> get() = (0 until size).map { VirtualRow(this, it) } @@ -19,7 +19,7 @@ class ColumnTableBuilder(val size: Int) : Table { /** * Add a fixed column to the end of the table */ - fun add(column: Column<*>) { + fun add(column: Column<C>) { require(column.size == this.size) { "Required column size $size, but found ${column.size}" } _columns.add(column) } @@ -27,7 +27,7 @@ class ColumnTableBuilder(val size: Int) : Table { /** * Insert a column at [index] */ - fun insert(index: Int, column: Column<*>) { + fun insert(index: Int, column: Column<C>) { require(column.size == this.size) { "Required column size $size, but found ${column.size}" } _columns.add(index, 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 c640aade..bc2ca808 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt @@ -4,13 +4,13 @@ import hep.dataforge.meta.Meta import kotlin.reflect.KClass -class RowTable<R : Row>(override val rows: List<R>, override val header: TableHeader) : Table { +class RowTable<C: Any, R : Row>(override val rows: List<R>, override val header: List<ColumnHeader<C>>) : Table<C> { override fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? = rows[row].getValue(column, type) - override val columns: List<Column<*>> get() = header.map { RotTableColumn(it) } + override val columns: List<Column<C>> get() = header.map { RotTableColumn(it) } - private inner class RotTableColumn<T : Any>(val header: ColumnHeader<T>) : Column<T> { + private inner class RotTableColumn<T : C>(val header: ColumnHeader<T>) : Column<T> { override val name: String get() = header.name override val type: KClass<out T> get() = header.type override val meta: Meta get() = header.meta 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 4961f9bb..96d7964d 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt @@ -16,9 +16,9 @@ internal fun <T : Any> KClass<T>.cast(value: Any?): T? { typealias TableHeader = List<ColumnHeader<*>> -interface Table { +interface Table<out C: Any> { fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? - val columns: Collection<Column<*>> + val columns: Collection<Column<C>> val header: TableHeader get() = columns.toList() val rows: List<Row> @@ -30,7 +30,7 @@ interface Table { operator fun Collection<Column<*>>.get(name: String): Column<*>? = find { it.name == name } -inline operator fun <reified T : Any> Table.get(row: Int, column: String): T? = getValue(row, column, T::class) +inline operator fun <C: Any, reified T : C> Table<C>.get(row: Int, column: String): T? = getValue(row, column, T::class) interface ColumnHeader<out T : Any> { val name: String @@ -38,7 +38,7 @@ interface ColumnHeader<out T : Any> { val meta: Meta } -operator fun <T : Any> Table.get(row: Int, column: Column<T>): T? = getValue(row, column.name, column.type) +operator fun <C: Any, T : C> Table<C>.get(row: Int, column: Column<T>): T? = getValue(row, column.name, column.type) interface Column<out T : Any> : ColumnHeader<T> { val size: Int diff --git a/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt b/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt index 9b9801c8..1ac7e138 100644 --- a/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt +++ b/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt @@ -25,7 +25,7 @@ class CastColumn<T : Any>(val origin: Column<*>, override val type: KClass<T>) : override fun get(index: Int): T? = type.cast(origin[index]) } -class ColumnProperty<T : Any>(val table: Table, val type: KClass<T>) : ReadOnlyProperty<Any?, Column<T>> { +class ColumnProperty<C: Any, T : C>(val table: Table<C>, val type: KClass<T>) : ReadOnlyProperty<Any?, Column<T>> { override fun getValue(thisRef: Any?, property: KProperty<*>): Column<T> { val name = property.name return (table.columns[name] ?: error("Column with name $name not found in the table")).cast(type) From cd4f07267b20868f0ff6b0bcb2eb7895a271eb3a Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Mon, 10 Feb 2020 21:49:53 +0300 Subject: [PATCH 30/41] Mutable tables --- .../kotlin/hep/dataforge/io/FileEnvelope.kt | 2 + .../kotlin/hep/dataforge/meta/Meta.kt | 2 +- .../meta/scheme/ConfigurableDelegate.kt | 4 +- .../hep/dataforge/tables/ColumnScheme.kt | 11 +- .../kotlin/hep/dataforge/tables/MapRow.kt | 2 +- .../kotlin/hep/dataforge/tables/MetaQuery.kt | 11 -- ...nTableBuilder.kt => MutableColumnTable.kt} | 5 +- .../hep/dataforge/tables/MutableTable.kt | 43 +++++++ .../kotlin/hep/dataforge/tables/RowTable.kt | 25 ++-- .../kotlin/hep/dataforge/tables/Table.kt | 28 ++-- .../hep/dataforge/tables/io/TextRows.kt | 120 ++++++++++++++++++ .../hep/dataforge/tables/io/TextRowsTest.kt | 6 + 12 files changed, 222 insertions(+), 37 deletions(-) delete mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MetaQuery.kt rename dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/{ColumnTableBuilder.kt => MutableColumnTable.kt} (93%) create mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableTable.kt create mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/TextRows.kt create mode 100644 dataforge-tables/src/commonTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/FileEnvelope.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/FileEnvelope.kt index 3b34c26c..21cca102 100644 --- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/FileEnvelope.kt +++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/FileEnvelope.kt @@ -2,10 +2,12 @@ package hep.dataforge.io import hep.dataforge.meta.Meta import kotlinx.io.Binary +import kotlinx.io.ExperimentalIoApi import kotlinx.io.FileBinary import kotlinx.io.read import java.nio.file.Path +@ExperimentalIoApi class FileEnvelope internal constructor(val path: Path, val format: EnvelopeFormat) : Envelope { //TODO do not like this constructor. Hope to replace it later 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 d917559d..93e416de 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt @@ -208,7 +208,7 @@ val MetaItem<*>?.int get() = number?.toInt() val MetaItem<*>?.long get() = number?.toLong() val MetaItem<*>?.short get() = number?.toShort() -inline fun <reified E : Enum<E>> MetaItem<*>?.enum() = if (this is ValueItem && this.value is EnumValue<*>) { +inline fun <reified E : Enum<E>> MetaItem<*>?.enum(): E? = if (this is ValueItem && this.value is EnumValue<*>) { this.value.value as E } else { string?.let { enumValueOf<E>(it) } diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt index 9b86abba..c6e9a44a 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt @@ -168,8 +168,8 @@ fun Configurable.float(default: Float, key: Name? = null): ReadWriteProperty<Any /** * Enum delegate */ -inline fun <reified E : Enum<E>> Configurable.enum(default: E, key: Name? = null): ReadWriteProperty<Any?, E?> = - item(default, key).transform { it.enum<E>() } +inline fun <reified E : Enum<E>> Configurable.enum(default: E, key: Name? = null): ReadWriteProperty<Any?, E> = + item(default, key).transform { it.enum<E>() ?: default } /* * Extra delegates for special cases diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnScheme.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnScheme.kt index aa0d2aec..2b65b234 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnScheme.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnScheme.kt @@ -2,7 +2,16 @@ package hep.dataforge.tables import hep.dataforge.meta.scheme.Scheme import hep.dataforge.meta.scheme.SchemeSpec +import hep.dataforge.meta.scheme.enum +import hep.dataforge.meta.scheme.string +import hep.dataforge.values.ValueType + +open class ColumnScheme : Scheme() { + var title by string() -class ColumnScheme : Scheme() { companion object : SchemeSpec<ColumnScheme>(::ColumnScheme) +} + +class ValueColumnScheme : ColumnScheme() { + var valueType by enum(ValueType.STRING) } \ No newline at end of file diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt index 76002e9d..421241d4 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt @@ -2,7 +2,7 @@ package hep.dataforge.tables import kotlin.reflect.KClass -class MapRow(val values: Map<String, Any>) : Row { +inline class MapRow(val values: Map<String, Any?>) : Row { override fun <T : Any> getValue(column: String, type: KClass<out T>): T? { val value = values[column] return type.cast(value) diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MetaQuery.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MetaQuery.kt deleted file mode 100644 index cf07a5c8..00000000 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MetaQuery.kt +++ /dev/null @@ -1,11 +0,0 @@ -package hep.dataforge.tables - -import hep.dataforge.meta.scheme.Scheme -import hep.dataforge.meta.scheme.SchemeSpec -import hep.dataforge.meta.scheme.string - -class MetaQuery : Scheme() { - var field by string() - - companion object : SchemeSpec<MetaQuery>(::MetaQuery) -} \ No newline at end of file diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTableBuilder.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableColumnTable.kt similarity index 93% rename from dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTableBuilder.kt rename to dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableColumnTable.kt index 78a32366..d7a360c9 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTableBuilder.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableColumnTable.kt @@ -3,7 +3,10 @@ package hep.dataforge.tables import hep.dataforge.meta.Meta import kotlin.reflect.KClass -class ColumnTableBuilder<C: Any>(val size: Int) : Table<C> { +/** + * Mutable table with a fixed size, but dynamic columns + */ +class MutableColumnTable<C: Any>(val size: Int) : Table<C> { private val _columns = ArrayList<Column<C>>() override val columns: List<Column<C>> get() = _columns diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableTable.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableTable.kt new file mode 100644 index 00000000..686e3c34 --- /dev/null +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableTable.kt @@ -0,0 +1,43 @@ +package hep.dataforge.tables + +import hep.dataforge.meta.Meta +import kotlin.reflect.KClass + +class SimpleColumnHeader<T : Any>( + override val name: String, + override val type: KClass<out T>, + override val meta: Meta +) : ColumnHeader<T> + +class MutableTable<C : Any>( + override val rows: MutableList<Row>, + override val header: MutableList<ColumnHeader<C>> +) : RowTable<C, Row>(rows, header) { + fun <T : C> column(name: String, type: KClass<out T>, meta: Meta): ColumnHeader<T> { + val column = SimpleColumnHeader(name, type, meta) + header.add(column) + return column + } + + inline fun <reified T : C> column( + name: String, + noinline columnMetaBuilder: ColumnScheme.() -> Unit + ): ColumnHeader<T> { + return column(name, T::class, ColumnScheme(columnMetaBuilder).toMeta()) + } + + fun row(block: MutableMap<String, Any?>.() -> Unit): Row { + val map = HashMap<String, Any?>().apply(block) + val row = MapRow(map) + rows.add(row) + return row + } + + operator fun <T : Any> MutableMap<String, Any?>.set(header: ColumnHeader<T>, value: T?) { + set(header.name, value) + } +} + +fun <C : Any> Table<C>.edit(block: MutableTable<C>.() -> Unit): Table<C> { + return MutableTable(rows.toMutableList(), header.toMutableList()).apply(block) +} \ No newline at end of file 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 bc2ca808..98d7d4e1 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt @@ -1,23 +1,24 @@ package hep.dataforge.tables import hep.dataforge.meta.Meta +import kotlinx.coroutines.flow.toList import kotlin.reflect.KClass +internal class RowTableColumn<C : Any, T : C>(val table: Table<C>, val header: ColumnHeader<T>) : Column<T> { + override val name: String get() = header.name + override val type: KClass<out T> get() = header.type + override val meta: Meta get() = header.meta + override val size: Int get() = table.rows.size -class RowTable<C: Any, R : Row>(override val rows: List<R>, override val header: List<ColumnHeader<C>>) : Table<C> { + override fun get(index: Int): T? = table.rows[index].getValue(name, type) +} + +open class RowTable<C : Any, R : Row>(override val rows: List<R>, override val header: List<ColumnHeader<C>>) : + Table<C> { override fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? = rows[row].getValue(column, type) - override val columns: List<Column<C>> get() = header.map { RotTableColumn(it) } - - private inner class RotTableColumn<T : C>(val header: ColumnHeader<T>) : Column<T> { - override val name: String get() = header.name - override val type: KClass<out T> get() = header.type - override val meta: Meta get() = header.meta - override val size: Int get() = rows.size - - override fun get(index: Int): T? = rows[index].getValue(name, type) - } - + override val columns: List<Column<C>> get() = header.map { RowTableColumn(this, it) } } +suspend fun Rows.collect(): Table<*> = this as? Table<*> ?: RowTable(rowFlow().toList(), header) \ No newline at end of file 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 96d7964d..4e8215e0 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt @@ -1,6 +1,8 @@ package hep.dataforge.tables import hep.dataforge.meta.Meta +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.asFlow import kotlin.reflect.KClass //TODO to be removed in 1.3.70 @@ -13,24 +15,34 @@ internal fun <T : Any> KClass<T>.cast(value: Any?): T? { } } -typealias TableHeader = List<ColumnHeader<*>> +typealias TableHeader<C> = List<ColumnHeader<C>> +/** + * 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<Row> +} -interface Table<out C: Any> { +interface Table<out C : Any> : Rows { fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? val columns: Collection<Column<C>> - val header: TableHeader get() = columns.toList() + override val header: TableHeader<C> get() = columns.toList() val rows: List<Row> + override fun rowFlow(): Flow<Row> = rows.asFlow() /** - * Apply query to a table and return lazy Flow + * Apply typed query to this table and return lazy [Flow] of resulting rows. The flow could be empty. */ - //fun find(query: Any): Flow<Row> + //fun select(query: Any): Flow<Row> = error("Query of type ${query::class} is not supported by this table") } operator fun Collection<Column<*>>.get(name: String): Column<*>? = find { it.name == name } -inline operator fun <C: Any, reified T : C> Table<C>.get(row: Int, column: String): T? = getValue(row, column, T::class) +inline operator fun <C : Any, reified T : C> Table<C>.get(row: Int, column: String): T? = + getValue(row, column, T::class) interface ColumnHeader<out T : Any> { val name: String @@ -38,7 +50,7 @@ interface ColumnHeader<out T : Any> { val meta: Meta } -operator fun <C: Any, T : C> Table<C>.get(row: Int, column: Column<T>): T? = getValue(row, column.name, column.type) +operator fun <C : Any, T : C> Table<C>.get(row: Int, column: Column<T>): T? = getValue(row, column.name, column.type) interface Column<out T : Any> : ColumnHeader<T> { val size: Int @@ -58,4 +70,4 @@ interface Row { } inline operator fun <reified T : Any> Row.get(column: String): T? = getValue(column, T::class) -operator fun <T : Any> Row.get(column: Column<T>): T? = getValue(column.name, column.type) \ No newline at end of file +operator fun <T : Any> Row.get(column: ColumnHeader<T>): T? = getValue(column.name, column.type) \ 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 new file mode 100644 index 00000000..f4e73702 --- /dev/null +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/TextRows.kt @@ -0,0 +1,120 @@ +package hep.dataforge.tables.io + +import hep.dataforge.meta.get +import hep.dataforge.meta.int +import hep.dataforge.meta.string +import hep.dataforge.tables.* +import hep.dataforge.values.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.flow +import kotlinx.io.Binary +import kotlinx.io.ExperimentalIoApi +import kotlinx.io.Output +import kotlinx.io.RandomAccessBinary +import kotlinx.io.text.forEachUtf8Line +import kotlinx.io.text.readUtf8Line +import kotlinx.io.text.writeUtf8String +import kotlin.reflect.KClass + +private fun readLine(header: List<ColumnHeader<Value>>, line: String): Row { + val values = line.split("\\s+".toRegex()).map { it.parseValue() } + + if (values.size == header.size) { + val map = header.map { it.name }.zip(values).toMap() + return MapRow(map) + } else { + error("Can't read line $line. Expected ${header.size} values in a line, but found ${values.size}") + } +} + + +@ExperimentalIoApi +class TextRows(override val header: List<ColumnHeader<Value>>, val binary: Binary) : Rows { + + override fun rowFlow(): Flow<Row> = binary.read { + flow { + forEachUtf8Line { line -> + if (line.isNotBlank()) { + val row = readLine(header, line) + emit(row) + } + } + } + } +} + +@ExperimentalIoApi +class TextTable( + override val header: List<ColumnHeader<Value>>, + val binary: RandomAccessBinary, + val index: List<Int> +) : Table<Value> { + + override val columns: Collection<Column<Value>> get() = header.map { RowTableColumn(this, it) } + + override val rows: List<Row> get() = index.map { readAt(it) } + + override fun rowFlow(): Flow<Row> = TextRows(header, binary).rowFlow() + + private fun readAt(offset: Int): Row { + return binary.read(offset) { + val line = readUtf8Line() + return@read readLine(header, line) + } + } + + override fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? { + val offset = index[row] + return type.cast(readAt(offset)[column]) + } +} + +fun Output.writeValue(value: Value, width: Int, left: Boolean = true) { + require(width > 5) { "Width could not be less than 5" } + val str: String = when (value.type) { + ValueType.NUMBER -> value.number.toString() //TODO apply decimal format + ValueType.STRING -> value.string.take(width) + ValueType.BOOLEAN -> if (value.boolean) { + "true" + } else { + "false" + } + ValueType.NULL -> "@null" + } + val padded = if (left) { + str.padEnd(width) + } else { + str.padStart(width) + } + writeUtf8String(padded) +} + +val ColumnHeader<Value>.valueType: ValueType? get() = meta["valueType"].string?.let { ValueType.valueOf(it) } + +private val ColumnHeader<Value>.width: Int + get() = meta["columnWidth"].int ?: when (valueType) { + ValueType.NUMBER -> 8 + ValueType.STRING -> 16 + ValueType.BOOLEAN -> 5 + ValueType.NULL -> 5 + null -> 16 + } + + +/** + * Write rows without header to the output + */ +suspend fun Output.writeRows(rows: Rows) { + @Suppress("UNCHECKED_CAST") val header = rows.header.map { + if (it.type != Value::class) error("Expected Value column, but found ${it.type}") else (it as ColumnHeader<Value>) + } + val widths: List<Int> = header.map { + it.width + } + rows.rowFlow().collect { row -> + header.forEachIndexed { index, columnHeader -> + writeValue(row[columnHeader] ?: Null, widths[index]) + } + } +} \ No newline at end of file diff --git a/dataforge-tables/src/commonTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt b/dataforge-tables/src/commonTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt new file mode 100644 index 00000000..010f903f --- /dev/null +++ b/dataforge-tables/src/commonTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt @@ -0,0 +1,6 @@ +package hep.dataforge.tables.io + + +class TextRowsTest{ + +} \ No newline at end of file From dfea68b65c9b94f4e53002b71fb2eea144302ac9 Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Sun, 16 Feb 2020 15:18:09 +0300 Subject: [PATCH 31/41] Text tables test working --- .../kotlin/hep/dataforge/io/Envelope.kt | 2 +- .../hep/dataforge/io/EnvelopeBuilder.kt | 20 ++++- .../kotlin/hep/dataforge/meta/MutableMeta.kt | 6 +- .../hep/dataforge/tables/ColumnHeader.kt | 36 ++++++++ .../hep/dataforge/tables/ColumnTable.kt | 12 ++- .../kotlin/hep/dataforge/tables/MapRow.kt | 4 +- .../dataforge/tables/MutableColumnTable.kt | 4 +- .../hep/dataforge/tables/MutableTable.kt | 25 +++--- .../kotlin/hep/dataforge/tables/RowTable.kt | 7 +- .../kotlin/hep/dataforge/tables/Table.kt | 37 ++++---- .../hep/dataforge/tables/io/TextRows.kt | 87 ++++++++++++------- .../dataforge/tables/io/textTableEnvelope.kt | 42 +++++++++ .../hep/dataforge/tables/io/TextRowsTest.kt | 6 -- .../kotlin/hep/dataforge/tables/CastColumn.kt | 6 +- .../hep/dataforge/tables/io/TextRowsTest.kt | 39 +++++++++ 15 files changed, 243 insertions(+), 90 deletions(-) create mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnHeader.kt create mode 100644 dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/textTableEnvelope.kt delete mode 100644 dataforge-tables/src/commonTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt create mode 100644 dataforge-tables/src/jvmTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt index adffdbf7..d7c60116 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt @@ -28,7 +28,7 @@ interface Envelope { /** * Build a static envelope using provided builder */ - operator fun invoke(block: EnvelopeBuilder.() -> Unit) = EnvelopeBuilder().apply(block).build() + inline operator fun invoke(block: EnvelopeBuilder.() -> Unit) = EnvelopeBuilder().apply(block).build() } } diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt index 0f12c213..94f4e59d 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt @@ -18,8 +18,15 @@ class EnvelopeBuilder { metaBuilder.update(meta) } + /** + * The general purpose of the envelope + */ var type by metaBuilder.string(key = Envelope.ENVELOPE_TYPE_KEY) var dataType by metaBuilder.string(key = Envelope.ENVELOPE_DATA_TYPE_KEY) + + /** + * Data unique identifier to bypass identity checks + */ var dataID by metaBuilder.string(key = Envelope.ENVELOPE_DATA_ID_KEY) var description by metaBuilder.string(key = Envelope.ENVELOPE_DESCRIPTION_KEY) var name by metaBuilder.string(key = Envelope.ENVELOPE_NAME_KEY) @@ -32,5 +39,14 @@ class EnvelopeBuilder { data = ArrayBinary.write(builder = block) } - internal fun build() = SimpleEnvelope(metaBuilder.seal(), data) -} \ No newline at end of file + fun build() = SimpleEnvelope(metaBuilder.seal(), data) + +} + +//@ExperimentalContracts +//suspend fun EnvelopeBuilder.buildData(block: suspend Output.() -> Unit): Binary{ +// contract { +// callsInPlace(block, InvocationKind.EXACTLY_ONCE) +// } +// val scope = CoroutineScope(coroutineContext) +//} \ No newline at end of file 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 88819f2c..043283e3 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt @@ -115,6 +115,8 @@ operator fun MutableMeta<*>.set(name: NameToken, value: Any?) = set(name.asName( operator fun MutableMeta<*>.set(key: String, value: Any?) = set(key.toName(), value) +operator fun MutableMeta<*>.set(key: String, index: String, value: Any?) = set(key.toName().withIndex(index), value) + /** * Update existing mutable node with another node. The rules are following: * * value replaces anything @@ -161,7 +163,7 @@ operator fun MutableMeta<*>.set(name: String, metas: Iterable<Meta>): Unit = set /** * Append the node with a same-name-sibling, automatically generating numerical index */ -fun <M: MutableMeta<M>> M.append(name: Name, value: Any?) { +fun <M : MutableMeta<M>> M.append(name: Name, value: Any?) { require(!name.isEmpty()) { "Name could not be empty for append operation" } val newIndex = name.last()!!.index if (newIndex.isNotEmpty()) { @@ -172,4 +174,4 @@ fun <M: MutableMeta<M>> M.append(name: Name, value: Any?) { } } -fun <M: MutableMeta<M>> M.append(name: String, value: Any?) = append(name.toName(), value) \ No newline at end of file +fun <M : MutableMeta<M>> M.append(name: String, value: Any?) = append(name.toName(), value) \ No newline at end of file diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnHeader.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnHeader.kt new file mode 100644 index 00000000..2023d11b --- /dev/null +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnHeader.kt @@ -0,0 +1,36 @@ +package hep.dataforge.tables + +import hep.dataforge.meta.Meta +import hep.dataforge.meta.get +import hep.dataforge.meta.int +import hep.dataforge.meta.string +import hep.dataforge.values.Value +import hep.dataforge.values.ValueType +import kotlin.reflect.KClass + +typealias TableHeader<C> = List<ColumnHeader<C>> + +typealias ValueTableHeader = List<ColumnHeader<Value>> + +interface ColumnHeader<out T : Any> { + val name: String + val type: KClass<out T> + val meta: Meta +} + +data class SimpleColumnHeader<T : Any>( + override val name: String, + override val type: KClass<out T>, + override val meta: Meta +) : ColumnHeader<T> + +val ColumnHeader<Value>.valueType: ValueType? get() = meta["valueType"].string?.let { ValueType.valueOf(it) } + +val ColumnHeader<Value>.textWidth: Int + get() = meta["columnWidth"].int ?: when (valueType) { + ValueType.NUMBER -> 8 + ValueType.STRING -> 16 + ValueType.BOOLEAN -> 5 + ValueType.NULL -> 5 + null -> 16 + } 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 c48b30f8..dbb90cf2 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt @@ -12,16 +12,20 @@ class ColumnTable<C : Any>(override val columns: Collection<Column<C>>) : Table< require(columns.all { it.size == rowsNum }) { "All columns must be of the same size" } } - override val rows: List<Row> + override val rows: List<Row<C>> get() = (0 until rowsNum).map { VirtualRow(this, it) } - override fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? { + override fun <T : C> getValue(row: Int, column: String, type: KClass<out T>): T? { val value = columns[column]?.get(row) return type.cast(value) } } -internal class VirtualRow<C : Any>(val table: Table<C>, val index: Int) : Row { - override fun <T : Any> getValue(column: String, type: KClass<out T>): T? = table.getValue(index, column, type) +internal class VirtualRow<C : Any>(val table: Table<C>, val index: Int) : Row<C> { + override fun <T : C> getValue(column: String, type: KClass<out T>): T? = table.getValue(index, column, type) + +// override fun <T : C> get(columnHeader: ColumnHeader<T>): 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 index 421241d4..04c7d3a4 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt @@ -2,8 +2,8 @@ package hep.dataforge.tables import kotlin.reflect.KClass -inline class MapRow(val values: Map<String, Any?>) : Row { - override fun <T : Any> getValue(column: String, type: KClass<out T>): T? { +inline class MapRow<C: Any>(val values: Map<String, C?>) : Row<C> { + override fun <T : C> getValue(column: String, type: KClass<out T>): T? { val value = values[column] return type.cast(value) } 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 d7a360c9..8a9b06bc 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableColumnTable.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableColumnTable.kt @@ -10,11 +10,11 @@ class MutableColumnTable<C: Any>(val size: Int) : Table<C> { private val _columns = ArrayList<Column<C>>() override val columns: List<Column<C>> get() = _columns - override val rows: List<Row> get() = (0 until size).map { + override val rows: List<Row<C>> get() = (0 until size).map { VirtualRow(this, it) } - override fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? { + override fun <T : C> getValue(row: Int, column: String, type: KClass<out T>): T? { val value = columns[column]?.get(row) return type.cast(value) } 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 686e3c34..aa2d9abf 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableTable.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableTable.kt @@ -1,18 +1,14 @@ package hep.dataforge.tables import hep.dataforge.meta.Meta +import hep.dataforge.values.Value import kotlin.reflect.KClass -class SimpleColumnHeader<T : Any>( - override val name: String, - override val type: KClass<out T>, - override val meta: Meta -) : ColumnHeader<T> - class MutableTable<C : Any>( - override val rows: MutableList<Row>, + override val rows: MutableList<Row<C>>, override val header: MutableList<ColumnHeader<C>> -) : RowTable<C, Row>(rows, header) { +) : RowTable<C>(rows, header) { + fun <T : C> column(name: String, type: KClass<out T>, meta: Meta): ColumnHeader<T> { val column = SimpleColumnHeader(name, type, meta) header.add(column) @@ -21,23 +17,24 @@ class MutableTable<C : Any>( inline fun <reified T : C> column( name: String, - noinline columnMetaBuilder: ColumnScheme.() -> Unit + noinline columnMetaBuilder: ColumnScheme.() -> Unit = {} ): ColumnHeader<T> { return column(name, T::class, ColumnScheme(columnMetaBuilder).toMeta()) } - fun row(block: MutableMap<String, Any?>.() -> Unit): Row { - val map = HashMap<String, Any?>().apply(block) + fun row(map: Map<String, C?>): Row<C> { val row = MapRow(map) rows.add(row) return row } - operator fun <T : Any> MutableMap<String, Any?>.set(header: ColumnHeader<T>, value: T?) { - set(header.name, value) - } + fun <T : C> row(vararg pairs: Pair<ColumnHeader<T>, T>): Row<C> = + row(pairs.associate { it.first.name to it.second }) } +fun MutableTable<Value>.row(vararg pairs: Pair<ColumnHeader<Value>, Any?>): Row<Value> = + row(pairs.associate { it.first.name to Value.of(it.second) }) + fun <C : Any> Table<C>.edit(block: MutableTable<C>.() -> Unit): Table<C> { return MutableTable(rows.toMutableList(), header.toMutableList()).apply(block) } \ No newline at end of file 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 98d7d4e1..c565d9cd 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt @@ -13,12 +13,11 @@ internal class RowTableColumn<C : Any, T : C>(val table: Table<C>, val header: C override fun get(index: Int): T? = table.rows[index].getValue(name, type) } -open class RowTable<C : Any, R : Row>(override val rows: List<R>, override val header: List<ColumnHeader<C>>) : - Table<C> { - override fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? = +open class RowTable<C : Any>(override val rows: List<Row<C>>, override val header: List<ColumnHeader<C>>) : Table<C> { + override fun <T : C> getValue(row: Int, column: String, type: KClass<out T>): T? = rows[row].getValue(column, type) override val columns: List<Column<C>> get() = header.map { RowTableColumn(this, it) } } -suspend fun Rows.collect(): Table<*> = this as? Table<*> ?: RowTable(rowFlow().toList(), header) \ No newline at end of file +suspend fun <C : Any> Rows<C>.collect(): Table<C> = this as? Table<C> ?: RowTable(rowFlow().toList(), header) \ No newline at end of file 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 4e8215e0..71469984 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt @@ -1,6 +1,5 @@ package hep.dataforge.tables -import hep.dataforge.meta.Meta import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.asFlow import kotlin.reflect.KClass @@ -15,28 +14,30 @@ internal fun <T : Any> KClass<T>.cast(value: Any?): T? { } } -typealias TableHeader<C> = List<ColumnHeader<C>> - /** * 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<Row> +interface Rows<C : Any> { + val header: TableHeader<C> + fun rowFlow(): Flow<Row<C>> } -interface Table<out C : Any> : Rows { - fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? +interface Table<C : Any> : Rows<C> { + fun <T : C> getValue(row: Int, column: String, type: KClass<out T>): T? val columns: Collection<Column<C>> override val header: TableHeader<C> get() = columns.toList() - val rows: List<Row> - override fun rowFlow(): Flow<Row> = rows.asFlow() + val rows: List<Row<C>> + override fun rowFlow(): Flow<Row<C>> = rows.asFlow() /** * Apply typed query to this table and return lazy [Flow] of resulting rows. The flow could be empty. */ //fun select(query: Any): Flow<Row> = error("Query of type ${query::class} is not supported by this table") + companion object { + inline operator fun <T : Any> invoke(block: MutableTable<T>.() -> Unit): Table<T> = + MutableTable<T>(arrayListOf(), arrayListOf()).apply(block) + } } operator fun Collection<Column<*>>.get(name: String): Column<*>? = find { it.name == name } @@ -44,15 +45,9 @@ operator fun Collection<Column<*>>.get(name: String): Column<*>? = find { it.nam inline operator fun <C : Any, reified T : C> Table<C>.get(row: Int, column: String): T? = getValue(row, column, T::class) -interface ColumnHeader<out T : Any> { - val name: String - val type: KClass<out T> - val meta: Meta -} - operator fun <C : Any, T : C> Table<C>.get(row: Int, column: Column<T>): T? = getValue(row, column.name, column.type) -interface Column<out T : Any> : ColumnHeader<T> { +interface Column<T : Any> : ColumnHeader<T> { val size: Int operator fun get(index: Int): T? } @@ -65,9 +60,9 @@ operator fun <T : Any> Column<T>.iterator() = iterator { } } -interface Row { - fun <T : Any> getValue(column: String, type: KClass<out T>): T? +interface Row<C: Any> { + fun <T : C> getValue(column: String, type: KClass<out T>): T? } -inline operator fun <reified T : Any> Row.get(column: String): T? = getValue(column, T::class) -operator fun <T : Any> Row.get(column: ColumnHeader<T>): T? = getValue(column.name, column.type) \ No newline at end of file +inline operator fun <C : Any, reified T : C> Row<C>.get(column: String): T? = getValue(column, T::class) +operator fun <C : Any, T : C> Row<C>.get(column: ColumnHeader<T>): T? = getValue(column.name, column.type) \ 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 f4e73702..2675b483 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 @@ -1,38 +1,56 @@ package hep.dataforge.tables.io -import hep.dataforge.meta.get -import hep.dataforge.meta.int -import hep.dataforge.meta.string import hep.dataforge.tables.* import hep.dataforge.values.* import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.toList import kotlinx.io.Binary import kotlinx.io.ExperimentalIoApi import kotlinx.io.Output import kotlinx.io.RandomAccessBinary import kotlinx.io.text.forEachUtf8Line import kotlinx.io.text.readUtf8Line +import kotlinx.io.text.readUtf8StringUntilDelimiter import kotlinx.io.text.writeUtf8String import kotlin.reflect.KClass -private fun readLine(header: List<ColumnHeader<Value>>, line: String): Row { - val values = line.split("\\s+".toRegex()).map { it.parseValue() } +/** + * Read a lin as a fixed width [Row] + */ +private fun readLine(header: ValueTableHeader, line: String): Row<Value> { + val values = line.trim().split("\\s+".toRegex()).map { it.lazyParseValue() } if (values.size == header.size) { val map = header.map { it.name }.zip(values).toMap() return MapRow(map) } else { - error("Can't read line $line. Expected ${header.size} values in a line, but found ${values.size}") + error("Can't read line \"$line\". Expected ${header.size} values in a line, but found ${values.size}") } } - +/** + * Finite or infinite [Rows] created from a fixed width text binary + */ @ExperimentalIoApi -class TextRows(override val header: List<ColumnHeader<Value>>, val binary: Binary) : Rows { +class TextRows(override val header: ValueTableHeader, val binary: Binary) : Rows<Value> { - override fun rowFlow(): Flow<Row> = binary.read { + /** + * A flow of indexes of string start offsets ignoring empty strings + */ + fun indexFlow(): Flow<Int> = binary.read { + var counter: Int = 0 + flow { + val string = readUtf8StringUntilDelimiter('\n') + counter += string.length + if (!string.isBlank()) { + emit(counter) + } + } + } + + override fun rowFlow(): Flow<Row<Value>> = binary.read { flow { forEachUtf8Line { line -> if (line.isNotBlank()) { @@ -42,35 +60,57 @@ class TextRows(override val header: List<ColumnHeader<Value>>, val binary: Binar } } } + + companion object } +/** + * Create a row offset index for [TextRows] + */ +@ExperimentalIoApi +suspend fun TextRows.buildRowIndex(): List<Int> = indexFlow().toList() + +/** + * Finite table created from [RandomAccessBinary] with fixed width text table + */ @ExperimentalIoApi class TextTable( - override val header: List<ColumnHeader<Value>>, + override val header: ValueTableHeader, val binary: RandomAccessBinary, val index: List<Int> ) : Table<Value> { override val columns: Collection<Column<Value>> get() = header.map { RowTableColumn(this, it) } - override val rows: List<Row> get() = index.map { readAt(it) } + override val rows: List<Row<Value>> get() = index.map { readAt(it) } - override fun rowFlow(): Flow<Row> = TextRows(header, binary).rowFlow() + override fun rowFlow(): Flow<Row<Value>> = TextRows(header, binary).rowFlow() - private fun readAt(offset: Int): Row { + private fun readAt(offset: Int): Row<Value> { return binary.read(offset) { val line = readUtf8Line() return@read readLine(header, line) } } - override fun <T : Any> getValue(row: Int, column: String, type: KClass<out T>): T? { + override fun <T : Value> getValue(row: Int, column: String, type: KClass<out T>): T? { val offset = index[row] return type.cast(readAt(offset)[column]) } + + companion object { + suspend operator fun invoke(header: ValueTableHeader, binary: RandomAccessBinary): TextTable { + val index = TextRows(header, binary).buildRowIndex() + return TextTable(header, binary, index) + } + } } -fun Output.writeValue(value: Value, width: Int, left: Boolean = true) { + +/** + * Write a fixed width value to the output + */ +private fun Output.writeValue(value: Value, width: Int, left: Boolean = true) { require(width > 5) { "Width could not be less than 5" } val str: String = when (value.type) { ValueType.NUMBER -> value.number.toString() //TODO apply decimal format @@ -90,31 +130,20 @@ fun Output.writeValue(value: Value, width: Int, left: Boolean = true) { writeUtf8String(padded) } -val ColumnHeader<Value>.valueType: ValueType? get() = meta["valueType"].string?.let { ValueType.valueOf(it) } - -private val ColumnHeader<Value>.width: Int - get() = meta["columnWidth"].int ?: when (valueType) { - ValueType.NUMBER -> 8 - ValueType.STRING -> 16 - ValueType.BOOLEAN -> 5 - ValueType.NULL -> 5 - null -> 16 - } - - /** * Write rows without header to the output */ -suspend fun Output.writeRows(rows: Rows) { +suspend fun Output.writeRows(rows: Rows<Value>) { @Suppress("UNCHECKED_CAST") val header = rows.header.map { if (it.type != Value::class) error("Expected Value column, but found ${it.type}") else (it as ColumnHeader<Value>) } val widths: List<Int> = header.map { - it.width + it.textWidth } rows.rowFlow().collect { row -> header.forEachIndexed { index, columnHeader -> writeValue(row[columnHeader] ?: Null, widths[index]) } + writeUtf8String("\r\n") } } \ No newline at end of file diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/textTableEnvelope.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/textTableEnvelope.kt new file mode 100644 index 00000000..29ce27b2 --- /dev/null +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/textTableEnvelope.kt @@ -0,0 +1,42 @@ +package hep.dataforge.tables.io + +import hep.dataforge.io.Envelope +import hep.dataforge.meta.* +import hep.dataforge.tables.SimpleColumnHeader +import hep.dataforge.tables.Table +import hep.dataforge.values.Value +import kotlinx.io.ByteArrayOutput +import kotlinx.io.EmptyBinary +import kotlinx.io.ExperimentalIoApi +import kotlinx.io.asBinary + + +@ExperimentalIoApi +suspend fun Table<Value>.wrap(): Envelope = Envelope { + meta { + header.forEachIndexed { index, columnHeader -> + set("column", index.toString(), buildMeta { + "name" put columnHeader.name + if (!columnHeader.meta.isEmpty()) { + "meta" put columnHeader.meta + } + }) + } + } + + type = "table.value" + dataID = "valueTable[${this@wrap.hashCode()}]" + + data = ByteArrayOutput().apply { writeRows(this@wrap) }.toByteArray().asBinary() +} + +@DFExperimental +@ExperimentalIoApi +fun TextRows.Companion.readEnvelope(envelope: Envelope): TextRows { + val header = envelope.meta.getIndexed("column") + .entries.sortedBy { it.key.toInt() } + .map { (_, item) -> + SimpleColumnHeader(item.node["name"].string!!, Value::class, item.node["meta"].node ?: Meta.EMPTY) + } + return TextRows(header, envelope.data ?: EmptyBinary) +} \ No newline at end of file diff --git a/dataforge-tables/src/commonTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt b/dataforge-tables/src/commonTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt deleted file mode 100644 index 010f903f..00000000 --- a/dataforge-tables/src/commonTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt +++ /dev/null @@ -1,6 +0,0 @@ -package hep.dataforge.tables.io - - -class TextRowsTest{ - -} \ No newline at end of file diff --git a/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt b/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt index 1ac7e138..a0bcb75e 100644 --- a/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt +++ b/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt @@ -8,7 +8,7 @@ import kotlin.reflect.full.cast import kotlin.reflect.full.isSubclassOf @Suppress("UNCHECKED_CAST") -fun <T : Any> Column<*>.cast(type: KClass<T>): Column<T> { +fun <T : Any> Column<*>.cast(type: KClass<out T>): Column<T> { return if (type.isSubclassOf(this.type)) { this as Column<T> } else { @@ -16,7 +16,7 @@ fun <T : Any> Column<*>.cast(type: KClass<T>): Column<T> { } } -class CastColumn<T : Any>(val origin: Column<*>, override val type: KClass<T>) : Column<T> { +class CastColumn<T : Any>(val origin: Column<*>, override val type: KClass<out T>) : Column<T> { override val name: String get() = origin.name override val meta: Meta get() = origin.meta override val size: Int get() = origin.size @@ -32,5 +32,5 @@ class ColumnProperty<C: Any, T : C>(val table: Table<C>, val type: KClass<T>) : } } -operator fun <T : Any> Collection<Column<*>>.get(header: ColumnHeader<T>): Column<T>? = +operator fun <C: Any, T : C> Collection<Column<C>>.get(header: ColumnHeader<T>): Column<T>? = find { it.name == header.name }?.cast(header.type) diff --git a/dataforge-tables/src/jvmTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt b/dataforge-tables/src/jvmTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt new file mode 100644 index 00000000..02d97caf --- /dev/null +++ b/dataforge-tables/src/jvmTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt @@ -0,0 +1,39 @@ +package hep.dataforge.tables.io + +import hep.dataforge.meta.DFExperimental +import hep.dataforge.tables.Table +import hep.dataforge.tables.get +import hep.dataforge.tables.row +import hep.dataforge.values.Value +import hep.dataforge.values.int +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.runBlocking +import kotlinx.io.ExperimentalIoApi +import kotlinx.io.toByteArray +import kotlin.test.Test +import kotlin.test.assertEquals + + +@DFExperimental +@ExperimentalIoApi +class TextRowsTest { + val table = Table<Value> { + val a = column<Value>("a") + val b = column<Value>("b") + row(a to 1, b to "b1") + row(a to 2, b to "b2") + } + + @Test + fun testTableWriteRead() { + runBlocking { + val envelope = table.wrap() + val string = envelope.data!!.toByteArray().decodeToString() + println(string) + val table = TextRows.readEnvelope(envelope) + val rows = table.rowFlow().toList() + assertEquals(1, rows[0]["a"]?.int) + assertEquals("b2", rows[1]["b"]?.string) + } + } +} \ No newline at end of file From 8dfd56f02ec2b3cc4527d33af9753a08c6a27feb Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Mon, 17 Feb 2020 14:46:03 +0300 Subject: [PATCH 32/41] Initial API --- build.gradle.kts | 2 +- .../hep/dataforge/meta/MetaDelegateTest.kt | 28 ++++++++++--------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 14ccd3e9..82d402e2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("scientifik.publish") version toolsVersion apply false } -val dataforgeVersion by extra("0.1.5-dev-8") +val dataforgeVersion by extra("0.1.5-dev-9") val bintrayRepo by extra("dataforge") val githubProject by extra("dataforge-core") diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt index 2461c363..4cf2002f 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt @@ -11,25 +11,27 @@ class MetaDelegateTest { NO } + class InnerSpec : Scheme() { + var innerValue by string() + companion object: SchemeSpec<InnerSpec>(::InnerSpec) + } + + class TestScheme : Scheme() { + var myValue by string() + var safeValue by double(2.2) + var enumValue by enum(TestEnum.YES) + var inner by spec(InnerSpec) + companion object: SchemeSpec<TestScheme>(::TestScheme) + } + @Test fun delegateTest() { - class InnerSpec : Scheme() { - var innerValue by string() - } - - val innerSpec = object : SchemeSpec<InnerSpec>(::InnerSpec){} - - val testObject = object : Scheme(Config()) { - var myValue by string() - var safeValue by double(2.2) - var enumValue by enum(TestEnum.YES) - var inner by spec(innerSpec) - } + val testObject = TestScheme.empty() testObject.config["myValue"] = "theString" testObject.enumValue = TestEnum.NO - testObject.inner = innerSpec { innerValue = "ddd" } + testObject.inner = InnerSpec { innerValue = "ddd" } assertEquals("theString", testObject.myValue) assertEquals(TestEnum.NO, testObject.enumValue) From a2e8d4f018de3b847df7fb6cd1489816eb4bf24d Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Sat, 22 Feb 2020 22:02:58 +0300 Subject: [PATCH 33/41] Basic design for property builders --- .../kotlin/hep/dataforge/meta/MetaBuilder.kt | 7 ++ .../meta/transformations/MetaCaster.kt | 75 +++++++++++++++++++ .../hep/dataforge/values/valueExtensions.kt | 3 +- .../kotlin/hep/dataforge/tables/Table.kt | 2 +- 4 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaCaster.kt diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaBuilder.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaBuilder.kt index c5ad3831..243d40bf 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaBuilder.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaBuilder.kt @@ -141,8 +141,15 @@ fun Meta.edit(builder: MetaBuilder.() -> Unit): MetaBuilder = builder().apply(bu /** * Build a [MetaBuilder] using given transformation */ +@Deprecated("To be replaced with fake constructor", ReplaceWith("Meta")) fun buildMeta(builder: MetaBuilder.() -> Unit): MetaBuilder = MetaBuilder().apply(builder) +/** + * Build a [MetaBuilder] using given transformation + */ +@Suppress("FunctionName") +fun Meta(builder: MetaBuilder.() -> Unit): MetaBuilder = MetaBuilder().apply(builder) + /** * Build meta using given source meta as a base */ diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaCaster.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaCaster.kt new file mode 100644 index 00000000..21785980 --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaCaster.kt @@ -0,0 +1,75 @@ +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.values.* + +/** + * A converter of generic object to and from [MetaItem] + */ +interface MetaCaster<T : Any> { + fun itemToObject(item: MetaItem<*>): T + fun objectToMetaItem(obj: T): MetaItem<*> + + companion object { + + val meta = object : MetaCaster<Meta> { + override fun itemToObject(item: MetaItem<*>): Meta = when (item) { + is MetaItem.NodeItem -> item.node + is MetaItem.ValueItem -> item.value.toMeta() + } + + override fun objectToMetaItem(obj: Meta): MetaItem<*> = MetaItem.NodeItem(obj) + } + + val value = object : MetaCaster<Value> { + override fun itemToObject(item: MetaItem<*>): Value = when (item) { + is MetaItem.NodeItem -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value") + is MetaItem.ValueItem -> item.value + } + + override fun objectToMetaItem(obj: Value): MetaItem<*> = MetaItem.ValueItem(obj) + } + + val string = object : MetaCaster<String> { + override fun itemToObject(item: MetaItem<*>): String = when (item) { + is MetaItem.NodeItem -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value") + is MetaItem.ValueItem -> item.value + }.string + + override fun objectToMetaItem(obj: String): MetaItem<*> = MetaItem.ValueItem(obj.asValue()) + } + + val boolean = object : MetaCaster<Boolean> { + override fun itemToObject(item: MetaItem<*>): Boolean = when (item) { + is MetaItem.NodeItem -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value") + is MetaItem.ValueItem -> item.value + }.boolean + + override fun objectToMetaItem(obj: Boolean): MetaItem<*> = MetaItem.ValueItem(obj.asValue()) + } + + val double = object : MetaCaster<Double> { + 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") + is MetaItem.ValueItem -> item.value + }.double + + override fun objectToMetaItem(obj: Double): MetaItem<*> = MetaItem.ValueItem(obj.asValue()) + } + + val int = object : MetaCaster<Int> { + 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") + is MetaItem.ValueItem -> item.value + }.int + + override fun objectToMetaItem(obj: Int): MetaItem<*> = MetaItem.ValueItem(obj.asValue()) + } + } +} + +fun <T : Any> MetaCaster<T>.metaToObject(meta: Meta): T = itemToObject(MetaItem.NodeItem(meta)) +fun <T : Any> MetaCaster<T>.valueToObject(value: Value): T = itemToObject(MetaItem.ValueItem(value)) diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/valueExtensions.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/valueExtensions.kt index f9137dca..3767e2fb 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/valueExtensions.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/valueExtensions.kt @@ -1,7 +1,6 @@ package hep.dataforge.values import hep.dataforge.meta.Meta -import hep.dataforge.meta.buildMeta /** * Check if value is null @@ -35,4 +34,4 @@ val Value.doubleArray: DoubleArray } -fun Value.toMeta() = buildMeta { Meta.VALUE_KEY put this } \ No newline at end of file +fun Value.toMeta() = Meta { Meta.VALUE_KEY put this } \ No newline at end of file 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 71469984..bba90d38 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt @@ -45,7 +45,7 @@ operator fun Collection<Column<*>>.get(name: String): Column<*>? = find { it.nam inline operator fun <C : Any, reified T : C> Table<C>.get(row: Int, column: String): T? = getValue(row, column, T::class) -operator fun <C : Any, T : C> Table<C>.get(row: Int, column: Column<T>): T? = getValue(row, column.name, column.type) +operator fun <C : Any, T : C> Table<C>.get(row: Int, column: ColumnHeader<T>): T? = getValue(row, column.name, column.type) interface Column<T : Any> : ColumnHeader<T> { val size: Int From 93c806c3bfe5a9d7a9a43d6c8af92502161f4045 Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Mon, 2 Mar 2020 19:17:36 +0300 Subject: [PATCH 34/41] Basic design for external connectors --- .../kotlin/hep/dataforge/context/PluginManager.kt | 2 +- .../kotlin/hep/dataforge/meta/MetaBuilder.kt | 4 ++++ .../kotlin/hep/dataforge/meta/MutableMetaDelegate.kt | 12 +++++++++++- .../kotlin/hep/dataforge/meta/scheme/Scheme.kt | 3 +-- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginManager.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginManager.kt index dd114f79..afac0392 100644 --- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginManager.kt +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginManager.kt @@ -62,7 +62,7 @@ class PluginManager(override val context: Context) : ContextAware, Iterable<Plug * @return */ @Suppress("UNCHECKED_CAST") - operator fun <T : Any> get(type: KClass<T>, tag: PluginTag? = null, recursive: Boolean = true): T? = + operator fun <T : Any> get(type: KClass<out T>, tag: PluginTag? = null, recursive: Boolean = true): T? = find(recursive) { type.isInstance(it) && (tag == null || tag.matches(it.tag)) } as T? inline operator fun <reified T : Any> get(tag: PluginTag? = null, recursive: Boolean = true): T? = diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaBuilder.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaBuilder.kt index 243d40bf..b36b3316 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaBuilder.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaBuilder.kt @@ -15,6 +15,10 @@ class MetaBuilder : AbstractMutableMeta<MetaBuilder>() { override fun wrapNode(meta: Meta): MetaBuilder = if (meta is MetaBuilder) meta else meta.builder() override fun empty(): MetaBuilder = MetaBuilder() + infix fun String.put(item: MetaItem<*>?) { + set(this, item) + } + infix fun String.put(value: Value?) { set(this, value) } diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMetaDelegate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMetaDelegate.kt index 5ab6ba0c..3fcdc9da 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMetaDelegate.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMetaDelegate.kt @@ -49,9 +49,19 @@ fun <T, R> ReadWriteProperty<Any?, T>.map(reader: (T) -> R, writer: (R) -> T): R fun <R> ReadWriteProperty<Any?, MetaItem<*>?>.transform(reader: (MetaItem<*>?) -> R): ReadWriteProperty<Any?, R> = map(reader = reader, writer = { MetaItem.of(it) }) -fun <R> ReadWriteProperty<Any?, Value?>.transform(reader: (Value?) -> R) = +fun <R> ReadWriteProperty<Any?, Value?>.transform(reader: (Value?) -> R): ReadWriteDelegateWrapper<Value?, R> = map(reader = reader, writer = { Value.of(it) }) +/** + * A delegate that throws + */ +fun <R : Any> ReadWriteProperty<Any?, R?>.notNull(default: () -> R): ReadWriteProperty<Any?, R> { + return ReadWriteDelegateWrapper(this, + reader = { it ?: default() }, + writer = { it } + ) +} + fun <M : MutableMeta<M>> M.item(default: Any? = null, key: Name? = null): MutableMetaDelegate<M> = MutableMetaDelegate(this, key, default?.let { MetaItem.of(it) }) diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt index 7ac49de0..78d0a511 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt @@ -59,8 +59,7 @@ open class Scheme() : Configurable, Described, MetaRepr { /** * A specification for simplified generation of wrappers */ -open class SchemeSpec<T : Scheme>(val builder: () -> T) : - Specification<T> { +open class SchemeSpec<T : Scheme>(val builder: () -> T) : Specification<T> { override fun wrap(config: Config, defaultProvider: (Name) -> MetaItem<*>?): T { return builder().apply { this.config = config From e2845f4efc1217bc7d50c97ad35ac8755e1e14be Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Wed, 11 Mar 2020 22:30:29 +0300 Subject: [PATCH 35/41] Move Meta serialization to dataforge-meta --- build.gradle.kts | 4 +- dataforge-context/build.gradle.kts | 6 +- .../kotlin/hep/dataforge/context/Context.kt | 2 +- .../hep/dataforge/context/ContextBuilder.kt | 5 +- .../kotlin/hep/dataforge/context/Plugin.kt | 2 +- .../hep/dataforge/context/PluginManager.kt | 4 +- .../kotlin/hep/dataforge/context/PluginTag.kt | 2 +- .../kotlin/hep/dataforge/data/Data.kt | 2 +- .../kotlin/hep/dataforge/data/DataNode.kt | 6 +- .../kotlin/hep/dataforge/io/JsonMetaFormat.kt | 131 +--------------- .../hep/dataforge/io/TaglessEnvelopeFormat.kt | 6 +- .../io/serialization/MetaSerializer.kt | 142 ------------------ .../io/serialization/nameSerializers.kt | 33 ---- .../io/serialization/serializationUtils.kt | 93 ------------ dataforge-meta/build.gradle.kts | 4 + .../kotlin/hep/dataforge/meta/Laminate.kt | 2 +- .../kotlin/hep/dataforge/meta/Meta.kt | 7 +- .../kotlin/hep/dataforge/meta/MetaBuilder.kt | 7 +- .../kotlin/hep/dataforge/meta/annotations.kt | 2 +- .../meta/descriptors/ItemDescriptor.kt | 8 +- .../dataforge/meta/serialization/JsonMeta.kt | 135 +++++++++++++++++ .../meta/serialization/MetaSerializer.kt | 110 ++++++++++++++ .../meta/serialization/serializationUtils.kt | 67 +++++++++ .../transformations/MetaTransformation.kt | 8 +- .../kotlin/hep/dataforge/names/Name.kt | 30 +++- .../kotlin/hep/dataforge/values/Value.kt | 3 + 26 files changed, 392 insertions(+), 429 deletions(-) delete mode 100644 dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt delete mode 100644 dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/nameSerializers.kt delete mode 100644 dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/serializationUtils.kt create mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/JsonMeta.kt create mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/MetaSerializer.kt create mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/serializationUtils.kt diff --git a/build.gradle.kts b/build.gradle.kts index 82d402e2..0d4cc1fb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,12 +1,12 @@ plugins { - val toolsVersion = "0.3.1" + val toolsVersion = "0.4.0" id("scientifik.mpp") version toolsVersion apply false id("scientifik.jvm") version toolsVersion apply false id("scientifik.publish") version toolsVersion apply false } -val dataforgeVersion by extra("0.1.5-dev-9") +val dataforgeVersion by extra("0.1.5-dev-10") val bintrayRepo by extra("dataforge") val githubProject by extra("dataforge-core") diff --git a/dataforge-context/build.gradle.kts b/dataforge-context/build.gradle.kts index 896e7b89..104f4037 100644 --- a/dataforge-context/build.gradle.kts +++ b/dataforge-context/build.gradle.kts @@ -12,20 +12,20 @@ kotlin { dependencies { api(project(":dataforge-meta")) api(kotlin("reflect")) - api("io.github.microutils:kotlin-logging-common:1.7.2") + api("io.github.microutils:kotlin-logging-common:1.7.8") api("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$coroutinesVersion") } } val jvmMain by getting { dependencies { - api("io.github.microutils:kotlin-logging:1.7.2") + api("io.github.microutils:kotlin-logging:1.7.8") api("ch.qos.logback:logback-classic:1.2.3") api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") } } val jsMain by getting { dependencies { - api("io.github.microutils:kotlin-logging-js:1.7.2") + api("io.github.microutils:kotlin-logging-js:1.7.8") api("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$coroutinesVersion") } } diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Context.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Context.kt index ffc5b197..b3adcbfd 100644 --- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Context.kt +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Context.kt @@ -103,7 +103,7 @@ open class Context( plugins.forEach { it.detach() } } - override fun toMeta(): Meta = buildMeta { + override fun toMeta(): Meta = Meta { "parent" to parent?.name "properties" put properties.seal() "plugins" put plugins.map { it.toMeta() } diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/ContextBuilder.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/ContextBuilder.kt index 0aed9b7f..1f267c37 100644 --- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/ContextBuilder.kt +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/ContextBuilder.kt @@ -1,6 +1,7 @@ package hep.dataforge.context import hep.dataforge.meta.DFBuilder +import hep.dataforge.meta.Meta import hep.dataforge.meta.MetaBuilder import hep.dataforge.meta.buildMeta import hep.dataforge.names.toName @@ -22,11 +23,11 @@ class ContextBuilder(var name: String = "@anonymous", val parent: Context = Glob } fun plugin(tag: PluginTag, action: MetaBuilder.() -> Unit = {}) { - plugins.add(PluginRepository.fetch(tag, buildMeta(action))) + plugins.add(PluginRepository.fetch(tag, Meta(action))) } fun plugin(builder: PluginFactory<*>, action: MetaBuilder.() -> Unit = {}) { - plugins.add(builder.invoke(buildMeta(action))) + plugins.add(builder.invoke(Meta(action))) } fun plugin(name: String, group: String = "", version: String = "", action: MetaBuilder.() -> Unit = {}) { diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Plugin.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Plugin.kt index 90d669e0..6a937962 100644 --- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Plugin.kt +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Plugin.kt @@ -65,7 +65,7 @@ interface Plugin : Named, ContextAware, Provider, MetaRepr { */ fun detach() - override fun toMeta(): Meta = buildMeta { + override fun toMeta(): Meta = Meta { "context" put context.name.toString() "type" to this::class.simpleName "tag" put tag diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginManager.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginManager.kt index afac0392..5f35d876 100644 --- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginManager.kt +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginManager.kt @@ -102,7 +102,7 @@ class PluginManager(override val context: Context) : ContextAware, Iterable<Plug load(factory(meta, context)) fun <T : Plugin> load(factory: PluginFactory<T>, metaBuilder: MetaBuilder.() -> Unit): T = - load(factory, buildMeta(metaBuilder)) + load(factory, Meta(metaBuilder)) /** * Remove a plugin from [PluginManager] @@ -134,7 +134,7 @@ class PluginManager(override val context: Context) : ContextAware, Iterable<Plug factory: PluginFactory<T>, recursive: Boolean = true, metaBuilder: MetaBuilder.() -> Unit - ): T = fetch(factory, recursive, buildMeta(metaBuilder)) + ): T = fetch(factory, recursive, Meta(metaBuilder)) override fun iterator(): Iterator<Plugin> = plugins.iterator() diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginTag.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginTag.kt index 390de7bc..f02308fd 100644 --- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginTag.kt +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/PluginTag.kt @@ -36,7 +36,7 @@ data class PluginTag( override fun toString(): String = listOf(group, name, version).joinToString(separator = ":") - override fun toMeta(): Meta = buildMeta { + override fun toMeta(): Meta = Meta { "name" put name "group" put group "version" put version diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Data.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Data.kt index ea25e070..4d787b69 100644 --- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Data.kt +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/Data.kt @@ -19,7 +19,7 @@ interface Data<out T : Any> : Goal<T>, MetaRepr{ */ val meta: Meta - override fun toMeta(): Meta = buildMeta { + override fun toMeta(): Meta = Meta { "type" put (type.simpleName?:"undefined") if(!meta.isEmpty()) { "meta" put meta diff --git a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt index 764aea62..ee8d9c4d 100644 --- a/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt +++ b/dataforge-data/src/commonMain/kotlin/hep/dataforge/data/DataNode.kt @@ -47,7 +47,7 @@ interface DataNode<out T : Any> : MetaRepr { val meta: Meta - override fun toMeta(): Meta = buildMeta { + override fun toMeta(): Meta = Meta { "type" put (type.simpleName ?: "undefined") "items" put { this@DataNode.items.forEach { @@ -255,11 +255,11 @@ fun <T : Any> DataTreeBuilder<T>.static(name: Name, data: T, meta: Meta = EmptyM } fun <T : Any> DataTreeBuilder<T>.static(name: Name, data: T, block: MetaBuilder.() -> Unit = {}) { - this[name] = Data.static(data, buildMeta(block)) + this[name] = Data.static(data, Meta(block)) } fun <T : Any> DataTreeBuilder<T>.static(name: String, data: T, block: MetaBuilder.() -> Unit = {}) { - this[name.toName()] = Data.static(data, buildMeta(block)) + this[name.toName()] = Data.static(data, Meta(block)) } fun <T : Any> DataTreeBuilder<T>.node(name: Name, node: DataNode<T>) { diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt index 8ec244a5..1101b35f 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt @@ -9,6 +9,8 @@ import hep.dataforge.meta.descriptors.ValueDescriptor import hep.dataforge.meta.Meta import hep.dataforge.meta.MetaBase import hep.dataforge.meta.MetaItem +import hep.dataforge.meta.serialization.toJson +import hep.dataforge.meta.serialization.toMeta import hep.dataforge.names.NameToken import hep.dataforge.names.toName import hep.dataforge.values.* @@ -16,6 +18,7 @@ import kotlinx.io.Input import kotlinx.io.Output import kotlinx.io.text.readUtf8String import kotlinx.io.text.writeUtf8String +import kotlinx.serialization.UnstableDefault import kotlinx.serialization.json.* @@ -23,8 +26,8 @@ import kotlin.collections.component1 import kotlin.collections.component2 import kotlin.collections.set - -class JsonMetaFormat(private val json: Json = Json.indented) : MetaFormat { +@OptIn(UnstableDefault::class) +class JsonMetaFormat(private val json: Json = DEFAULT_JSON) : MetaFormat { override fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor?) { val jsonObject = meta.toJson(descriptor) @@ -38,6 +41,8 @@ class JsonMetaFormat(private val json: Json = Json.indented) : MetaFormat { } companion object : MetaFormatFactory { + val DEFAULT_JSON = Json{prettyPrint = true} + override fun invoke(meta: Meta, context: Context): MetaFormat = default override val shortName = "json" @@ -52,125 +57,3 @@ class JsonMetaFormat(private val json: Json = Json.indented) : MetaFormat { default.run { readMeta(descriptor) } } } - -/** - * @param descriptor reserved for custom serialization in future - */ -fun Value.toJson(descriptor: ValueDescriptor? = null): JsonElement { - return if (isList()) { - JsonArray(list.map { it.toJson() }) - } else { - when (type) { - ValueType.NUMBER -> JsonPrimitive(number) - ValueType.STRING -> JsonPrimitive(string) - ValueType.BOOLEAN -> JsonPrimitive(boolean) - ValueType.NULL -> JsonNull - } - } -} - -//Use these methods to customize JSON key mapping -private fun NameToken.toJsonKey(descriptor: ItemDescriptor?) = toString() - -//private fun NodeDescriptor?.getDescriptor(key: String) = this?.items?.get(key) - -fun Meta.toJson(descriptor: NodeDescriptor? = null): JsonObject { - - //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 JsonElement.toMeta(descriptor: NodeDescriptor? = null): Meta { - return when (val item = toMetaItem(descriptor)) { - is MetaItem.NodeItem<*> -> item.node - is MetaItem.ValueItem -> item.value.toMeta() - } -} - -fun JsonPrimitive.toValue(descriptor: ValueDescriptor?): Value { - return when (this) { - JsonNull -> Null - else -> this.content.parseValue() // Optimize number and boolean parsing - } -} - -fun JsonElement.toMetaItem(descriptor: ItemDescriptor? = null): MetaItem<JsonMeta> = when (this) { - is JsonPrimitive -> { - val value = this.toValue(descriptor as? ValueDescriptor) - MetaItem.ValueItem(value) - } - is JsonObject -> { - val meta = JsonMeta(this, descriptor as? NodeDescriptor) - MetaItem.NodeItem(meta) - } - is JsonArray -> { - if (this.all { it is JsonPrimitive }) { - val value = if (isEmpty()) { - Null - } else { - ListValue( - map<JsonElement, Value> { - //We already checked that all values are primitives - (it as JsonPrimitive).toValue(descriptor as? ValueDescriptor) - } - ) - } - MetaItem.ValueItem(value) - } else { - json { - "@value" to this@toMetaItem - }.toMetaItem(descriptor) - } - } -} - -class JsonMeta(val json: JsonObject, val descriptor: NodeDescriptor? = null) : MetaBase() { - - @Suppress("UNCHECKED_CAST") - private operator fun MutableMap<String, MetaItem<JsonMeta>>.set(key: String, value: JsonElement): Unit { - val itemDescriptor = descriptor?.items?.get(key) - return when (value) { - is JsonPrimitive -> { - this[key] = MetaItem.ValueItem(value.toValue(itemDescriptor as? ValueDescriptor)) as MetaItem<JsonMeta> - } - 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) - } - ) - this[key] = MetaItem.ValueItem(listValue) as MetaItem<JsonMeta> - } - else -> value.forEachIndexed { index, jsonElement -> - this["$key[$index]"] = jsonElement.toMetaItem(itemDescriptor) - } - } - } - } - } - - override val items: Map<NameToken, MetaItem<JsonMeta>> by lazy { - val map = HashMap<String, MetaItem<JsonMeta>>() - json.forEach { (key, value) -> map[key] = value } - map.mapKeys { it.key.toName().first()!! } - } -} \ No newline at end of file diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt index 0ed5214b..ebeedd6d 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt @@ -119,7 +119,7 @@ class TaglessEnvelopeFormat( var line: String do { line = readUtf8Line()// ?: error("Input does not contain tagless envelope header") - offset += line.toUtf8Bytes().size.toUInt() + offset += line.encodeToByteArray().size.toUInt() } while (!line.startsWith(TAGLESS_ENVELOPE_HEADER)) val properties = HashMap<String, String>() @@ -133,7 +133,7 @@ class TaglessEnvelopeFormat( } try { line = readUtf8Line() - offset += line.toUtf8Bytes().size.toUInt() + offset += line.encodeToByteArray().size.toUInt() } catch (ex: EOFException) { return PartialEnvelope(Meta.EMPTY, offset.toUInt(), 0.toULong()) } @@ -156,7 +156,7 @@ class TaglessEnvelopeFormat( do { line = readUtf8Line() ?: return PartialEnvelope(Meta.EMPTY, offset.toUInt(), 0.toULong()) - offset += line.toUtf8Bytes().size.toUInt() + offset += line.encodeToByteArray().size.toUInt() //returning an Envelope without data if end of input is reached } while (!line.startsWith(dataStart)) diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt deleted file mode 100644 index ef989869..00000000 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/MetaSerializer.kt +++ /dev/null @@ -1,142 +0,0 @@ -package hep.dataforge.io.serialization - -import hep.dataforge.io.toJson -import hep.dataforge.io.toMeta -import hep.dataforge.meta.* -import hep.dataforge.names.NameToken -import hep.dataforge.values.* -import kotlinx.serialization.* -import kotlinx.serialization.internal.* -import kotlinx.serialization.json.JsonInput -import kotlinx.serialization.json.JsonObjectSerializer -import kotlinx.serialization.json.JsonOutput - - -@Serializer(Value::class) -@UseExperimental(InternalSerializationApi::class) -object ValueSerializer : KSerializer<Value> { - private val valueTypeSerializer = EnumSerializer(ValueType::class) - private val listSerializer by lazy { ArrayListSerializer(ValueSerializer) } - - override val descriptor: SerialDescriptor = descriptor("Value") { - boolean("isList") - enum<ValueType>("valueType") - element("value", null) - } - - private fun Decoder.decodeValue(): Value { - return when (decode(valueTypeSerializer)) { - ValueType.NULL -> Null - ValueType.NUMBER -> decodeDouble().asValue() //TODO differentiate? - ValueType.BOOLEAN -> decodeBoolean().asValue() - ValueType.STRING -> decodeString().asValue() - else -> decodeString().parseValue() - } - } - - - override fun deserialize(decoder: Decoder): Value { - val isList = decoder.decodeBoolean() - return if (isList) { - listSerializer.deserialize(decoder).asValue() - } else { - decoder.decodeValue() - } - } - - private fun Encoder.encodeValue(value: Value) { - encode(valueTypeSerializer, value.type) - when (value.type) { - ValueType.NULL -> { - // do nothing - } - ValueType.NUMBER -> encodeDouble(value.double) - ValueType.BOOLEAN -> encodeBoolean(value.boolean) - ValueType.STRING -> encodeString(value.string) - else -> encodeString(value.string) - } - } - - override fun serialize(encoder: Encoder, obj: Value) { - encoder.encodeBoolean(obj.isList()) - if (obj.isList()) { - listSerializer.serialize(encoder, obj.list) - } else { - encoder.encodeValue(obj) - } - } -} - -@Serializer(MetaItem::class) -object MetaItemSerializer : KSerializer<MetaItem<*>> { - override val descriptor: SerialDescriptor = descriptor("MetaItem") { - boolean("isNode") - element("value", null) - } - - - override fun deserialize(decoder: Decoder): MetaItem<*> { - val isNode = decoder.decodeBoolean() - return if (isNode) { - MetaItem.NodeItem(decoder.decode(MetaSerializer)) - } else { - MetaItem.ValueItem(decoder.decode(ValueSerializer)) - } - } - - override fun serialize(encoder: Encoder, obj: MetaItem<*>) { - encoder.encodeBoolean(obj is MetaItem.NodeItem) - when (obj) { - is MetaItem.NodeItem -> MetaSerializer.serialize(encoder, obj.node) - is MetaItem.ValueItem -> ValueSerializer.serialize(encoder, obj.value) - } - } -} - -private class DeserializedMeta(override val items: Map<NameToken, MetaItem<*>>) : MetaBase() - -/** - * Serialized for meta - */ -@Serializer(Meta::class) -object MetaSerializer : KSerializer<Meta> { - private val mapSerializer = HashMapSerializer( - StringSerializer, - MetaItemSerializer - ) - - override val descriptor: SerialDescriptor = NamedMapClassDescriptor( - "hep.dataforge.meta.Meta", - StringSerializer.descriptor, - MetaItemSerializer.descriptor - ) - - override fun deserialize(decoder: Decoder): Meta { - return if (decoder is JsonInput) { - JsonObjectSerializer.deserialize(decoder).toMeta() - } else { - DeserializedMeta(mapSerializer.deserialize(decoder).mapKeys { NameToken(it.key) }) - } - } - - override fun serialize(encoder: Encoder, obj: Meta) { - if (encoder is JsonOutput) { - JsonObjectSerializer.serialize(encoder, obj.toJson()) - } else { - mapSerializer.serialize(encoder, obj.items.mapKeys { it.key.toString() }) - } - } -} - -@Serializer(Config::class) -object ConfigSerializer : KSerializer<Config> { - override val descriptor: SerialDescriptor = MetaSerializer.descriptor - - override fun deserialize(decoder: Decoder): Config { - return MetaSerializer.deserialize(decoder).asConfig() - } - - override fun serialize(encoder: Encoder, obj: Config) { - MetaSerializer.serialize(encoder, obj) - } -} \ No newline at end of file diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/nameSerializers.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/nameSerializers.kt deleted file mode 100644 index c12a6c19..00000000 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/nameSerializers.kt +++ /dev/null @@ -1,33 +0,0 @@ -package hep.dataforge.io.serialization - -import hep.dataforge.names.Name -import hep.dataforge.names.NameToken -import hep.dataforge.names.toName -import kotlinx.serialization.* -import kotlinx.serialization.internal.StringDescriptor - -@Serializer(Name::class) -object NameSerializer : KSerializer<Name> { - override val descriptor: SerialDescriptor = StringDescriptor.withName("Name") - - override fun deserialize(decoder: Decoder): Name { - return decoder.decodeString().toName() - } - - override fun serialize(encoder: Encoder, obj: Name) { - encoder.encodeString(obj.toString()) - } -} - -@Serializer(NameToken::class) -object NameTokenSerializer : KSerializer<NameToken> { - override val descriptor: SerialDescriptor = StringDescriptor.withName("NameToken") - - override fun deserialize(decoder: Decoder): NameToken { - return decoder.decodeString().toName().first()!! - } - - override fun serialize(encoder: Encoder, obj: NameToken) { - encoder.encodeString(obj.toString()) - } -} \ No newline at end of file diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/serializationUtils.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/serializationUtils.kt deleted file mode 100644 index 18e28423..00000000 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/serialization/serializationUtils.kt +++ /dev/null @@ -1,93 +0,0 @@ -package hep.dataforge.io.serialization - -import hep.dataforge.meta.DFExperimental -import kotlinx.serialization.* -import kotlinx.serialization.internal.* - -/** - * A convenience builder for serial descriptors - */ -inline class SerialDescriptorBuilder(private val impl: SerialClassDescImpl) { - fun element( - name: String, - descriptor: SerialDescriptor?, - isOptional: Boolean = false, - vararg annotations: Annotation - ) { - impl.addElement(name, isOptional) - descriptor?.let { impl.pushDescriptor(descriptor) } - annotations.forEach { - impl.pushAnnotation(it) - } - } - - fun element( - name: String, - isOptional: Boolean = false, - vararg annotations: Annotation, - block: SerialDescriptorBuilder.() -> Unit - ) { - impl.addElement(name, isOptional) - impl.pushDescriptor(SerialDescriptorBuilder(SerialClassDescImpl(name)).apply(block).build()) - annotations.forEach { - impl.pushAnnotation(it) - } - } - - fun boolean(name: String, isOptional: Boolean = false, vararg annotations: Annotation) = - element(name, BooleanDescriptor, isOptional, *annotations) - - fun string(name: String, isOptional: Boolean = false, vararg annotations: Annotation) = - element(name, StringDescriptor, isOptional, *annotations) - - fun int(name: String, isOptional: Boolean = false, vararg annotations: Annotation) = - element(name, IntDescriptor, isOptional, *annotations) - - fun double(name: String, isOptional: Boolean = false, vararg annotations: Annotation) = - element(name, DoubleDescriptor, isOptional, *annotations) - - fun float(name: String, isOptional: Boolean = false, vararg annotations: Annotation) = - element(name, FloatDescriptor, isOptional, *annotations) - - fun long(name: String, isOptional: Boolean = false, vararg annotations: Annotation) = - element(name, LongDescriptor, isOptional, *annotations) - - fun doubleArray(name: String, isOptional: Boolean = false, vararg annotations: Annotation) = - element(name, DoubleArraySerializer.descriptor, isOptional, *annotations) - - @UseExperimental(InternalSerializationApi::class) - inline fun <reified E : Enum<E>> enum(name: String, isOptional: Boolean = false, vararg annotations: Annotation) = - element(name, EnumSerializer(E::class).descriptor, isOptional, *annotations) - - fun classAnnotation(a: Annotation) = impl.pushClassAnnotation(a) - - fun build(): SerialDescriptor = impl -} - -inline fun <reified T : Any> KSerializer<T>.descriptor( - name: String, - block: SerialDescriptorBuilder.() -> Unit -): SerialDescriptor = - SerialDescriptorBuilder(SerialClassDescImpl(name)).apply(block).build() - -@DFExperimental -inline fun <R> Decoder.decodeStructure( - desc: SerialDescriptor, - vararg typeParams: KSerializer<*> = emptyArray(), - crossinline block: CompositeDecoder.() -> R -): R { - val decoder = beginStructure(desc, *typeParams) - val res = decoder.block() - decoder.endStructure(desc) - return res -} - -inline fun Encoder.encodeStructure( - desc: SerialDescriptor, - vararg typeParams: KSerializer<*> = emptyArray(), - block: CompositeEncoder.() -> Unit -) { - val encoder = beginStructure(desc, *typeParams) - encoder.block() - encoder.endStructure(desc) -} \ No newline at end of file diff --git a/dataforge-meta/build.gradle.kts b/dataforge-meta/build.gradle.kts index 6f2a5160..f19a39fc 100644 --- a/dataforge-meta/build.gradle.kts +++ b/dataforge-meta/build.gradle.kts @@ -1,5 +1,9 @@ +import scientifik.useSerialization + plugins { id("scientifik.mpp") } +useSerialization() + description = "Meta definition and basic operations on meta" \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt index beda1732..7dec0790 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Laminate.kt @@ -64,7 +64,7 @@ class Laminate(layers: List<Meta>) : MetaBase() { } else -> map { when (it) { - is MetaItem.ValueItem -> MetaItem.NodeItem(buildMeta { Meta.VALUE_KEY put it.value }) + is MetaItem.ValueItem -> MetaItem.NodeItem(Meta { Meta.VALUE_KEY put it.value }) is MetaItem.NodeItem -> it } }.merge() 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 93e416de..b37188b4 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt @@ -3,11 +3,13 @@ package hep.dataforge.meta import hep.dataforge.meta.Meta.Companion.VALUE_KEY import hep.dataforge.meta.MetaItem.NodeItem import hep.dataforge.meta.MetaItem.ValueItem +import hep.dataforge.meta.serialization.toJson import hep.dataforge.names.* import hep.dataforge.values.EnumValue import hep.dataforge.values.Null import hep.dataforge.values.Value import hep.dataforge.values.boolean +import kotlinx.serialization.Serializable /** @@ -15,11 +17,14 @@ import hep.dataforge.values.boolean * * a [ValueItem] (leaf) * * a [NodeItem] (node) */ +@Serializable sealed class MetaItem<out M : Meta> { + @Serializable data class ValueItem(val value: Value) : MetaItem<Nothing>() { override fun toString(): String = value.toString() } + @Serializable data class NodeItem<M : Meta>(val node: M) : MetaItem<M>() { override fun toString(): String = node.toString() } @@ -161,7 +166,7 @@ abstract class MetaBase : Meta { override fun hashCode(): Int = items.hashCode() - override fun toString(): String = items.toString() + override fun toString(): String = toJson().toString() } /** diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaBuilder.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaBuilder.kt index b36b3316..da1bafad 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaBuilder.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaBuilder.kt @@ -152,9 +152,4 @@ fun buildMeta(builder: MetaBuilder.() -> Unit): MetaBuilder = MetaBuilder().appl * Build a [MetaBuilder] using given transformation */ @Suppress("FunctionName") -fun Meta(builder: MetaBuilder.() -> Unit): MetaBuilder = MetaBuilder().apply(builder) - -/** - * Build meta using given source meta as a base - */ -fun buildMeta(source: Meta, builder: MetaBuilder.() -> Unit): MetaBuilder = source.builder().apply(builder) \ No newline at end of file +fun Meta(builder: MetaBuilder.() -> Unit): MetaBuilder = MetaBuilder().apply(builder) \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/annotations.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/annotations.kt index 865ef800..2085bc5d 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/annotations.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/annotations.kt @@ -6,6 +6,6 @@ package hep.dataforge.meta @DslMarker annotation class DFBuilder -@Experimental(level = Experimental.Level.WARNING) +@RequiresOptIn(level = RequiresOptIn.Level.WARNING) @Retention(AnnotationRetention.BINARY) annotation class DFExperimental \ 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 9c5cf15f..4ff5de11 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 @@ -255,10 +255,10 @@ class ValueDescriptor : ItemDescriptor() { } companion object : SchemeSpec<ValueDescriptor>(::ValueDescriptor) { - inline fun <reified E : Enum<E>> enum(name: String) = ValueDescriptor { - type(ValueType.STRING) - this.allowedValues = enumValues<E>().map { Value.of(it.name) } - } +// inline fun <reified E : Enum<E>> enum(name: String) = ValueDescriptor { +// type(ValueType.STRING) +// this.allowedValues = enumValues<E>().map { Value.of(it.name) } +// } // /** // * Build a value descriptor from annotation diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/JsonMeta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/JsonMeta.kt new file mode 100644 index 00000000..78357c6d --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/JsonMeta.kt @@ -0,0 +1,135 @@ +package hep.dataforge.meta.serialization + +import hep.dataforge.meta.Meta +import hep.dataforge.meta.MetaBase +import hep.dataforge.meta.MetaItem +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.values.* +import kotlinx.serialization.json.* + + +/** + * @param descriptor reserved for custom serialization in future + */ +fun Value.toJson(descriptor: ValueDescriptor? = null): JsonElement { + return if (isList()) { + JsonArray(list.map { it.toJson() }) + } else { + when (type) { + ValueType.NUMBER -> JsonPrimitive(number) + ValueType.STRING -> JsonPrimitive(string) + ValueType.BOOLEAN -> JsonPrimitive(boolean) + ValueType.NULL -> JsonNull + } + } +} + +//Use these methods to customize JSON key mapping +private fun NameToken.toJsonKey(descriptor: ItemDescriptor?) = toString() + +//private fun NodeDescriptor?.getDescriptor(key: String) = this?.items?.get(key) + +fun Meta.toJson(descriptor: NodeDescriptor? = null): JsonObject { + + //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 JsonElement.toMeta(descriptor: NodeDescriptor? = null): Meta { + return when (val item = toMetaItem(descriptor)) { + is MetaItem.NodeItem<*> -> item.node + is MetaItem.ValueItem -> item.value.toMeta() + } +} + +fun JsonPrimitive.toValue(descriptor: ValueDescriptor?): Value { + return when (this) { + JsonNull -> Null + else -> this.content.parseValue() // Optimize number and boolean parsing + } +} + +fun JsonElement.toMetaItem(descriptor: ItemDescriptor? = null): MetaItem<JsonMeta> = when (this) { + is JsonPrimitive -> { + val value = this.toValue(descriptor as? ValueDescriptor) + MetaItem.ValueItem(value) + } + is JsonObject -> { + val meta = JsonMeta(this, descriptor as? NodeDescriptor) + MetaItem.NodeItem(meta) + } + is JsonArray -> { + if (this.all { it is JsonPrimitive }) { + val value = if (isEmpty()) { + Null + } else { + ListValue( + map<JsonElement, Value> { + //We already checked that all values are primitives + (it as JsonPrimitive).toValue(descriptor as? ValueDescriptor) + } + ) + } + MetaItem.ValueItem(value) + } else { + json { + "@value" to this@toMetaItem + }.toMetaItem(descriptor) + } + } +} + +class JsonMeta(val json: JsonObject, val descriptor: NodeDescriptor? = null) : MetaBase() { + + @Suppress("UNCHECKED_CAST") + private operator fun MutableMap<String, MetaItem<JsonMeta>>.set(key: String, value: JsonElement): Unit { + val itemDescriptor = descriptor?.items?.get(key) + return when (value) { + is JsonPrimitive -> { + this[key] = MetaItem.ValueItem(value.toValue(itemDescriptor as? ValueDescriptor)) as MetaItem<JsonMeta> + } + 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) + } + ) + this[key] = MetaItem.ValueItem(listValue) as MetaItem<JsonMeta> + } + else -> value.forEachIndexed { index, jsonElement -> + this["$key[$index]"] = jsonElement.toMetaItem(itemDescriptor) + } + } + } + } + } + + override val items: Map<NameToken, MetaItem<JsonMeta>> by lazy { + val map = HashMap<String, MetaItem<JsonMeta>>() + json.forEach { (key, value) -> map[key] = value } + map.mapKeys { it.key.toName().first()!! } + } +} \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/MetaSerializer.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/MetaSerializer.kt new file mode 100644 index 00000000..a98aa4c0 --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/MetaSerializer.kt @@ -0,0 +1,110 @@ +package hep.dataforge.meta.serialization + +import hep.dataforge.meta.* +import hep.dataforge.names.NameToken +import hep.dataforge.values.* +import kotlinx.serialization.* +import kotlinx.serialization.builtins.MapSerializer +import kotlinx.serialization.builtins.list +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.json.JsonInput +import kotlinx.serialization.json.JsonObjectSerializer +import kotlinx.serialization.json.JsonOutput + + +@Serializer(Value::class) +@OptIn(InternalSerializationApi::class) +object ValueSerializer : KSerializer<Value> { + // private val valueTypeSerializer = EnumSerializer(ValueType::class) + private val listSerializer by lazy { ValueSerializer.list } + + override val descriptor: SerialDescriptor = SerialDescriptor("Value") { + boolean("isList") + enum<ValueType>("valueType") + string("value") + } + + private fun Decoder.decodeValue(): Value { + return when (decode(ValueType.serializer())) { + ValueType.NULL -> Null + ValueType.NUMBER -> decodeDouble().asValue() //TODO differentiate? + ValueType.BOOLEAN -> decodeBoolean().asValue() + ValueType.STRING -> decodeString().asValue() + } + } + + + override fun deserialize(decoder: Decoder): Value { + val isList = decoder.decodeBoolean() + return if (isList) { + listSerializer.deserialize(decoder).asValue() + } else { + decoder.decodeValue() + } + } + + private fun Encoder.encodeValue(value: Value) { + encode(ValueType.serializer(), value.type) + when (value.type) { + ValueType.NULL -> { + // do nothing + } + ValueType.NUMBER -> encodeDouble(value.double) + ValueType.BOOLEAN -> encodeBoolean(value.boolean) + ValueType.STRING -> encodeString(value.string) + } + } + + override fun serialize(encoder: Encoder, value: Value) { + encoder.encodeBoolean(value.isList()) + if (value.isList()) { + listSerializer.serialize(encoder, value.list) + } else { + encoder.encodeValue(value) + } + } +} + +private class DeserializedMeta(override val items: Map<NameToken, MetaItem<*>>) : MetaBase() + +/** + * Serialized for meta + */ +@Serializer(Meta::class) +object MetaSerializer : KSerializer<Meta> { + private val mapSerializer = MapSerializer( + String.serializer(), + MetaItem.serializer(MetaSerializer) + ) + + override val descriptor: SerialDescriptor get() = mapSerializer.descriptor + + override fun deserialize(decoder: Decoder): Meta { + return if (decoder is JsonInput) { + JsonObjectSerializer.deserialize(decoder).toMeta() + } else { + DeserializedMeta(mapSerializer.deserialize(decoder).mapKeys { NameToken(it.key) }) + } + } + + override fun serialize(encoder: Encoder, value: Meta) { + if (encoder is JsonOutput) { + JsonObjectSerializer.serialize(encoder, value.toJson()) + } else { + mapSerializer.serialize(encoder, value.items.mapKeys { it.key.toString() }) + } + } +} + +@Serializer(Config::class) +object ConfigSerializer : KSerializer<Config> { + override val descriptor: SerialDescriptor = MetaSerializer.descriptor + + override fun deserialize(decoder: Decoder): Config { + return MetaSerializer.deserialize(decoder).asConfig() + } + + override fun serialize(encoder: Encoder, value: Config) { + MetaSerializer.serialize(encoder, value) + } +} \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/serializationUtils.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/serializationUtils.kt new file mode 100644 index 00000000..a92609e9 --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/serializationUtils.kt @@ -0,0 +1,67 @@ +package hep.dataforge.meta.serialization + +import hep.dataforge.meta.DFExperimental +import kotlinx.serialization.* +import kotlinx.serialization.builtins.DoubleArraySerializer +import kotlinx.serialization.internal.* + +fun SerialDescriptorBuilder.boolean(name: String, isOptional: Boolean = false, vararg annotations: Annotation) = + element(name, PrimitiveDescriptor(name, PrimitiveKind.BOOLEAN), isOptional = isOptional, annotations = annotations.toList()) + +fun SerialDescriptorBuilder.string(name: String, isOptional: Boolean = false, vararg annotations: Annotation) = + element(name, PrimitiveDescriptor(name, PrimitiveKind.STRING), isOptional = isOptional, annotations = annotations.toList()) + +fun SerialDescriptorBuilder.int(name: String, isOptional: Boolean = false, vararg annotations: Annotation) = + element(name, PrimitiveDescriptor(name, PrimitiveKind.INT), isOptional = isOptional, annotations = annotations.toList()) + +fun SerialDescriptorBuilder.double(name: String, isOptional: Boolean = false, vararg annotations: Annotation) = + element(name,PrimitiveDescriptor(name, PrimitiveKind.DOUBLE), isOptional = isOptional, annotations = annotations.toList()) + +fun SerialDescriptorBuilder.float(name: String, isOptional: Boolean = false, vararg annotations: Annotation) = + element(name, PrimitiveDescriptor(name, PrimitiveKind.FLOAT), isOptional = isOptional, annotations = annotations.toList()) + +fun SerialDescriptorBuilder.long(name: String, isOptional: Boolean = false, vararg annotations: Annotation) = + element(name, PrimitiveDescriptor(name, PrimitiveKind.LONG), isOptional = isOptional, annotations = annotations.toList()) + +fun SerialDescriptorBuilder.doubleArray(name: String, isOptional: Boolean = false, vararg annotations: Annotation) = + element(name, DoubleArraySerializer().descriptor, isOptional = isOptional, annotations = annotations.toList()) + +@OptIn(InternalSerializationApi::class) +inline fun <reified E : Enum<E>> SerialDescriptorBuilder.enum(name: String, isOptional: Boolean = false, vararg annotations: Annotation) { + val enumDescriptor = SerialDescriptor(serialName, UnionKind.ENUM_KIND) { + enumValues<E>().forEach { + val fqn = "$serialName.${it.name}" + val enumMemberDescriptor = SerialDescriptor(fqn, StructureKind.OBJECT) + element(it.name, enumMemberDescriptor) + } + } + element(name, enumDescriptor, isOptional = isOptional, annotations = annotations.toList()) +} + +//inline fun <reified T : Any> KSerializer<T>.descriptor( +// name: String, +// block: SerialDescriptorBuilder.() -> Unit +//): SerialDescriptor = +// SerialDescriptorBuilder(SerialClassDescImpl(name)).apply(block).build() + +@DFExperimental +inline fun <R> Decoder.decodeStructure( + desc: SerialDescriptor, + vararg typeParams: KSerializer<*> = emptyArray(), + crossinline block: CompositeDecoder.() -> R +): R { + val decoder = beginStructure(desc, *typeParams) + val res = decoder.block() + decoder.endStructure(desc) + return res +} + +inline fun Encoder.encodeStructure( + desc: SerialDescriptor, + vararg typeParams: KSerializer<*> = emptyArray(), + block: CompositeEncoder.() -> Unit +) { + val encoder = beginStructure(desc, *typeParams) + encoder.block() + encoder.endStructure(desc) +} \ No newline at end of file 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 4dad4466..d6f3bedf 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 @@ -90,7 +90,7 @@ inline class MetaTransformation(val transformations: Collection<TransformationRu * Produce new meta using only those items that match transformation rules */ fun transform(source: Meta): Meta = - buildMeta { + Meta { transformations.forEach { rule -> rule.selectItems(source).forEach { name -> rule.transformItem(name, source[name], this) @@ -102,7 +102,7 @@ inline class MetaTransformation(val transformations: Collection<TransformationRu * Transform a meta, replacing all elements found in rules with transformed entries */ fun apply(source: Meta): Meta = - buildMeta(source) { + source.edit { transformations.forEach { rule -> rule.selectItems(source).forEach { name -> remove(name) @@ -156,8 +156,8 @@ class MetaTransformationBuilder { fun keep(regex: String) { transformations.add( RegexItemTransformationRule(regex.toRegex()) { name, _, metaItem -> - setItem(name, metaItem) - }) + setItem(name, metaItem) + }) } /** 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 3c8d93d5..255a2ffd 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt @@ -1,11 +1,14 @@ package hep.dataforge.names +import kotlinx.serialization.* + /** * The general interface for working with names. * The name is a dot separated list of strings like `token1.token2.token3`. * Each token could contain additional index in square brackets. */ +@Serializable class Name(val tokens: List<NameToken>) { val length get() = tokens.size @@ -51,10 +54,21 @@ class Name(val tokens: List<NameToken>) { } - companion object { + @Serializer(Name::class) + companion object: KSerializer<Name> { const val NAME_SEPARATOR = "." val EMPTY = Name(emptyList()) + + override val descriptor: SerialDescriptor = PrimitiveDescriptor("Name", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): Name { + return decoder.decodeString().toName() + } + + override fun serialize(encoder: Encoder, value: Name) { + encoder.encodeString(value.toString()) + } } } @@ -63,6 +77,7 @@ class Name(val tokens: List<NameToken>) { * Following symbols are prohibited in name tokens: `{}.:\`. * A name token could have appendix in square brackets called *index* */ +@Serializable data class NameToken(val body: String, val index: String = "") { init { @@ -82,6 +97,19 @@ data class NameToken(val body: String, val index: String = "") { } fun hasIndex() = index.isNotEmpty() + + @Serializer(NameToken::class) + companion object :KSerializer<NameToken>{ + override val descriptor: SerialDescriptor = PrimitiveDescriptor("NameToken", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): NameToken { + return decoder.decodeString().toName().first()!! + } + + override fun serialize(encoder: Encoder, value: NameToken) { + encoder.encodeString(value.toString()) + } + } } /** diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/Value.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/Value.kt index f044b018..ccc92d50 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/Value.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/Value.kt @@ -1,5 +1,7 @@ package hep.dataforge.values +import kotlinx.serialization.Serializable + /** * The list of supported Value types. @@ -7,6 +9,7 @@ package hep.dataforge.values * Time value and binary value are represented by string * */ +@Serializable enum class ValueType { NUMBER, STRING, BOOLEAN, NULL } From db03dfaae9996f53f8a3cf1ec77f90e01c09f024 Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Fri, 13 Mar 2020 19:15:37 +0300 Subject: [PATCH 36/41] Finish migrating to 0.4.0 plugin --- dataforge-context/build.gradle.kts | 7 +- dataforge-io/build.gradle.kts | 18 +-- .../dataforge-io-yaml/build.gradle.kts | 8 +- .../io/yaml/FrontMatterEnvelopeFormat.kt | 6 +- .../hep/dataforge/io/yaml/YamlMetaFormat.kt | 3 +- .../dataforge/io/yaml/YamlMetaFormatTest.kt | 3 +- .../hep/dataforge/io/BinaryMetaFormat.kt | 7 +- .../kotlin/hep/dataforge/io/EnvelopeParts.kt | 2 +- .../kotlin/hep/dataforge/io/JsonMetaFormat.kt | 28 ++--- .../hep/dataforge/io/TaglessEnvelopeFormat.kt | 3 +- .../kotlin/hep/dataforge/io/MetaFormatTest.kt | 4 +- .../hep/dataforge/io/MetaSerializerTest.kt | 20 ++-- dataforge-meta/build.gradle.kts | 4 +- .../meta/{serialization => }/JsonMeta.kt | 19 ++- .../kotlin/hep/dataforge/meta/Meta.kt | 32 ++++- .../hep/dataforge/meta/MetaSerializer.kt | 53 +++++++++ .../kotlin/hep/dataforge/meta/mapMeta.kt | 2 +- .../meta/scheme/ConfigurableDelegate.kt | 2 +- .../hep/dataforge/meta/scheme/Scheme.kt | 10 +- .../meta/serialization/MetaSerializer.kt | 110 ------------------ .../{serialization => }/serializationUtils.kt | 32 +++-- .../kotlin/hep/dataforge/names/Name.kt | 9 +- .../hep/dataforge/values/ValueSerializer.kt | 59 ++++++++++ .../hep/dataforge/meta/MetaExtensionTest.kt | 4 +- .../kotlin/hep/dataforge/meta/MetaTest.kt | 8 +- .../hep/dataforge/meta/MutableMetaTest.kt | 2 +- .../kotlin/hep/dataforge/meta/SchemeTest.kt | 3 +- .../dataforge/tables/io/textTableEnvelope.kt | 2 +- .../hep/dataforge/workspace/Dependency.kt | 7 +- .../hep/dataforge/workspace/TaskModel.kt | 4 +- .../hep/dataforge/workspace/Workspace.kt | 5 +- .../dataforge/workspace/WorkspaceBuilder.kt | 2 +- 32 files changed, 235 insertions(+), 243 deletions(-) rename dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/{serialization => }/JsonMeta.kt (90%) create mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaSerializer.kt delete mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/MetaSerializer.kt rename dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/{serialization => }/serializationUtils.kt (60%) create mode 100644 dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/ValueSerializer.kt diff --git a/dataforge-context/build.gradle.kts b/dataforge-context/build.gradle.kts index 104f4037..dd581254 100644 --- a/dataforge-context/build.gradle.kts +++ b/dataforge-context/build.gradle.kts @@ -1,10 +1,12 @@ +import scientifik.coroutines + plugins { id("scientifik.mpp") } description = "Context and provider definitions" -val coroutinesVersion: String = Scientifik.coroutinesVersion +coroutines() kotlin { sourceSets { @@ -13,20 +15,17 @@ kotlin { api(project(":dataforge-meta")) api(kotlin("reflect")) api("io.github.microutils:kotlin-logging-common:1.7.8") - api("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$coroutinesVersion") } } val jvmMain by getting { dependencies { api("io.github.microutils:kotlin-logging:1.7.8") api("ch.qos.logback:logback-classic:1.2.3") - api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") } } val jsMain by getting { dependencies { api("io.github.microutils:kotlin-logging-js:1.7.8") - api("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$coroutinesVersion") } } } diff --git a/dataforge-io/build.gradle.kts b/dataforge-io/build.gradle.kts index 2606f5ea..1760f613 100644 --- a/dataforge-io/build.gradle.kts +++ b/dataforge-io/build.gradle.kts @@ -1,4 +1,5 @@ -import scientifik.useSerialization +import scientifik.DependencySourceSet.TEST +import scientifik.serialization plugins { id("scientifik.mpp") @@ -6,7 +7,9 @@ plugins { description = "IO module" -useSerialization() +serialization(sourceSet = TEST){ + cbor() +} val ioVersion by rootProject.extra("0.2.0-npm-dev-4") @@ -16,17 +19,6 @@ kotlin { dependencies { api(project(":dataforge-context")) api("org.jetbrains.kotlinx:kotlinx-io:$ioVersion") - //api("org.jetbrains.kotlinx:kotlinx-io-metadata:$ioVersion") - } - } - jvmMain { - dependencies { - //api("org.jetbrains.kotlinx:kotlinx-io-jvm:$ioVersion") - } - } - jsMain { - dependencies { - //api("org.jetbrains.kotlinx:kotlinx-io-js:$ioVersion") } } } diff --git a/dataforge-io/dataforge-io-yaml/build.gradle.kts b/dataforge-io/dataforge-io-yaml/build.gradle.kts index bad9b42e..14ea2c19 100644 --- a/dataforge-io/dataforge-io-yaml/build.gradle.kts +++ b/dataforge-io/dataforge-io-yaml/build.gradle.kts @@ -1,4 +1,4 @@ -import scientifik.useSerialization +import scientifik.serialization plugins { id("scientifik.jvm") @@ -6,9 +6,11 @@ plugins { description = "YAML meta IO" -useSerialization() +serialization{ + yaml() +} dependencies { api(project(":dataforge-io")) - api("org.yaml:snakeyaml:1.25") + api("org.yaml:snakeyaml:1.26") } diff --git a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt index be74d080..6361c5dd 100644 --- a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt +++ b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt @@ -21,7 +21,7 @@ class FrontMatterEnvelopeFormat( var line: String = "" var offset = 0u do { - line = readUtf8Line() ?: error("Input does not contain front matter separator") + line = readUtf8Line() //?: error("Input does not contain front matter separator") offset += line.toUtf8Bytes().size.toUInt() } while (!line.startsWith(SEPARATOR)) @@ -46,7 +46,7 @@ class FrontMatterEnvelopeFormat( override fun Input.readObject(): Envelope { var line: String = "" do { - line = readUtf8Line() ?: error("Input does not contain front matter separator") + line = readUtf8Line() //?: error("Input does not contain front matter separator") } while (!line.startsWith(SEPARATOR)) val readMetaFormat = @@ -89,7 +89,7 @@ class FrontMatterEnvelopeFormat( override fun peekFormat(io: IOPlugin, input: Input): EnvelopeFormat? { val line = input.readUtf8Line() - return if (line != null && line.startsWith("---")) { + return if (line.startsWith("---")) { invoke() } else { null diff --git a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt index d0543fd0..d1ab09e4 100644 --- a/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt +++ b/dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/YamlMetaFormat.kt @@ -1,13 +1,12 @@ package hep.dataforge.io.yaml import hep.dataforge.context.Context -import hep.dataforge.meta.descriptors.NodeDescriptor import hep.dataforge.io.MetaFormat import hep.dataforge.io.MetaFormatFactory import hep.dataforge.meta.DFExperimental import hep.dataforge.meta.Meta +import hep.dataforge.meta.descriptors.NodeDescriptor import hep.dataforge.meta.toMap -import hep.dataforge.meta.scheme.toMeta import hep.dataforge.meta.toMeta import kotlinx.io.Input import kotlinx.io.Output diff --git a/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt b/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt index b330e080..24fb6593 100644 --- a/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt +++ b/dataforge-io/dataforge-io-yaml/src/test/kotlin/hep/dataforge/io/yaml/YamlMetaFormatTest.kt @@ -3,7 +3,6 @@ package hep.dataforge.io.yaml import hep.dataforge.io.parse import hep.dataforge.io.toString import hep.dataforge.meta.Meta -import hep.dataforge.meta.buildMeta import hep.dataforge.meta.get import hep.dataforge.meta.seal import kotlin.test.Test @@ -13,7 +12,7 @@ import kotlin.test.assertEquals class YamlMetaFormatTest { @Test fun testYamlMetaFormat() { - val meta = buildMeta { + val meta = Meta { "a" put 22 "node" put { "b" put "DDD" diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/BinaryMetaFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/BinaryMetaFormat.kt index c86fe883..e753b56c 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/BinaryMetaFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/BinaryMetaFormat.kt @@ -1,8 +1,11 @@ package hep.dataforge.io import hep.dataforge.context.Context +import hep.dataforge.meta.Meta +import hep.dataforge.meta.MetaBuilder +import hep.dataforge.meta.MetaItem import hep.dataforge.meta.descriptors.NodeDescriptor -import hep.dataforge.meta.* +import hep.dataforge.meta.setItem import hep.dataforge.values.* import kotlinx.io.* import kotlinx.io.text.readUtf8String @@ -112,7 +115,7 @@ object BinaryMetaFormat : MetaFormat, MetaFormatFactory { } 'M' -> { val length = readInt() - val meta = buildMeta { + val meta = Meta { (1..length).forEach { _ -> val name = readString() val item = readMetaItem() diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt index 8e0b16de..9541c8fb 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeParts.kt @@ -77,7 +77,7 @@ fun EnvelopeBuilder.multipart( writeRawString(MULTIPART_DATA_SEPARATOR) writeEnvelope(envelope) meta { - append(INDEX_KEY, buildMeta { + append(INDEX_KEY, Meta { "key" put key "index" put counter }) diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt index 1101b35f..01606e6d 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/JsonMetaFormat.kt @@ -2,29 +2,20 @@ package hep.dataforge.io + import hep.dataforge.context.Context -import hep.dataforge.meta.descriptors.ItemDescriptor -import hep.dataforge.meta.descriptors.NodeDescriptor -import hep.dataforge.meta.descriptors.ValueDescriptor import hep.dataforge.meta.Meta -import hep.dataforge.meta.MetaBase -import hep.dataforge.meta.MetaItem -import hep.dataforge.meta.serialization.toJson -import hep.dataforge.meta.serialization.toMeta -import hep.dataforge.names.NameToken -import hep.dataforge.names.toName -import hep.dataforge.values.* +import hep.dataforge.meta.descriptors.NodeDescriptor +import hep.dataforge.meta.node +import hep.dataforge.meta.toJson +import hep.dataforge.meta.toMetaItem import kotlinx.io.Input import kotlinx.io.Output import kotlinx.io.text.readUtf8String import kotlinx.io.text.writeUtf8String import kotlinx.serialization.UnstableDefault - - -import kotlinx.serialization.json.* -import kotlin.collections.component1 -import kotlin.collections.component2 -import kotlin.collections.set +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObjectSerializer @OptIn(UnstableDefault::class) class JsonMetaFormat(private val json: Json = DEFAULT_JSON) : MetaFormat { @@ -37,11 +28,12 @@ class JsonMetaFormat(private val json: Json = DEFAULT_JSON) : MetaFormat { override fun Input.readMeta(descriptor: NodeDescriptor?): Meta { val str = readUtf8String() val jsonElement = json.parseJson(str) - return jsonElement.toMeta() + val item = jsonElement.toMetaItem(descriptor) + return item.node ?: Meta.EMPTY } companion object : MetaFormatFactory { - val DEFAULT_JSON = Json{prettyPrint = true} + val DEFAULT_JSON = Json { prettyPrint = true } override fun invoke(meta: Meta, context: Context): MetaFormat = default diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt index ebeedd6d..7023ce1a 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/TaglessEnvelopeFormat.kt @@ -8,7 +8,6 @@ import kotlinx.io.text.readRawString import kotlinx.io.text.readUtf8Line import kotlinx.io.text.writeRawString import kotlinx.io.text.writeUtf8String -import kotlinx.serialization.toUtf8Bytes @ExperimentalIoApi class TaglessEnvelopeFormat( @@ -155,7 +154,7 @@ class TaglessEnvelopeFormat( } do { - line = readUtf8Line() ?: return PartialEnvelope(Meta.EMPTY, offset.toUInt(), 0.toULong()) + line = readUtf8Line() //?: return PartialEnvelope(Meta.EMPTY, offset.toUInt(), 0.toULong()) offset += line.encodeToByteArray().size.toUInt() //returning an Envelope without data if end of input is reached } while (!line.startsWith(dataStart)) 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 9064a485..6fc801ec 100644 --- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaFormatTest.kt +++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaFormatTest.kt @@ -20,7 +20,7 @@ fun MetaFormat.fromBytes(packet: Bytes): Meta { class MetaFormatTest { @Test fun testBinaryMetaFormat() { - val meta = buildMeta { + val meta = Meta { "a" put 22 "node" put { "b" put "DDD" @@ -35,7 +35,7 @@ class MetaFormatTest { @Test fun testJsonMetaFormat() { - val meta = buildMeta { + val meta = Meta { "a" put 22 "node" put { "b" put "DDD" diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaSerializerTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaSerializerTest.kt index 37db8833..69b0427b 100644 --- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaSerializerTest.kt +++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaSerializerTest.kt @@ -1,9 +1,9 @@ package hep.dataforge.io -import hep.dataforge.io.serialization.MetaItemSerializer -import hep.dataforge.io.serialization.MetaSerializer -import hep.dataforge.io.serialization.NameSerializer -import hep.dataforge.meta.buildMeta +import hep.dataforge.meta.Meta +import hep.dataforge.meta.MetaItem +import hep.dataforge.meta.MetaSerializer +import hep.dataforge.names.Name import hep.dataforge.names.toName import kotlinx.serialization.cbor.Cbor import kotlinx.serialization.json.Json @@ -13,7 +13,7 @@ import kotlin.test.assertEquals class MetaSerializerTest { @Test fun testMetaSerialization() { - val meta = buildMeta { + val meta = Meta { "a" put 22 "node" put { "b" put "DDD" @@ -29,7 +29,7 @@ class MetaSerializerTest { @Test fun testCborSerialization() { - val meta = buildMeta { + val meta = Meta { "a" put 22 "node" put { "b" put "DDD" @@ -47,13 +47,13 @@ class MetaSerializerTest { @Test fun testNameSerialization() { val name = "a.b.c".toName() - val string = Json.indented.stringify(NameSerializer, name) - val restored = Json.plain.parse(NameSerializer, string) + val string = Json.indented.stringify(Name.serializer(), name) + val restored = Json.plain.parse(Name.serializer(), string) assertEquals(restored, name) } @Test - fun testMetaItemDescriptor(){ - val descriptor = MetaItemSerializer.descriptor.getElementDescriptor(0) + fun testMetaItemDescriptor() { + val descriptor = MetaItem.serializer(MetaSerializer).descriptor.getElementDescriptor(0) } } \ No newline at end of file diff --git a/dataforge-meta/build.gradle.kts b/dataforge-meta/build.gradle.kts index f19a39fc..fea7ecd7 100644 --- a/dataforge-meta/build.gradle.kts +++ b/dataforge-meta/build.gradle.kts @@ -1,9 +1,9 @@ -import scientifik.useSerialization +import scientifik.serialization plugins { id("scientifik.mpp") } -useSerialization() +serialization() description = "Meta definition and basic operations on meta" \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/JsonMeta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/JsonMeta.kt similarity index 90% rename from dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/JsonMeta.kt rename to dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/JsonMeta.kt index 78357c6d..ee906008 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/JsonMeta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/JsonMeta.kt @@ -1,8 +1,5 @@ -package hep.dataforge.meta.serialization +package hep.dataforge.meta -import hep.dataforge.meta.Meta -import hep.dataforge.meta.MetaBase -import hep.dataforge.meta.MetaItem import hep.dataforge.meta.descriptors.ItemDescriptor import hep.dataforge.meta.descriptors.NodeDescriptor import hep.dataforge.meta.descriptors.ValueDescriptor @@ -52,12 +49,7 @@ fun Meta.toJson(descriptor: NodeDescriptor? = null): JsonObject { return JsonObject(map) } -fun JsonElement.toMeta(descriptor: NodeDescriptor? = null): Meta { - return when (val item = toMetaItem(descriptor)) { - is MetaItem.NodeItem<*> -> item.node - is MetaItem.ValueItem -> item.value.toMeta() - } -} +fun JsonObject.toMeta(descriptor: NodeDescriptor? = null): Meta = JsonMeta(this, descriptor) fun JsonPrimitive.toValue(descriptor: ValueDescriptor?): Value { return when (this) { @@ -106,7 +98,12 @@ class JsonMeta(val json: JsonObject, val descriptor: NodeDescriptor? = null) : M this[key] = MetaItem.ValueItem(value.toValue(itemDescriptor as? ValueDescriptor)) as MetaItem<JsonMeta> } is JsonObject -> { - this[key] = MetaItem.NodeItem(JsonMeta(value, itemDescriptor as? NodeDescriptor)) + this[key] = MetaItem.NodeItem( + JsonMeta( + value, + itemDescriptor as? NodeDescriptor + ) + ) } is JsonArray -> { when { 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 b37188b4..e5cda8f6 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt @@ -3,13 +3,9 @@ package hep.dataforge.meta import hep.dataforge.meta.Meta.Companion.VALUE_KEY import hep.dataforge.meta.MetaItem.NodeItem import hep.dataforge.meta.MetaItem.ValueItem -import hep.dataforge.meta.serialization.toJson import hep.dataforge.names.* -import hep.dataforge.values.EnumValue -import hep.dataforge.values.Null -import hep.dataforge.values.Value -import hep.dataforge.values.boolean -import kotlinx.serialization.Serializable +import hep.dataforge.values.* +import kotlinx.serialization.* /** @@ -22,11 +18,33 @@ sealed class MetaItem<out M : Meta> { @Serializable data class ValueItem(val value: Value) : MetaItem<Nothing>() { override fun toString(): String = value.toString() + + @Serializer(ValueItem::class) + companion object : KSerializer<ValueItem> { + override val descriptor: SerialDescriptor get() = ValueSerializer.descriptor + + override fun deserialize(decoder: Decoder): ValueItem = ValueItem(ValueSerializer.deserialize(decoder)) + + override fun serialize(encoder: Encoder, value: ValueItem) { + ValueSerializer.serialize(encoder, value.value) + } + } } @Serializable data class NodeItem<M : Meta>(val node: M) : MetaItem<M>() { override fun toString(): String = node.toString() + + @Serializer(NodeItem::class) + companion object : KSerializer<NodeItem<*>> { + override val descriptor: SerialDescriptor get() = ValueSerializer.descriptor + + override fun deserialize(decoder: Decoder): NodeItem<*> = NodeItem(MetaSerializer.deserialize(decoder)) + + override fun serialize(encoder: Encoder, value: NodeItem<*>) { + MetaSerializer.serialize(encoder, value.node) + } + } } companion object { @@ -72,6 +90,7 @@ interface Meta : MetaRepr { companion object { const val TYPE = "meta" + /** * A key for single value node */ @@ -101,6 +120,7 @@ operator fun Meta?.get(name: Name): MetaItem<*>? { } operator fun Meta?.get(token: NameToken): MetaItem<*>? = this?.items?.get(token) + /** * Parse [Name] from [key] using full name notation and pass it to [Meta.get] */ diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaSerializer.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaSerializer.kt new file mode 100644 index 00000000..63a45664 --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaSerializer.kt @@ -0,0 +1,53 @@ +package hep.dataforge.meta + +import hep.dataforge.names.NameToken +import kotlinx.serialization.* +import kotlinx.serialization.builtins.MapSerializer +import kotlinx.serialization.json.JsonInput +import kotlinx.serialization.json.JsonObjectSerializer +import kotlinx.serialization.json.JsonOutput + + +private class DeserializedMeta(override val items: Map<NameToken, MetaItem<Meta>>) : MetaBase() + +/** + * Serialized for meta + */ +@Serializer(Meta::class) +object MetaSerializer : KSerializer<Meta> { + private val mapSerializer = MapSerializer( + NameToken.serializer(), + MetaItem.serializer(MetaSerializer) + ) + + override val descriptor: SerialDescriptor get() = mapSerializer.descriptor + + override fun deserialize(decoder: Decoder): Meta { + return if (decoder is JsonInput) { + JsonObjectSerializer.deserialize(decoder).toMeta() + } else { + DeserializedMeta(mapSerializer.deserialize(decoder)) + } + } + + override fun serialize(encoder: Encoder, value: Meta) { + if (encoder is JsonOutput) { + JsonObjectSerializer.serialize(encoder, value.toJson()) + } else { + mapSerializer.serialize(encoder, value.items) + } + } +} + +@Serializer(Config::class) +object ConfigSerializer : KSerializer<Config> { + override val descriptor: SerialDescriptor get() = MetaSerializer.descriptor + + override fun deserialize(decoder: Decoder): Config { + return MetaSerializer.deserialize(decoder).asConfig() + } + + override fun serialize(encoder: Encoder, value: Config) { + MetaSerializer.serialize(encoder, value) + } +} \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/mapMeta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/mapMeta.kt index ddf049e3..df3e97a5 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/mapMeta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/mapMeta.kt @@ -20,7 +20,7 @@ fun Meta.toMap(descriptor: NodeDescriptor? = null): Map<String, Any?> { * Convert map of maps to meta */ @DFExperimental -fun Map<String, Any?>.toMeta(descriptor: NodeDescriptor? = null): Meta = buildMeta { +fun Map<String, Any?>.toMeta(descriptor: NodeDescriptor? = null): Meta = Meta { entries.forEach { (key, value) -> @Suppress("UNCHECKED_CAST") when (value) { diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt index c6e9a44a..da02c30e 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt @@ -201,7 +201,7 @@ fun Configurable.doubleArray(vararg doubles: Double, key: Name? = null): ReadWri fun Configurable.config(key: Name? = null): ReadWriteProperty<Any?, Config?> = config.node(key) -fun Configurable.node(key: Name? = null): ReadWriteProperty<Any?, Meta?> = item().map( +fun Configurable.node(key: Name? = null): ReadWriteProperty<Any?, Meta?> = item(key).map( reader = { it.node }, writer = { it?.let { MetaItem.NodeItem(it) } } ) diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt index 78d0a511..8e74ba7b 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt @@ -39,7 +39,7 @@ open class Scheme() : Configurable, Described, MetaRepr { */ open val defaultLayer: Meta get() = DefaultLayer(Name.EMPTY) - override fun toMeta(): Meta = Laminate(config, defaultLayer) + override fun toMeta(): Laminate = Laminate(config, defaultLayer) private inner class DefaultLayer(val path: Name) : MetaBase() { override val items: Map<NameToken, MetaItem<*>> = @@ -87,10 +87,4 @@ open class MetaScheme( fun Meta.asScheme() = MetaScheme(this) -fun <T : Configurable> Meta.toScheme(spec: Specification<T>, block: T.() -> Unit) = spec.wrap(this).apply(block) - -/** - * Create a snapshot laminate - */ -fun Scheme.toMeta(): Laminate = - Laminate(config, defaultLayer) \ No newline at end of file +fun <T : Configurable> Meta.toScheme(spec: Specification<T>, block: T.() -> Unit) = spec.wrap(this).apply(block) \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/MetaSerializer.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/MetaSerializer.kt deleted file mode 100644 index a98aa4c0..00000000 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/MetaSerializer.kt +++ /dev/null @@ -1,110 +0,0 @@ -package hep.dataforge.meta.serialization - -import hep.dataforge.meta.* -import hep.dataforge.names.NameToken -import hep.dataforge.values.* -import kotlinx.serialization.* -import kotlinx.serialization.builtins.MapSerializer -import kotlinx.serialization.builtins.list -import kotlinx.serialization.builtins.serializer -import kotlinx.serialization.json.JsonInput -import kotlinx.serialization.json.JsonObjectSerializer -import kotlinx.serialization.json.JsonOutput - - -@Serializer(Value::class) -@OptIn(InternalSerializationApi::class) -object ValueSerializer : KSerializer<Value> { - // private val valueTypeSerializer = EnumSerializer(ValueType::class) - private val listSerializer by lazy { ValueSerializer.list } - - override val descriptor: SerialDescriptor = SerialDescriptor("Value") { - boolean("isList") - enum<ValueType>("valueType") - string("value") - } - - private fun Decoder.decodeValue(): Value { - return when (decode(ValueType.serializer())) { - ValueType.NULL -> Null - ValueType.NUMBER -> decodeDouble().asValue() //TODO differentiate? - ValueType.BOOLEAN -> decodeBoolean().asValue() - ValueType.STRING -> decodeString().asValue() - } - } - - - override fun deserialize(decoder: Decoder): Value { - val isList = decoder.decodeBoolean() - return if (isList) { - listSerializer.deserialize(decoder).asValue() - } else { - decoder.decodeValue() - } - } - - private fun Encoder.encodeValue(value: Value) { - encode(ValueType.serializer(), value.type) - when (value.type) { - ValueType.NULL -> { - // do nothing - } - ValueType.NUMBER -> encodeDouble(value.double) - ValueType.BOOLEAN -> encodeBoolean(value.boolean) - ValueType.STRING -> encodeString(value.string) - } - } - - override fun serialize(encoder: Encoder, value: Value) { - encoder.encodeBoolean(value.isList()) - if (value.isList()) { - listSerializer.serialize(encoder, value.list) - } else { - encoder.encodeValue(value) - } - } -} - -private class DeserializedMeta(override val items: Map<NameToken, MetaItem<*>>) : MetaBase() - -/** - * Serialized for meta - */ -@Serializer(Meta::class) -object MetaSerializer : KSerializer<Meta> { - private val mapSerializer = MapSerializer( - String.serializer(), - MetaItem.serializer(MetaSerializer) - ) - - override val descriptor: SerialDescriptor get() = mapSerializer.descriptor - - override fun deserialize(decoder: Decoder): Meta { - return if (decoder is JsonInput) { - JsonObjectSerializer.deserialize(decoder).toMeta() - } else { - DeserializedMeta(mapSerializer.deserialize(decoder).mapKeys { NameToken(it.key) }) - } - } - - override fun serialize(encoder: Encoder, value: Meta) { - if (encoder is JsonOutput) { - JsonObjectSerializer.serialize(encoder, value.toJson()) - } else { - mapSerializer.serialize(encoder, value.items.mapKeys { it.key.toString() }) - } - } -} - -@Serializer(Config::class) -object ConfigSerializer : KSerializer<Config> { - override val descriptor: SerialDescriptor = MetaSerializer.descriptor - - override fun deserialize(decoder: Decoder): Config { - return MetaSerializer.deserialize(decoder).asConfig() - } - - override fun serialize(encoder: Encoder, value: Config) { - MetaSerializer.serialize(encoder, value) - } -} \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/serializationUtils.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serializationUtils.kt similarity index 60% rename from dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/serializationUtils.kt rename to dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serializationUtils.kt index a92609e9..16c58bdc 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serialization/serializationUtils.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/serializationUtils.kt @@ -1,33 +1,36 @@ -package hep.dataforge.meta.serialization +package hep.dataforge.meta -import hep.dataforge.meta.DFExperimental import kotlinx.serialization.* import kotlinx.serialization.builtins.DoubleArraySerializer -import kotlinx.serialization.internal.* +import kotlinx.serialization.builtins.serializer fun SerialDescriptorBuilder.boolean(name: String, isOptional: Boolean = false, vararg annotations: Annotation) = - element(name, PrimitiveDescriptor(name, PrimitiveKind.BOOLEAN), isOptional = isOptional, annotations = annotations.toList()) + element(name, Boolean.serializer().descriptor, isOptional = isOptional, annotations = annotations.toList()) fun SerialDescriptorBuilder.string(name: String, isOptional: Boolean = false, vararg annotations: Annotation) = - element(name, PrimitiveDescriptor(name, PrimitiveKind.STRING), isOptional = isOptional, annotations = annotations.toList()) + element(name, String.serializer().descriptor, isOptional = isOptional, annotations = annotations.toList()) fun SerialDescriptorBuilder.int(name: String, isOptional: Boolean = false, vararg annotations: Annotation) = - element(name, PrimitiveDescriptor(name, PrimitiveKind.INT), isOptional = isOptional, annotations = annotations.toList()) + element(name, Int.serializer().descriptor, isOptional = isOptional, annotations = annotations.toList()) fun SerialDescriptorBuilder.double(name: String, isOptional: Boolean = false, vararg annotations: Annotation) = - element(name,PrimitiveDescriptor(name, PrimitiveKind.DOUBLE), isOptional = isOptional, annotations = annotations.toList()) + element(name, Double.serializer().descriptor, isOptional = isOptional, annotations = annotations.toList()) fun SerialDescriptorBuilder.float(name: String, isOptional: Boolean = false, vararg annotations: Annotation) = - element(name, PrimitiveDescriptor(name, PrimitiveKind.FLOAT), isOptional = isOptional, annotations = annotations.toList()) + element(name, Float.serializer().descriptor, isOptional = isOptional, annotations = annotations.toList()) fun SerialDescriptorBuilder.long(name: String, isOptional: Boolean = false, vararg annotations: Annotation) = - element(name, PrimitiveDescriptor(name, PrimitiveKind.LONG), isOptional = isOptional, annotations = annotations.toList()) + element(name, Long.serializer().descriptor, isOptional = isOptional, annotations = annotations.toList()) fun SerialDescriptorBuilder.doubleArray(name: String, isOptional: Boolean = false, vararg annotations: Annotation) = element(name, DoubleArraySerializer().descriptor, isOptional = isOptional, annotations = annotations.toList()) @OptIn(InternalSerializationApi::class) -inline fun <reified E : Enum<E>> SerialDescriptorBuilder.enum(name: String, isOptional: Boolean = false, vararg annotations: Annotation) { +inline fun <reified E : Enum<E>> SerialDescriptorBuilder.enum( + name: String, + isOptional: Boolean = false, + vararg annotations: Annotation +) { val enumDescriptor = SerialDescriptor(serialName, UnionKind.ENUM_KIND) { enumValues<E>().forEach { val fqn = "$serialName.${it.name}" @@ -38,17 +41,11 @@ inline fun <reified E : Enum<E>> SerialDescriptorBuilder.enum(name: String, isOp element(name, enumDescriptor, isOptional = isOptional, annotations = annotations.toList()) } -//inline fun <reified T : Any> KSerializer<T>.descriptor( -// name: String, -// block: SerialDescriptorBuilder.() -> Unit -//): SerialDescriptor = -// SerialDescriptorBuilder(SerialClassDescImpl(name)).apply(block).build() - @DFExperimental inline fun <R> Decoder.decodeStructure( desc: SerialDescriptor, vararg typeParams: KSerializer<*> = emptyArray(), - crossinline block: CompositeDecoder.() -> R + crossinline block: CompositeDecoder.() -> R ): R { val decoder = beginStructure(desc, *typeParams) val res = decoder.block() @@ -56,6 +53,7 @@ inline fun <R> Decoder.decodeStructure( return res } +@DFExperimental inline fun Encoder.encodeStructure( desc: SerialDescriptor, vararg typeParams: KSerializer<*> = emptyArray(), 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 255a2ffd..2b8908ed 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt @@ -53,14 +53,13 @@ class Name(val tokens: List<NameToken>) { } } - @Serializer(Name::class) - companion object: KSerializer<Name> { + companion object : KSerializer<Name> { const val NAME_SEPARATOR = "." val EMPTY = Name(emptyList()) - override val descriptor: SerialDescriptor = PrimitiveDescriptor("Name", PrimitiveKind.STRING) + override val descriptor: SerialDescriptor = PrimitiveDescriptor("hep.dataforge.names.Name", PrimitiveKind.STRING) override fun deserialize(decoder: Decoder): Name { return decoder.decodeString().toName() @@ -99,8 +98,8 @@ data class NameToken(val body: String, val index: String = "") { fun hasIndex() = index.isNotEmpty() @Serializer(NameToken::class) - companion object :KSerializer<NameToken>{ - override val descriptor: SerialDescriptor = PrimitiveDescriptor("NameToken", PrimitiveKind.STRING) + companion object : KSerializer<NameToken> { + override val descriptor: SerialDescriptor = PrimitiveDescriptor("hep.dataforge.names.NameToken", PrimitiveKind.STRING) override fun deserialize(decoder: Decoder): NameToken { return decoder.decodeString().toName().first()!! diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/ValueSerializer.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/ValueSerializer.kt new file mode 100644 index 00000000..8055b554 --- /dev/null +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/ValueSerializer.kt @@ -0,0 +1,59 @@ +package hep.dataforge.values + +import hep.dataforge.meta.boolean +import hep.dataforge.meta.enum +import hep.dataforge.meta.string +import kotlinx.serialization.* +import kotlinx.serialization.builtins.list + +@Serializer(Value::class) +object ValueSerializer : KSerializer<Value> { + private val listSerializer by lazy { ValueSerializer.list } + + override val descriptor: SerialDescriptor = + SerialDescriptor("hep.dataforge.values.Value") { + boolean("isList") + enum<ValueType>("valueType") + string("value") + } + + private fun Decoder.decodeValue(): Value { + return when (decode(ValueType.serializer())) { + ValueType.NULL -> Null + ValueType.NUMBER -> decodeDouble().asValue() //TODO differentiate? + ValueType.BOOLEAN -> decodeBoolean().asValue() + ValueType.STRING -> decodeString().asValue() + } + } + + + override fun deserialize(decoder: Decoder): Value { + val isList = decoder.decodeBoolean() + return if (isList) { + listSerializer.deserialize(decoder).asValue() + } else { + decoder.decodeValue() + } + } + + private fun Encoder.encodeValue(value: Value) { + encode(ValueType.serializer(), value.type) + when (value.type) { + ValueType.NULL -> { + // do nothing + } + ValueType.NUMBER -> encodeDouble(value.double) + ValueType.BOOLEAN -> encodeBoolean(value.boolean) + ValueType.STRING -> encodeString(value.string) + } + } + + override fun serialize(encoder: Encoder, value: Value) { + encoder.encodeBoolean(value.isList()) + if (value.isList()) { + listSerializer.serialize(encoder, value.list) + } else { + encoder.encodeValue(value) + } + } +} \ No newline at end of file diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaExtensionTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaExtensionTest.kt index f2fffd19..0f4c19be 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaExtensionTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaExtensionTest.kt @@ -11,12 +11,12 @@ class MetaExtensionTest { @Test fun testEnum(){ - val meta = buildMeta{"enum" put TestEnum.test} + val meta = Meta{"enum" put TestEnum.test} meta["enum"].enum<TestEnum>() } @Test fun testEnumByString(){ - val meta = buildMeta{"enum" put TestEnum.test.name} + val meta = Meta{"enum" put TestEnum.test.name} println(meta["enum"].enum<TestEnum>()) } diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaTest.kt index fb424116..c55cd4ad 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaTest.kt @@ -16,13 +16,13 @@ class MetaTest { @Test fun metaEqualityTest() { - val meta1 = buildMeta { + val meta1 = Meta { "a" put 22 "b" put { "c" put "ddd" } } - val meta2 = buildMeta { + val meta2 = Meta { "b" put { "c" put "ddd" } @@ -33,13 +33,13 @@ class MetaTest { @Test fun metaToMap(){ - val meta = buildMeta { + val meta = Meta { "a" put 22 "b" put { "c" put "ddd" } "list" put (0..4).map { - buildMeta { + Meta { "value" put it } } diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MutableMetaTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MutableMetaTest.kt index 194c77e3..ba44edec 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MutableMetaTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MutableMetaTest.kt @@ -6,7 +6,7 @@ import kotlin.test.assertEquals class MutableMetaTest{ @Test fun testRemove(){ - val meta = buildMeta { + val meta = Meta { "aNode" put { "innerNode" put { "innerValue" put true 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 09a3e03c..bcebedc6 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/SchemeTest.kt @@ -2,7 +2,6 @@ package hep.dataforge.meta import hep.dataforge.meta.scheme.asScheme import hep.dataforge.meta.scheme.getProperty -import hep.dataforge.meta.scheme.toMeta import kotlin.test.Test import kotlin.test.assertEquals @@ -10,7 +9,7 @@ import kotlin.test.assertEquals class SchemeTest{ @Test fun testMetaScheme(){ - val styled = buildMeta { + val styled = Meta { repeat(10){ "b.a[$it]" put { "d" put it diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/textTableEnvelope.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/textTableEnvelope.kt index 29ce27b2..1180ca23 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/textTableEnvelope.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/textTableEnvelope.kt @@ -15,7 +15,7 @@ import kotlinx.io.asBinary suspend fun Table<Value>.wrap(): Envelope = Envelope { meta { header.forEachIndexed { index, columnHeader -> - set("column", index.toString(), buildMeta { + set("column", index.toString(), Meta { "name" put columnHeader.name if (!columnHeader.meta.isEmpty()) { "meta" put columnHeader.meta diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Dependency.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Dependency.kt index 72402a17..ed5ab7f0 100644 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Dependency.kt +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Dependency.kt @@ -5,7 +5,6 @@ import hep.dataforge.data.DataNode import hep.dataforge.data.filter import hep.dataforge.meta.Meta import hep.dataforge.meta.MetaRepr -import hep.dataforge.meta.buildMeta import hep.dataforge.names.Name import hep.dataforge.names.asName import hep.dataforge.names.isEmpty @@ -28,7 +27,7 @@ class DataDependency(val filter: DataFilter, val placement: Name = Name.EMPTY) : } } - override fun toMeta(): Meta = buildMeta { + override fun toMeta(): Meta = Meta { "data" put filter.config "to" put placement.toString() } @@ -41,7 +40,7 @@ class AllDataDependency(val placement: Name = Name.EMPTY) : Dependency() { DataNode.invoke(Any::class) { this[placement] = workspace.data } } - override fun toMeta() = buildMeta { + override fun toMeta() = Meta { "data" put "@all" "to" put placement.toString() } @@ -69,7 +68,7 @@ abstract class TaskDependency<out T : Any>( } } - override fun toMeta(): Meta = buildMeta { + override fun toMeta(): Meta = Meta { "task" put name.toString() "meta" put meta "to" put placement.toString() 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 ede9efaa..a811f428 100644 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/TaskModel.kt @@ -29,7 +29,7 @@ data class TaskModel( //TODO provide a way to get task descriptor //TODO add pre-run check of task result type? - override fun toMeta(): Meta = buildMeta { + override fun toMeta(): Meta = Meta { "name" put name.toString() "meta" put meta "dependsOn" put { @@ -98,7 +98,7 @@ fun <T : Any> TaskDependencyContainer.dependsOn( placement: Name = Name.EMPTY, metaBuilder: MetaBuilder.() -> Unit ): DirectTaskDependency<T> = - dependsOn(task, placement, buildMeta(metaBuilder)) + dependsOn(task, placement, Meta(metaBuilder)) /** * Add custom data dependency diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Workspace.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Workspace.kt index 31da6c56..ac2b1131 100644 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Workspace.kt +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/Workspace.kt @@ -8,7 +8,6 @@ import hep.dataforge.data.DataNode import hep.dataforge.data.dataSequence import hep.dataforge.meta.Meta import hep.dataforge.meta.MetaBuilder -import hep.dataforge.meta.buildMeta import hep.dataforge.names.Name import hep.dataforge.names.toName import hep.dataforge.provider.Provider @@ -76,7 +75,7 @@ fun Workspace.run(task: String, meta: Meta) = tasks[task.toName()]?.let { run(it, meta) } ?: error("Task with name $task not found") fun Workspace.run(task: String, block: MetaBuilder.() -> Unit = {}) = - run(task, buildMeta(block)) + run(task, Meta(block)) fun <T: Any> Workspace.run(task: Task<T>, metaBuilder: MetaBuilder.() -> Unit = {}): DataNode<T> = - run(task, buildMeta(metaBuilder)) \ No newline at end of file + run(task, Meta(metaBuilder)) \ No newline at end of file diff --git a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt index 7b831a50..3bc1ffcf 100644 --- a/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt +++ b/dataforge-workspace/src/commonMain/kotlin/hep/dataforge/workspace/WorkspaceBuilder.kt @@ -52,7 +52,7 @@ fun WorkspaceBuilder.data( fun WorkspaceBuilder.target(name: String, block: MetaBuilder.() -> Unit) { - targets[name] = buildMeta(block).seal() + targets[name] = Meta(block).seal() } /** From 4ab71a79db75e3fd65760dd2a86926886c8caa4d Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Sun, 15 Mar 2020 09:22:08 +0300 Subject: [PATCH 37/41] Fix meta serialization --- .../hep/dataforge/io/MetaSerializerTest.kt | 27 ++++++------------ .../jvmMain/kotlin/hep/dataforge/io/fileIO.kt | 3 +- .../kotlin/hep/dataforge/io/functionsJVM.kt | 3 +- .../kotlin/hep/dataforge/meta/Config.kt | 15 +++++++++- .../kotlin/hep/dataforge/meta/Meta.kt | 13 +++++---- .../hep/dataforge/meta/MetaSerializer.kt | 19 ++---------- .../meta/scheme/ConfigurableDelegate.kt | 5 ++-- gradle/wrapper/gradle-wrapper.jar | Bin 58702 -> 58695 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew.bat | 3 ++ 10 files changed, 44 insertions(+), 46 deletions(-) diff --git a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaSerializerTest.kt b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaSerializerTest.kt index 69b0427b..86a569a6 100644 --- a/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaSerializerTest.kt +++ b/dataforge-io/src/commonTest/kotlin/hep/dataforge/io/MetaSerializerTest.kt @@ -11,17 +11,17 @@ import kotlin.test.Test import kotlin.test.assertEquals class MetaSerializerTest { + val meta = Meta { + "a" put 22 + "node" put { + "b" put "DDD" + "c" put 11.1 + "array" put doubleArrayOf(1.0, 2.0, 3.0) + } + } + @Test fun testMetaSerialization() { - 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 string = Json.indented.stringify(MetaSerializer, meta) val restored = Json.plain.parse(MetaSerializer, string) assertEquals(restored, meta) @@ -29,15 +29,6 @@ class MetaSerializerTest { @Test fun testCborSerialization() { - 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 = Cbor.dump(MetaSerializer, meta) println(bytes.contentToString()) val restored = Cbor.load(MetaSerializer, bytes) diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt index 1fe9d59d..c9b4f1bf 100644 --- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt +++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt @@ -1,9 +1,9 @@ package hep.dataforge.io -import hep.dataforge.meta.descriptors.NodeDescriptor import hep.dataforge.meta.DFExperimental import hep.dataforge.meta.EmptyMeta import hep.dataforge.meta.Meta +import hep.dataforge.meta.descriptors.NodeDescriptor import hep.dataforge.meta.isEmpty import kotlinx.io.* import java.nio.file.Files @@ -14,6 +14,7 @@ import kotlin.streams.asSequence /** * Resolve IOFormat based on type */ +@Suppress("UNCHECKED_CAST") @DFExperimental inline fun <reified T : Any> IOPlugin.resolveIOFormat(): IOFormat<T>? { return ioFormats.values.find { it.type.isSuperclassOf(T::class) } as IOFormat<T>? diff --git a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/functionsJVM.kt b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/functionsJVM.kt index fae986d7..ffb924ef 100644 --- a/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/functionsJVM.kt +++ b/dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/functionsJVM.kt @@ -3,7 +3,6 @@ package hep.dataforge.io import hep.dataforge.io.functions.FunctionServer import hep.dataforge.io.functions.function import hep.dataforge.meta.Meta -import hep.dataforge.meta.buildMeta import hep.dataforge.names.Name import kotlin.reflect.KClass import kotlin.reflect.full.isSuperclassOf @@ -14,7 +13,7 @@ fun IOPlugin.resolveIOFormatName(type: KClass<*>): Name { ?: error("Can't resolve IOFormat for type $type") } -inline fun <reified T : Any, reified R : Any> IOPlugin.generateFunctionMeta(functionName: String): Meta = buildMeta { +inline fun <reified T : Any, reified R : Any> IOPlugin.generateFunctionMeta(functionName: String): Meta = Meta { FunctionServer.FUNCTION_NAME_KEY put functionName FunctionServer.INPUT_FORMAT_KEY put resolveIOFormatName(T::class).toString() FunctionServer.OUTPUT_FORMAT_KEY put resolveIOFormatName(R::class).toString() diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt index 833117b5..53a2e184 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Config.kt @@ -4,6 +4,7 @@ import hep.dataforge.names.Name import hep.dataforge.names.NameToken import hep.dataforge.names.asName import hep.dataforge.names.plus +import kotlinx.serialization.* //TODO add validator to configuration @@ -20,6 +21,7 @@ interface ObservableMeta : Meta { /** * Mutable meta representing object state */ +@Serializable class Config : AbstractMutableMeta<Config>(), ObservableMeta { private val listeners = HashSet<MetaListener>() @@ -66,8 +68,19 @@ class Config : AbstractMutableMeta<Config>(), ObservableMeta { override fun empty(): Config = Config() - companion object { + @Serializer(Config::class) + companion object ConfigSerializer : KSerializer<Config> { + fun empty(): Config = Config() + override val descriptor: SerialDescriptor get() = MetaSerializer.descriptor + + override fun deserialize(decoder: Decoder): Config { + return MetaSerializer.deserialize(decoder).asConfig() + } + + override fun serialize(encoder: Encoder, value: Config) { + MetaSerializer.serialize(encoder, value) + } } } 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 e5cda8f6..672a1922 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt @@ -15,6 +15,7 @@ import kotlinx.serialization.* */ @Serializable sealed class MetaItem<out M : Meta> { + @Serializable data class ValueItem(val value: Value) : MetaItem<Nothing>() { override fun toString(): String = value.toString() @@ -32,12 +33,13 @@ sealed class MetaItem<out M : Meta> { } @Serializable - data class NodeItem<M : Meta>(val node: M) : MetaItem<M>() { + data class NodeItem<M : Meta>(@Serializable(MetaSerializer::class) val node: M) : MetaItem<M>() { + //Fixing serializer for node could cause class cast problems, but it should not since Meta descendants are not serializeable override fun toString(): String = node.toString() @Serializer(NodeItem::class) - companion object : KSerializer<NodeItem<*>> { - override val descriptor: SerialDescriptor get() = ValueSerializer.descriptor + companion object : KSerializer<NodeItem<out Meta>> { + override val descriptor: SerialDescriptor get() = MetaSerializer.descriptor override fun deserialize(decoder: Decoder): NodeItem<*> = NodeItem(MetaSerializer.deserialize(decoder)) @@ -199,8 +201,9 @@ abstract class AbstractMetaNode<M : MetaNode<M>> : MetaNode<M>, MetaBase() * * If the argument is possibly mutable node, it is copied on creation */ -class SealedMeta internal constructor(override val items: Map<NameToken, MetaItem<SealedMeta>>) : - AbstractMetaNode<SealedMeta>() +class SealedMeta internal constructor( + override val items: Map<NameToken, MetaItem<SealedMeta>> +) : AbstractMetaNode<SealedMeta>() /** * Generate sealed node from [this]. If it is already sealed return it as is diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaSerializer.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaSerializer.kt index 63a45664..6f0db59e 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaSerializer.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MetaSerializer.kt @@ -8,8 +8,6 @@ import kotlinx.serialization.json.JsonObjectSerializer import kotlinx.serialization.json.JsonOutput -private class DeserializedMeta(override val items: Map<NameToken, MetaItem<Meta>>) : MetaBase() - /** * Serialized for meta */ @@ -26,7 +24,9 @@ object MetaSerializer : KSerializer<Meta> { return if (decoder is JsonInput) { JsonObjectSerializer.deserialize(decoder).toMeta() } else { - DeserializedMeta(mapSerializer.deserialize(decoder)) + object : MetaBase() { + override val items: Map<NameToken, MetaItem<*>> = mapSerializer.deserialize(decoder) + } } } @@ -37,17 +37,4 @@ object MetaSerializer : KSerializer<Meta> { mapSerializer.serialize(encoder, value.items) } } -} - -@Serializer(Config::class) -object ConfigSerializer : KSerializer<Config> { - override val descriptor: SerialDescriptor get() = MetaSerializer.descriptor - - override fun deserialize(decoder: Decoder): Config { - return MetaSerializer.deserialize(decoder).asConfig() - } - - override fun serialize(encoder: Encoder, value: Config) { - MetaSerializer.serialize(encoder, value) - } } \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt index da02c30e..da6d92a8 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt @@ -168,8 +168,9 @@ fun Configurable.float(default: Float, key: Name? = null): ReadWriteProperty<Any /** * Enum delegate */ -inline fun <reified E : Enum<E>> Configurable.enum(default: E, key: Name? = null): ReadWriteProperty<Any?, E> = - item(default, key).transform { it.enum<E>() ?: default } +inline fun <reified E : Enum<E>> Configurable.enum(default: E, key: Name? = null): ReadWriteProperty<Any?, E> { + return item(default, key).transform { it.enum<E>() ?: default } +} /* * Extra delegates for special cases diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index cc4fdc293d0e50b0ad9b65c16e7ddd1db2f6025b..f3d88b1c2faf2fc91d853cd5d4242b5547257070 100644 GIT binary patch delta 12333 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 delta 12451 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6ce793f2..a2bf1313 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew.bat b/gradlew.bat index 9618d8d9..62bd9b9c 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" From 2c52e9aaa34e160f69c8f9c57fbc3937900016d4 Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Sun, 15 Mar 2020 13:33:20 +0300 Subject: [PATCH 38/41] Workaround for enum delegate --- build.gradle.kts | 2 +- .../kotlin/hep/dataforge/meta/scheme/Configurable.kt | 8 ++++++-- .../hep/dataforge/meta/scheme/ConfigurableDelegate.kt | 7 +++---- .../kotlin/hep/dataforge/meta/scheme/Specification.kt | 11 +++++++---- .../commonMain/kotlin/hep/dataforge/values/Value.kt | 4 +++- .../kotlin/hep/dataforge/meta/MetaDelegateTest.kt | 8 +++++--- .../kotlin/hep/dataforge/tables/ColumnScheme.kt | 3 ++- 7 files changed, 27 insertions(+), 16 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 0d4cc1fb..fca616de 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,6 @@ plugins { - val toolsVersion = "0.4.0" + val toolsVersion = "0.4.0-dev" id("scientifik.mpp") version toolsVersion apply false id("scientifik.jvm") version toolsVersion apply false id("scientifik.publish") version toolsVersion apply false diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Configurable.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Configurable.kt index 42aaeb07..df8c8e93 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Configurable.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Configurable.kt @@ -4,6 +4,7 @@ import hep.dataforge.meta.* import hep.dataforge.meta.descriptors.* import hep.dataforge.names.Name import hep.dataforge.names.toName +import hep.dataforge.values.Value /** * A container that holds a [Config] and a default item provider. @@ -45,17 +46,20 @@ fun Configurable.getProperty(key: String) = getProperty(key.toName()) * Set a configurable property */ fun Configurable.setProperty(name: Name, item: MetaItem<*>?) { - if(validateItem(name,item)) { + if (validateItem(name, item)) { config[name] = item } else { error("Validation failed for property $name with value $item") } } +fun Configurable.setProperty(name: Name, value: Value) = setProperty(name, MetaItem.ValueItem(value)) +fun Configurable.setProperty(name: Name, meta: Meta) = setProperty(name, MetaItem.NodeItem(meta)) + fun Configurable.setProperty(key: String, item: MetaItem<*>?) { setProperty(key.toName(), item) } fun <T : Configurable> T.configure(meta: Meta): T = this.apply { config.update(meta) } -fun <T : Configurable> T.configure(action: Config.() -> Unit): T = apply { config.apply(action) } +inline fun <T : Configurable> T.configure(action: Config.() -> Unit): T = apply { config.apply(action) } diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt index da6d92a8..a83bc62c 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt @@ -168,14 +168,13 @@ fun Configurable.float(default: Float, key: Name? = null): ReadWriteProperty<Any /** * Enum delegate */ -inline fun <reified E : Enum<E>> Configurable.enum(default: E, key: Name? = null): ReadWriteProperty<Any?, E> { - return item(default, key).transform { it.enum<E>() ?: default } -} +fun <E : Enum<E>> Configurable.enum( + default: E, key: Name? = null, resolve: MetaItem<*>.() -> E? +): ReadWriteProperty<Any?, E> = item(default, key).transform {it?.resolve() ?: default } /* * Extra delegates for special cases */ - fun Configurable.stringList(vararg strings: String, key: Name? = null): ReadWriteProperty<Any?, List<String>> = item(listOf(*strings), key) { it?.value?.stringList ?: emptyList() diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Specification.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Specification.kt index 4aa7bf02..1783e841 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Specification.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Specification.kt @@ -29,14 +29,14 @@ interface Specification<T : Configurable> { /** * Wrap a configuration using static meta as default */ - fun wrap(config: Config = Config(), default: Meta): T = wrap(config){default[it]} + fun wrap(config: Config = Config(), default: Meta): T = wrap(config) { default[it] } /** * Wrap a configuration using static meta as default */ fun wrap(default: Meta): T = wrap( Config() - ){default[it]} + ) { default[it] } } /** @@ -57,8 +57,11 @@ fun <C : Configurable, S : Specification<C>> Configurable.update(spec: S, action fun <C : Configurable, S : Specification<C>> S.createStyle(action: C.() -> Unit): Meta = Config().also { update(it, action) } -fun <T : Configurable> MetaItem<*>.spec(spec: Specification<T>): T? = node?.let { spec.wrap( - Config(), it) } +fun <T : Configurable> MetaItem<*>.spec(spec: Specification<T>): T? = node?.let { + spec.wrap( + Config(), it + ) +} @JvmName("configSpec") fun <T : Configurable> MetaItem<Config>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(it) } diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/Value.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/Value.kt index ccc92d50..28a5838c 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/Value.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/values/Value.kt @@ -44,7 +44,7 @@ interface Value { * get this value represented as List */ val list: List<Value> - get() = if(this == Null) emptyList() else listOf(this) + get() = if (this == Null) emptyList() else listOf(this) override fun equals(other: Any?): Boolean @@ -231,6 +231,8 @@ fun FloatArray.asValue(): Value = if (isEmpty()) Null else ListValue(map { Numbe fun ByteArray.asValue(): Value = if (isEmpty()) Null else ListValue(map { NumberValue(it) }) +fun <E : Enum<E>> E.asValue(): Value = EnumValue(this) + /** * Create Value from String using closest match conversion diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt index 4cf2002f..277c2a6c 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt @@ -13,15 +13,17 @@ class MetaDelegateTest { class InnerSpec : Scheme() { var innerValue by string() - companion object: SchemeSpec<InnerSpec>(::InnerSpec) + + companion object : SchemeSpec<InnerSpec>(::InnerSpec) } class TestScheme : Scheme() { var myValue by string() var safeValue by double(2.2) - var enumValue by enum(TestEnum.YES) + var enumValue by enum(TestEnum.YES) { enum<TestEnum>() } var inner by spec(InnerSpec) - companion object: SchemeSpec<TestScheme>(::TestScheme) + + companion object : SchemeSpec<TestScheme>(::TestScheme) } @Test diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnScheme.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnScheme.kt index 2b65b234..7d364784 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnScheme.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnScheme.kt @@ -1,5 +1,6 @@ package hep.dataforge.tables +import hep.dataforge.meta.enum import hep.dataforge.meta.scheme.Scheme import hep.dataforge.meta.scheme.SchemeSpec import hep.dataforge.meta.scheme.enum @@ -13,5 +14,5 @@ open class ColumnScheme : Scheme() { } class ValueColumnScheme : ColumnScheme() { - var valueType by enum(ValueType.STRING) + var valueType by enum(ValueType.STRING){enum<ValueType>()} } \ No newline at end of file From b78fc2e7d7fe6ec675c42fbe3e5c44f4da1e6b7a Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Sun, 15 Mar 2020 13:34:13 +0300 Subject: [PATCH 39/41] bump version --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index fca616de..7e41aa3b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("scientifik.publish") version toolsVersion apply false } -val dataforgeVersion by extra("0.1.5-dev-10") +val dataforgeVersion by extra("0.1.5-dev-11") val bintrayRepo by extra("dataforge") val githubProject by extra("dataforge-core") From bc8878da4822d9fefb7f8219b814d03701f79189 Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Sun, 15 Mar 2020 13:45:24 +0300 Subject: [PATCH 40/41] Remove redundant cast in tables --- .../commonMain/kotlin/hep/dataforge/tables/io/TextRows.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) 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 2675b483..ae3c386e 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 @@ -134,14 +134,11 @@ private fun Output.writeValue(value: Value, width: Int, left: Boolean = true) { * Write rows without header to the output */ suspend fun Output.writeRows(rows: Rows<Value>) { - @Suppress("UNCHECKED_CAST") val header = rows.header.map { - if (it.type != Value::class) error("Expected Value column, but found ${it.type}") else (it as ColumnHeader<Value>) - } - val widths: List<Int> = header.map { + val widths: List<Int> = rows.header.map { it.textWidth } rows.rowFlow().collect { row -> - header.forEachIndexed { index, columnHeader -> + rows.header.forEachIndexed { index, columnHeader -> writeValue(row[columnHeader] ?: Null, widths[index]) } writeUtf8String("\r\n") From e835d811833a19ed7a94f6559f2143f602c66e4a Mon Sep 17 00:00:00 2001 From: Alexander Nozik <altavir@gmail.com> Date: Mon, 16 Mar 2020 22:52:54 +0300 Subject: [PATCH 41/41] Utility methods to access Configurable properties --- build.gradle.kts | 4 +- .../meta/descriptors/ItemDescriptor.kt | 1 + .../hep/dataforge/meta/scheme/Configurable.kt | 39 +++++++++--------- .../meta/scheme/ConfigurableDelegate.kt | 40 +++++++++++++------ .../hep/dataforge/meta/scheme/Scheme.kt | 8 ++-- 5 files changed, 55 insertions(+), 37 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 7e41aa3b..1fe59eff 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,12 +1,12 @@ plugins { - val toolsVersion = "0.4.0-dev" + val toolsVersion = "0.4.0" id("scientifik.mpp") version toolsVersion apply false id("scientifik.jvm") version toolsVersion apply false id("scientifik.publish") version toolsVersion apply false } -val dataforgeVersion by extra("0.1.5-dev-11") +val dataforgeVersion by extra("0.1.5") val bintrayRepo by extra("dataforge") val githubProject by extra("dataforge-core") 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 4ff5de11..84d0d5c0 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 @@ -67,6 +67,7 @@ fun ItemDescriptor.validateItem(item: MetaItem<*>?): Boolean { * * @author Alexander Nozik */ +@DFBuilder class NodeDescriptor : ItemDescriptor() { /** diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Configurable.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Configurable.kt index df8c8e93..4ae5e180 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Configurable.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Configurable.kt @@ -32,34 +32,37 @@ interface Configurable : Described { } override val descriptor: NodeDescriptor? get() = null -} -/** - * Get a property with default - */ -fun Configurable.getProperty(name: Name): MetaItem<*>? = - config[name] ?: getDefaultItem(name) ?: descriptor?.get(name)?.defaultItem() + /** + * Get a property with default + */ + fun getProperty(name: Name): MetaItem<*>? = + config[name] ?: getDefaultItem(name) ?: descriptor?.get(name)?.defaultItem() -fun Configurable.getProperty(key: String) = getProperty(key.toName()) - -/** - * Set a configurable property - */ -fun Configurable.setProperty(name: Name, item: MetaItem<*>?) { - if (validateItem(name, item)) { - config[name] = item - } else { - error("Validation failed for property $name with value $item") + /** + * Set a configurable property + */ + fun setProperty(name: Name, item: MetaItem<*>?) { + if (validateItem(name, item)) { + config[name] = item + } else { + error("Validation failed for property $name with value $item") + } } } -fun Configurable.setProperty(name: Name, value: Value) = setProperty(name, MetaItem.ValueItem(value)) -fun Configurable.setProperty(name: Name, meta: Meta) = setProperty(name, MetaItem.NodeItem(meta)) +fun Configurable.getProperty(key: String) = getProperty(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.setProperty(key: String, item: MetaItem<*>?) { setProperty(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 <T : Configurable> T.configure(meta: Meta): T = this.apply { config.update(meta) } inline fun <T : Configurable> T.configure(action: Config.() -> Unit): T = apply { config.apply(action) } diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt index a83bc62c..c582282b 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt @@ -170,7 +170,7 @@ fun Configurable.float(default: Float, key: Name? = null): ReadWriteProperty<Any */ fun <E : Enum<E>> Configurable.enum( default: E, key: Name? = null, resolve: MetaItem<*>.() -> E? -): ReadWriteProperty<Any?, E> = item(default, key).transform {it?.resolve() ?: default } +): ReadWriteProperty<Any?, E> = item(default, key).transform { it?.resolve() ?: default } /* * Extra delegates for special cases @@ -206,17 +206,31 @@ fun Configurable.node(key: Name? = null): ReadWriteProperty<Any?, Meta?> = item( writer = { it?.let { MetaItem.NodeItem(it) } } ) -fun <T : Configurable> Configurable.spec(spec: Specification<T>, key: Name? = null): ReadWriteProperty<Any?, T?> = - object : ReadWriteProperty<Any?, T?> { - override fun getValue(thisRef: Any?, property: KProperty<*>): T? { - val name = key ?: property.name.asName() - return 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 <T : Configurable> Configurable.spec( + spec: Specification<T>, key: Name? = null +): ReadWriteProperty<Any?, T?> = object : ReadWriteProperty<Any?, T?> { + override fun getValue(thisRef: Any?, property: KProperty<*>): T? { + val name = key ?: property.name.asName() + return 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 <T : Configurable> Configurable.spec( + spec: Specification<T>, default: T, key: Name? = null +): ReadWriteProperty<Any?, T> = object : ReadWriteProperty<Any?, T> { + override fun getValue(thisRef: Any?, property: KProperty<*>): T { + val name = key ?: property.name.asName() + return 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/scheme/Scheme.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt index 8e74ba7b..b8d6257f 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt @@ -18,11 +18,10 @@ open class Scheme() : Configurable, Described, MetaRepr { //constructor(config: Config, default: Meta) : this(config, { default[it] }) constructor(config: Config) : this(config, { null }) - final override var config: Config = - Config() + final override var config: Config = Config() internal set - lateinit var defaultProvider: (Name) -> MetaItem<*>? + var defaultProvider: (Name) -> MetaItem<*>? = { null } internal set final override var descriptor: NodeDescriptor? = null @@ -53,9 +52,10 @@ open class Scheme() : Configurable, Described, MetaRepr { token to item } ?: emptyMap() } - } +inline operator fun <T : Scheme> T.invoke(block: T.() -> Unit) = apply(block) + /** * A specification for simplified generation of wrappers */