Merge pull request #41 from mipt-npm/dev

0.1.5
This commit is contained in:
Alexander Nozik 2020-03-25 09:07:21 +03:00 committed by GitHub
commit 7dde03ee68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
142 changed files with 3723 additions and 2413 deletions
.github/workflows
README.mdbuild.gradle.kts
dataforge-context
build.gradle.kts
src
commonMain/kotlin/hep/dataforge/context
jvmMain/kotlin/hep/dataforge/descriptors
dataforge-data/src
commonMain/kotlin/hep/dataforge/data
jvmMain/kotlin/hep/dataforge/data
dataforge-io
dataforge-meta
dataforge-output
dataforge-output-html
build.gradle.kts
src/commonMain/kotlin/hep/dataforge/output/html
src/commonMain/kotlin/hep/dataforge/output

@ -1,4 +1,4 @@
name: Java CI name: Gradle build
on: [push] on: [push]

@ -1,6 +1,12 @@
[![JetBrains Research](https://jb.gg/badges/research.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
[![DOI](https://zenodo.org/badge/148831678.svg)](https://zenodo.org/badge/latestdoi/148831678) [![DOI](https://zenodo.org/badge/148831678.svg)](https://zenodo.org/badge/latestdoi/148831678)
![Gradle build](https://github.com/mipt-npm/dataforge-core/workflows/Gradle%20build/badge.svg)
[ ![Download](https://api.bintray.com/packages/mipt-npm/dataforge/dataforge-meta/images/download.svg) ](https://bintray.com/mipt-npm/dataforge/dataforge-meta/_latestVersion)
# Questions and Answers # # Questions and Answers #
In this section we will try to cover DataForge main ideas in the form of questions and answers. In this section we will try to cover DataForge main ideas in the form of questions and answers.

@ -1,10 +1,12 @@
plugins { plugins {
id("scientifik.mpp") version "0.2.1" apply false val toolsVersion = "0.4.0"
id("scientifik.jvm") version "0.2.1" apply false id("scientifik.mpp") version toolsVersion apply false
id("scientifik.publish") version "0.2.1" apply false id("scientifik.jvm") version toolsVersion apply false
id("scientifik.publish") version toolsVersion apply false
} }
val dataforgeVersion by extra("0.1.4") val dataforgeVersion by extra("0.1.5")
val bintrayRepo by extra("dataforge") val bintrayRepo by extra("dataforge")
val githubProject by extra("dataforge-core") val githubProject by extra("dataforge-core")
@ -12,10 +14,12 @@ val githubProject by extra("dataforge-core")
allprojects { allprojects {
group = "hep.dataforge" group = "hep.dataforge"
version = dataforgeVersion version = dataforgeVersion
repositories {
mavenLocal()
}
} }
subprojects { subprojects {
if (name.startsWith("dataforge")) {
apply(plugin = "scientifik.publish") apply(plugin = "scientifik.publish")
} }
}

@ -1,10 +1,12 @@
import scientifik.coroutines
plugins { plugins {
id("scientifik.mpp") id("scientifik.mpp")
} }
description = "Context and provider definitions" description = "Context and provider definitions"
val coroutinesVersion: String = Scientifik.coroutinesVersion coroutines()
kotlin { kotlin {
sourceSets { sourceSets {
@ -12,21 +14,18 @@ kotlin {
dependencies { dependencies {
api(project(":dataforge-meta")) api(project(":dataforge-meta"))
api(kotlin("reflect")) 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 { val jvmMain by getting {
dependencies { 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("ch.qos.logback:logback-classic:1.2.3")
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
} }
} }
val jsMain by getting { val jsMain by getting {
dependencies { 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")
} }
} }
} }

@ -103,7 +103,7 @@ open class Context(
plugins.forEach { it.detach() } plugins.forEach { it.detach() }
} }
override fun toMeta(): Meta = buildMeta { override fun toMeta(): Meta = Meta {
"parent" to parent?.name "parent" to parent?.name
"properties" put properties.seal() "properties" put properties.seal()
"plugins" put plugins.map { it.toMeta() } "plugins" put plugins.map { it.toMeta() }

@ -1,5 +1,7 @@
package hep.dataforge.context package hep.dataforge.context
import hep.dataforge.meta.DFBuilder
import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaBuilder import hep.dataforge.meta.MetaBuilder
import hep.dataforge.meta.buildMeta import hep.dataforge.meta.buildMeta
import hep.dataforge.names.toName import hep.dataforge.names.toName
@ -7,6 +9,7 @@ import hep.dataforge.names.toName
/** /**
* A convenience builder for context * A convenience builder for context
*/ */
@DFBuilder
class ContextBuilder(var name: String = "@anonymous", val parent: Context = Global) { class ContextBuilder(var name: String = "@anonymous", val parent: Context = Global) {
private val plugins = ArrayList<Plugin>() private val plugins = ArrayList<Plugin>()
private var meta = MetaBuilder() private var meta = MetaBuilder()
@ -20,11 +23,11 @@ class ContextBuilder(var name: String = "@anonymous", val parent: Context = Glob
} }
fun plugin(tag: PluginTag, action: MetaBuilder.() -> Unit = {}) { 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 = {}) { 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 = {}) { fun plugin(name: String, group: String = "", version: String = "", action: MetaBuilder.() -> Unit = {}) {

@ -65,7 +65,7 @@ interface Plugin : Named, ContextAware, Provider, MetaRepr {
*/ */
fun detach() fun detach()
override fun toMeta(): Meta = buildMeta { override fun toMeta(): Meta = Meta {
"context" put context.name.toString() "context" put context.name.toString()
"type" to this::class.simpleName "type" to this::class.simpleName
"tag" put tag "tag" put tag

@ -62,7 +62,7 @@ class PluginManager(override val context: Context) : ContextAware, Iterable<Plug
* @return * @return
*/ */
@Suppress("UNCHECKED_CAST") @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? 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? = inline operator fun <reified T : Any> get(tag: PluginTag? = null, recursive: Boolean = true): T? =
@ -102,7 +102,7 @@ class PluginManager(override val context: Context) : ContextAware, Iterable<Plug
load(factory(meta, context)) load(factory(meta, context))
fun <T : Plugin> load(factory: PluginFactory<T>, metaBuilder: MetaBuilder.() -> Unit): T = fun <T : Plugin> load(factory: PluginFactory<T>, metaBuilder: MetaBuilder.() -> Unit): T =
load(factory, buildMeta(metaBuilder)) load(factory, Meta(metaBuilder))
/** /**
* Remove a plugin from [PluginManager] * Remove a plugin from [PluginManager]
@ -134,7 +134,7 @@ class PluginManager(override val context: Context) : ContextAware, Iterable<Plug
factory: PluginFactory<T>, factory: PluginFactory<T>,
recursive: Boolean = true, recursive: Boolean = true,
metaBuilder: MetaBuilder.() -> Unit metaBuilder: MetaBuilder.() -> Unit
): T = fetch(factory, recursive, buildMeta(metaBuilder)) ): T = fetch(factory, recursive, Meta(metaBuilder))
override fun iterator(): Iterator<Plugin> = plugins.iterator() override fun iterator(): Iterator<Plugin> = plugins.iterator()

@ -36,7 +36,7 @@ data class PluginTag(
override fun toString(): String = listOf(group, name, version).joinToString(separator = ":") override fun toString(): String = listOf(group, name, version).joinToString(separator = ":")
override fun toMeta(): Meta = buildMeta { override fun toMeta(): Meta = Meta {
"name" put name "name" put name
"group" put group "group" put group
"version" put version "version" put version

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

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

@ -19,7 +19,7 @@ interface Data<out T : Any> : Goal<T>, MetaRepr{
*/ */
val meta: Meta val meta: Meta
override fun toMeta(): Meta = buildMeta { override fun toMeta(): Meta = Meta {
"type" put (type.simpleName?:"undefined") "type" put (type.simpleName?:"undefined")
if(!meta.isEmpty()) { if(!meta.isEmpty()) {
"meta" put meta "meta" put meta
@ -91,7 +91,7 @@ fun <T : Any, R : Any> Data<T>.map(
meta: Meta = this.meta, meta: Meta = this.meta,
block: suspend CoroutineScope.(T) -> R block: suspend CoroutineScope.(T) -> R
): Data<R> = DynamicData(outputType, meta, coroutineContext, listOf(this)) { ): 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, meta: Meta = this.meta,
noinline block: suspend CoroutineScope.(T) -> R noinline block: suspend CoroutineScope.(T) -> R
): Data<R> = DynamicData(R::class, meta, coroutineContext, listOf(this)) { ): 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, coroutineContext,
this this
) { ) {
block(map { run { it.await(this) } }) block(map { run { it.await() } })
} }
fun <K, T : Any, R : Any> Map<K, Data<T>>.reduce( 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, coroutineContext,
this.values 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, coroutineContext,
this.values this.values
) { ) {
block(mapValues { it.value.await(this) }) block(mapValues { it.value.await() })
} }

@ -1,10 +1,13 @@
package hep.dataforge.data package hep.dataforge.data
import hep.dataforge.meta.* 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 import hep.dataforge.names.toName
class DataFilter(override val config: Config) : Specific { class DataFilter : Scheme() {
/** /**
* A source node for the filter * A source node for the filter
*/ */
@ -22,9 +25,7 @@ class DataFilter(override val config: Config) : Specific {
fun isEmpty(): Boolean = config.isEmpty() fun isEmpty(): Boolean = config.isEmpty()
companion object : Specification<DataFilter> { companion object : SchemeSpec<DataFilter>(::DataFilter)
override fun wrap(config: Config): DataFilter = DataFilter(config)
}
} }
/** /**
@ -54,4 +55,4 @@ fun <T : Any> DataNode<T>.filter(filter: Meta): DataNode<T> = filter(DataFilter.
* Filter data using [DataFilter] builder * Filter data using [DataFilter] builder
*/ */
fun <T : Any> DataNode<T>.filter(filterBuilder: DataFilter.() -> Unit): DataNode<T> = fun <T : Any> DataNode<T>.filter(filterBuilder: DataFilter.() -> Unit): DataNode<T> =
filter(DataFilter.build(filterBuilder)) filter(DataFilter.invoke(filterBuilder))

@ -4,6 +4,7 @@ import hep.dataforge.meta.*
import hep.dataforge.names.* import hep.dataforge.names.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.collections.component1 import kotlin.collections.component1
import kotlin.collections.component2 import kotlin.collections.component2
@ -13,16 +14,22 @@ import kotlin.reflect.KClass
sealed class DataItem<out T : Any> : MetaRepr { sealed class DataItem<out T : Any> : MetaRepr {
abstract val type: KClass<out T> abstract val type: KClass<out T>
class Node<out T : Any>(val value: DataNode<T>) : DataItem<T>() { abstract val meta: Meta
override val type: KClass<out T> get() = value.type
override fun toMeta(): Meta = value.toMeta() class Node<out T : Any>(val node: DataNode<T>) : DataItem<T>() {
override val type: KClass<out T> get() = node.type
override fun toMeta(): Meta = node.toMeta()
override val meta: Meta get() = node.meta
} }
class Leaf<out T : Any>(val value: Data<T>) : DataItem<T>() { class Leaf<out T : Any>(val data: Data<T>) : DataItem<T>() {
override val type: KClass<out T> get() = value.type 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() = data.meta
} }
} }
@ -38,7 +45,9 @@ interface DataNode<out T : Any> : MetaRepr {
val items: Map<NameToken, DataItem<T>> val items: Map<NameToken, DataItem<T>>
override fun toMeta(): Meta = buildMeta { val meta: Meta
override fun toMeta(): Meta = Meta {
"type" put (type.simpleName ?: "undefined") "type" put (type.simpleName ?: "undefined")
"items" put { "items" put {
this@DataNode.items.forEach { this@DataNode.items.forEach {
@ -47,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 { companion object {
const val TYPE = "dataNode" const val TYPE = "dataNode"
@ -60,28 +82,10 @@ interface DataNode<out T : Any> : MetaRepr {
} }
} }
val <T : Any> DataItem<T>?.node: DataNode<T>? get() = (this as? DataItem.Node<T>)?.value suspend fun <T: Any> DataNode<T>.join(): Unit = coroutineScope { startAll().join() }
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
* Start computation for all goals in data node val <T : Any> DataItem<T>?.data: Data<T>? get() = (this as? DataItem.Leaf<T>)?.data
*/
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)
}
}
}
operator fun <T : Any> DataNode<T>.get(name: Name): DataItem<T>? = when (name.length) { operator fun <T : Any> DataNode<T>.get(name: Name): DataItem<T>? = when (name.length) {
0 -> error("Empty name") 0 -> error("Empty name")
@ -98,7 +102,7 @@ fun <T : Any> DataNode<T>.asSequence(): Sequence<Pair<Name, DataItem<T>>> = sequ
items.forEach { (head, item) -> items.forEach { (head, item) ->
yield(head.asName() to item) yield(head.asName() to item)
if (item is DataItem.Node) { if (item is DataItem.Node) {
val subSequence = item.value.asSequence() val subSequence = item.node.asSequence()
.map { (name, data) -> (head.asName() + name) to data } .map { (name, data) -> (head.asName() + name) to data }
yieldAll(subSequence) yieldAll(subSequence)
} }
@ -111,9 +115,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 { fun <T : Any> DataNode<T>.dataSequence(): Sequence<Pair<Name, Data<T>>> = sequence {
items.forEach { (head, item) -> items.forEach { (head, item) ->
when (item) { when (item) {
is DataItem.Leaf -> yield(head.asName() to item.value) is DataItem.Leaf -> yield(head.asName() to item.data)
is DataItem.Node -> { is DataItem.Node -> {
val subSequence = item.value.dataSequence() val subSequence = item.node.dataSequence()
.map { (name, data) -> (head.asName() + name) to data } .map { (name, data) -> (head.asName() + name) to data }
yieldAll(subSequence) yieldAll(subSequence)
} }
@ -125,12 +129,9 @@ operator fun <T : Any> DataNode<T>.iterator(): Iterator<Pair<Name, DataItem<T>>>
class DataTree<out T : Any> internal constructor( class DataTree<out T : Any> internal constructor(
override val type: KClass<out T>, override val type: KClass<out T>,
override val items: Map<NameToken, DataItem<T>> override val items: Map<NameToken, DataItem<T>>,
) : DataNode<T> { override val meta: Meta
override fun toString(): String { ) : DataNode<T>
return super.toString()
}
}
private sealed class DataTreeBuilderItem<out T : Any> { private sealed class DataTreeBuilderItem<out T : Any> {
class Node<T : Any>(val tree: DataTreeBuilder<T>) : DataTreeBuilderItem<T>() class Node<T : Any>(val tree: DataTreeBuilder<T>) : DataTreeBuilderItem<T>()
@ -144,6 +145,8 @@ private sealed class DataTreeBuilderItem<out T : Any> {
class DataTreeBuilder<T : Any>(val type: KClass<out T>) { class DataTreeBuilder<T : Any>(val type: KClass<out T>) {
private val map = HashMap<NameToken, DataTreeBuilderItem<T>>() private val map = HashMap<NameToken, DataTreeBuilderItem<T>>()
private var meta = MetaBuilder()
operator fun set(token: NameToken, node: DataTreeBuilder<out T>) { operator fun set(token: NameToken, node: DataTreeBuilder<out T>) {
if (map.containsKey(token)) error("Tree entry with name $token is not empty") if (map.containsKey(token)) error("Tree entry with name $token is not empty")
map[token] = DataTreeBuilderItem.Node(node) map[token] = DataTreeBuilderItem.Node(node)
@ -189,8 +192,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, node: DataNode<T>) = set(name, node.builder())
operator fun set(name: Name, item: DataItem<T>) = when (item) { operator fun set(name: Name, item: DataItem<T>) = when (item) {
is DataItem.Node<T> -> set(name, item.value.builder()) is DataItem.Node<T> -> set(name, item.node.builder())
is DataItem.Leaf<T> -> set(name, item.value) is DataItem.Leaf<T> -> set(name, item.data)
} }
/** /**
@ -211,11 +214,21 @@ class DataTreeBuilder<T : Any>(val type: KClass<out T>) {
infix fun String.put(block: DataTreeBuilder<T>.() -> Unit) = set(toName(), DataTreeBuilder(type).apply(block)) 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>) { fun update(node: DataNode<T>) {
node.dataSequence().forEach { node.dataSequence().forEach {
//TODO check if the place is occupied //TODO check if the place is occupied
this[it.first] = it.second this[it.first] = it.second
} }
meta.update(node.meta)
}
fun meta(block: MetaBuilder.() -> Unit) = meta.apply(block)
fun meta(meta: Meta) {
this.meta = meta.builder()
} }
fun build(): DataTree<T> { fun build(): DataTree<T> {
@ -225,7 +238,7 @@ class DataTreeBuilder<T : Any>(val type: KClass<out T>) {
is DataTreeBuilderItem.Node -> DataItem.Node(value.tree.build()) is DataTreeBuilderItem.Node -> DataItem.Node(value.tree.build())
} }
} }
return DataTree(type, resMap) return DataTree(type, resMap, meta.seal())
} }
} }
@ -242,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 = {}) { 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 = {}) { 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>) { fun <T : Any> DataTreeBuilder<T>.node(name: Name, node: DataNode<T>) {

@ -15,9 +15,7 @@ interface Goal<out T> {
* Get ongoing computation or start a new one. * Get ongoing computation or start a new one.
* Does not guarantee thread safety. In case of multi-thread access, could create orphan computations. * Does not guarantee thread safety. In case of multi-thread access, could create orphan computations.
*/ */
fun startAsync(scope: CoroutineScope): Deferred<T> fun CoroutineScope.startAsync(): Deferred<T>
suspend fun CoroutineScope.await(): T = startAsync(this).await()
/** /**
* Reset the computation * 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 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> { open class StaticGoal<T>(val value: T) : Goal<T> {
override val dependencies: Collection<Goal<*>> get() = emptyList() override val dependencies: Collection<Goal<*>> get() = emptyList()
override val result: Deferred<T> = CompletableDeferred(value) 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() { override fun reset() {
//doNothing //doNothing
@ -59,11 +55,12 @@ open class DynamicGoal<T>(
* Get ongoing computation or start a new one. * Get ongoing computation or start a new one.
* Does not guarantee thread safety. In case of multi-thread access, could create orphan computations. * Does not guarantee thread safety. In case of multi-thread access, could create orphan computations.
*/ */
override fun startAsync(scope: CoroutineScope): Deferred<T> { override fun CoroutineScope.startAsync(): Deferred<T> {
val startedDependencies = this.dependencies.map { goal -> val startedDependencies = this@DynamicGoal.dependencies.map { goal ->
goal.startAsync(scope) goal.run { startAsync() }
} }
return result ?: scope.async(coroutineContext + CoroutineMonitor() + Dependencies(startedDependencies)) { return result
?: async(this@DynamicGoal.coroutineContext + CoroutineMonitor() + Dependencies(startedDependencies)) {
startedDependencies.forEach { deferred -> startedDependencies.forEach { deferred ->
deferred.invokeOnCompletion { error -> deferred.invokeOnCompletion { error ->
if (error != null) cancel(CancellationException("Dependency $deferred failed with error: ${error.message}")) if (error != null) cancel(CancellationException("Dependency $deferred failed with error: ${error.message}"))
@ -89,7 +86,7 @@ fun <T, R> Goal<T>.map(
coroutineContext: CoroutineContext = EmptyCoroutineContext, coroutineContext: CoroutineContext = EmptyCoroutineContext,
block: suspend CoroutineScope.(T) -> R block: suspend CoroutineScope.(T) -> R
): Goal<R> = DynamicGoal(coroutineContext, listOf(this)) { ): 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, coroutineContext: CoroutineContext = EmptyCoroutineContext,
block: suspend CoroutineScope.(Collection<T>) -> R block: suspend CoroutineScope.(Collection<T>) -> R
): Goal<R> = DynamicGoal(coroutineContext, this) { ): 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, coroutineContext: CoroutineContext = EmptyCoroutineContext,
block: suspend CoroutineScope.(Map<K, T>) -> R block: suspend CoroutineScope.(Map<K, T>) -> R
): Goal<R> = DynamicGoal(coroutineContext, this.values) { ): Goal<R> = DynamicGoal(coroutineContext, this.values) {
block(mapValues { it.value.await(this) }) block(mapValues { it.value.await() })
} }

@ -1,9 +1,6 @@
package hep.dataforge.data package hep.dataforge.data
import hep.dataforge.meta.Meta import hep.dataforge.meta.*
import hep.dataforge.meta.MetaBuilder
import hep.dataforge.meta.builder
import hep.dataforge.meta.seal
import hep.dataforge.names.Name import hep.dataforge.names.Name
import kotlin.reflect.KClass import kotlin.reflect.KClass
@ -20,6 +17,7 @@ data class ActionEnv(
/** /**
* Action environment * Action environment
*/ */
@DFBuilder
class MapActionBuilder<T, R>(var name: Name, var meta: MetaBuilder, val actionMeta: Meta) { class MapActionBuilder<T, R>(var name: Name, var meta: MetaBuilder, val actionMeta: Meta) {
lateinit var result: suspend ActionEnv.(T) -> R lateinit var result: suspend ActionEnv.(T) -> R

@ -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 expect fun <R : Any> Data<*>.canCast(type: KClass<out R>): Boolean
fun <R : Any> DataItem<*>.canCast(type: KClass<out R>): Boolean = when (this) { fun <R : Any> DataItem<*>.canCast(type: KClass<out R>): Boolean = when (this) {
is DataItem.Node -> value.canCast(type) is DataItem.Node -> node.canCast(type)
is DataItem.Leaf -> value.canCast(type) is DataItem.Leaf -> data.canCast(type)
} }
/** /**
@ -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 meta: Meta get() = this@cast.meta
override val dependencies: Collection<Goal<*>> get() = this@cast.dependencies override val dependencies: Collection<Goal<*>> get() = this@cast.dependencies
override val result: Deferred<R>? get() = this@cast.result as Deferred<R> 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 fun reset() = this@cast.reset()
override val type: KClass<out R> = type override val type: KClass<out R> = type
} }
@ -52,6 +52,7 @@ inline fun <reified R : Any> Data<*>.cast(): Data<R> = cast(R::class)
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
fun <R : Any> DataNode<*>.cast(type: KClass<out R>): DataNode<R> { fun <R : Any> DataNode<*>.cast(type: KClass<out R>): DataNode<R> {
return object : DataNode<R> { return object : DataNode<R> {
override val meta: Meta get() = this@cast.meta
override val type: KClass<out R> = type override val type: KClass<out R> = type
override val items: Map<NameToken, DataItem<R>> get() = this@cast.items as Map<NameToken, DataItem<R>> override val items: Map<NameToken, DataItem<R>> get() = this@cast.items as Map<NameToken, DataItem<R>>
} }

@ -1,5 +1,6 @@
package hep.dataforge.data package hep.dataforge.data
import hep.dataforge.meta.Meta
import hep.dataforge.names.NameToken import hep.dataforge.names.NameToken
import kotlin.reflect.KClass import kotlin.reflect.KClass
@ -8,16 +9,17 @@ import kotlin.reflect.KClass
* A zero-copy data node wrapper that returns only children with appropriate type. * 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> { 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 { override val items: Map<NameToken, DataItem<T>> by lazy {
origin.items.mapNotNull { (key, item) -> origin.items.mapNotNull { (key, item) ->
when (item) { when (item) {
is DataItem.Leaf -> { is DataItem.Leaf -> {
(item.value.filterIsInstance(type))?.let { (item.data.filterIsInstance(type))?.let {
key to DataItem.Leaf(it) key to DataItem.Leaf(it)
} }
} }
is DataItem.Node -> { is DataItem.Node -> {
key to DataItem.Node(item.value.filterIsInstance(type)) key to DataItem.Node(item.node.filterIsInstance(type))
} }
} }
}.associate { it } }.associate { it }

@ -30,12 +30,10 @@ fun <R : Any> Data<*>.filterIsInstance(type: KClass<out R>): Data<R>? =
* but could contain empty nodes * but could contain empty nodes
*/ */
fun <R : Any> DataNode<*>.filterIsInstance(type: KClass<out R>): DataNode<R> { fun <R : Any> DataNode<*>.filterIsInstance(type: KClass<out R>): DataNode<R> {
return if (canCast(type)) { return when {
cast(type) canCast(type) -> cast(type)
} else if (this is TypeFilteredDataNode) { this is TypeFilteredDataNode -> origin.filterIsInstance(type)
origin.filterIsInstance(type) else -> TypeFilteredDataNode(this, type)
} else {
TypeFilteredDataNode(this, type)
} }
} }
@ -44,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) { fun <R : Any> DataItem<*>?.filterIsInstance(type: KClass<out R>): DataItem<R>? = when (this) {
null -> null null -> null
is DataItem.Node -> DataItem.Node(this.value.filterIsInstance(type)) is DataItem.Node -> DataItem.Node(this.node.filterIsInstance(type))
is DataItem.Leaf -> this.value.filterIsInstance(type)?.let { DataItem.Leaf(it) } 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) inline fun <reified R : Any> DataItem<*>?.filterIsInstance(): DataItem<R>? = this@filterIsInstance.filterIsInstance(R::class)

@ -1,30 +1,24 @@
import scientifik.DependencySourceSet.TEST
import scientifik.serialization
plugins { plugins {
id("scientifik.mpp") id("scientifik.mpp")
} }
description = "IO module" description = "IO module"
scientifik{ serialization(sourceSet = TEST){
withSerialization() cbor()
withIO()
} }
val ioVersion by rootProject.extra("0.2.0-npm-dev-4")
kotlin { kotlin {
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {
api(project(":dataforge-context")) api(project(":dataforge-context"))
} api("org.jetbrains.kotlinx:kotlinx-io:$ioVersion")
}
jvmMain{
dependencies {
}
}
jsMain{
dependencies{
api(npm("text-encoding"))
} }
} }
} }

@ -1,12 +1,16 @@
import scientifik.serialization
plugins { plugins {
id("scientifik.jvm") id("scientifik.jvm")
} }
description = "YAML meta IO" description = "YAML meta IO"
serialization{
yaml()
}
dependencies { dependencies {
api(project(":dataforge-io")) api(project(":dataforge-io"))
api("org.yaml:snakeyaml:1.25") api("org.yaml:snakeyaml:1.26")
testImplementation(kotlin("test"))
testImplementation(kotlin("test-junit"))
} }

@ -5,7 +5,10 @@ import hep.dataforge.io.*
import hep.dataforge.meta.DFExperimental import hep.dataforge.meta.DFExperimental
import hep.dataforge.meta.EmptyMeta import hep.dataforge.meta.EmptyMeta
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import kotlinx.io.core.* import kotlinx.io.*
import kotlinx.io.text.readUtf8Line
import kotlinx.io.text.writeRawString
import kotlinx.io.text.writeUtf8String
import kotlinx.serialization.toUtf8Bytes import kotlinx.serialization.toUtf8Bytes
@DFExperimental @DFExperimental
@ -18,52 +21,61 @@ class FrontMatterEnvelopeFormat(
var line: String = "" var line: String = ""
var offset = 0u var offset = 0u
do { 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() offset += line.toUtf8Bytes().size.toUInt()
} while (!line.startsWith(SEPARATOR)) } while (!line.startsWith(SEPARATOR))
val readMetaFormat = val readMetaFormat =
metaTypeRegex.matchEntire(line)?.groupValues?.first() metaTypeRegex.matchEntire(line)?.groupValues?.first()
?.let { io.metaFormat(it) } ?: YamlMetaFormat.default ?.let { io.metaFormat(it) } ?: YamlMetaFormat
val metaBlock = buildPacket { val meta = buildBytes {
do { do {
line = readUTF8Line() ?: error("Input does not contain closing front matter separator") line = readUtf8Line()
appendln(line) writeUtf8String(line + "\r\n")
offset += line.toUtf8Bytes().size.toUInt() offset += line.toUtf8Bytes().size.toUInt()
} while (!line.startsWith(SEPARATOR)) } while (!line.startsWith(SEPARATOR))
}.read {
readMetaFormat.run {
readMeta()
}
} }
val meta = readMetaFormat.fromBytes(metaBlock)
return PartialEnvelope(meta, offset, null) return PartialEnvelope(meta, offset, null)
} }
override fun Input.readObject(): Envelope { override fun Input.readObject(): Envelope {
var line: String = "" var line: String = ""
do { 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)) } while (!line.startsWith(SEPARATOR))
val readMetaFormat = val readMetaFormat =
metaTypeRegex.matchEntire(line)?.groupValues?.first() metaTypeRegex.matchEntire(line)?.groupValues?.first()
?.let { io.metaFormat(it) } ?: YamlMetaFormat.default ?.let { io.metaFormat(it) } ?: YamlMetaFormat
val metaBlock = buildPacket { val meta = buildBytes {
do { do {
appendln(readUTF8Line() ?: error("Input does not contain closing front matter separator")) writeUtf8String(readUtf8Line() + "\r\n")
} while (!line.startsWith(SEPARATOR)) } while (!line.startsWith(SEPARATOR))
}.read {
readMetaFormat.run {
readMeta()
} }
val meta = readMetaFormat.fromBytes(metaBlock) }
val bytes = readBytes() val bytes = readRemaining()
val data = bytes.asBinary() val data = bytes.asBinary()
return SimpleEnvelope(meta, data) return SimpleEnvelope(meta, data)
} }
override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) { override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) {
val metaFormat = metaFormatFactory(formatMeta, io.context) val metaFormat = metaFormatFactory(formatMeta, io.context)
writeText("$SEPARATOR\r\n") writeRawString("$SEPARATOR\r\n")
metaFormat.run { writeObject(envelope.meta) } metaFormat.run { writeObject(envelope.meta) }
writeText("$SEPARATOR\r\n") writeRawString("$SEPARATOR\r\n")
envelope.data?.read { copyTo(this@writeEnvelope) } //Printing data
envelope.data?.let { data ->
writeBinary(data)
}
} }
companion object : EnvelopeFormatFactory { companion object : EnvelopeFormatFactory {
@ -76,13 +88,24 @@ class FrontMatterEnvelopeFormat(
} }
override fun peekFormat(io: IOPlugin, input: Input): EnvelopeFormat? { override fun peekFormat(io: IOPlugin, input: Input): EnvelopeFormat? {
val line = input.readUTF8Line(3, 30) val line = input.readUtf8Line()
return if (line != null && line.startsWith("---")) { return if (line.startsWith("---")) {
invoke() invoke()
} else { } else {
null null
} }
} }
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() }
} }
} }

@ -1,25 +1,23 @@
package hep.dataforge.io.yaml package hep.dataforge.io.yaml
import hep.dataforge.context.Context import hep.dataforge.context.Context
import hep.dataforge.descriptors.NodeDescriptor
import hep.dataforge.io.MetaFormat import hep.dataforge.io.MetaFormat
import hep.dataforge.io.MetaFormatFactory import hep.dataforge.io.MetaFormatFactory
import hep.dataforge.meta.DFExperimental import hep.dataforge.meta.DFExperimental
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.meta.descriptors.NodeDescriptor
import hep.dataforge.meta.toMap import hep.dataforge.meta.toMap
import hep.dataforge.meta.toMeta import hep.dataforge.meta.toMeta
import hep.dataforge.names.Name import kotlinx.io.Input
import hep.dataforge.names.plus import kotlinx.io.Output
import kotlinx.io.core.Input import kotlinx.io.readUByte
import kotlinx.io.core.Output import kotlinx.io.text.writeUtf8String
import kotlinx.io.core.readUByte
import kotlinx.io.core.writeText
import org.yaml.snakeyaml.Yaml import org.yaml.snakeyaml.Yaml
import java.io.InputStream import java.io.InputStream
private class InputAsStream(val input: Input) : InputStream() { private class InputAsStream(val input: Input) : InputStream() {
override fun read(): Int { override fun read(): Int {
if (input.endOfInput) return -1 if (input.eof()) return -1
return input.readUByte().toInt() return input.readUByte().toInt()
} }
@ -36,7 +34,7 @@ class YamlMetaFormat(val meta: Meta) : MetaFormat {
override fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor?) { override fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor?) {
val string = yaml.dump(meta.toMap(descriptor)) val string = yaml.dump(meta.toMap(descriptor))
writeText(string) writeUtf8String(string)
} }
override fun Input.readMeta(descriptor: NodeDescriptor?): Meta { override fun Input.readMeta(descriptor: NodeDescriptor?): Meta {
@ -45,12 +43,18 @@ class YamlMetaFormat(val meta: Meta) : MetaFormat {
} }
companion object : MetaFormatFactory { companion object : MetaFormatFactory {
val default = YamlMetaFormat()
override fun invoke(meta: Meta, context: Context): MetaFormat = YamlMetaFormat(meta) 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 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) }
} }
} }

@ -3,17 +3,16 @@ package hep.dataforge.io.yaml
import hep.dataforge.io.parse import hep.dataforge.io.parse
import hep.dataforge.io.toString import hep.dataforge.io.toString
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.meta.buildMeta
import hep.dataforge.meta.get import hep.dataforge.meta.get
import hep.dataforge.meta.seal import hep.dataforge.meta.seal
import org.junit.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
class YamlMetaFormatTest { class YamlMetaFormatTest {
@Test @Test
fun testYamlMetaFormat() { fun testYamlMetaFormat() {
val meta = buildMeta { val meta = Meta {
"a" put 22 "a" put 22
"node" put { "node" put {
"b" put "DDD" "b" put "DDD"

@ -1,87 +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
*/
val size: ULong
/**
* 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
}
/**
* 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 {
this.readBytes()
}
@ExperimentalUnsignedTypes
fun RandomAccessBinary.readPacket(from: UInt, size: UInt): ByteReadPacket = read(from, size) {
buildPacket { copyTo(this) }
}
@ExperimentalUnsignedTypes
object EmptyBinary : RandomAccessBinary {
override val size: ULong = 0.toULong()
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())
}

@ -1,18 +1,18 @@
package hep.dataforge.io package hep.dataforge.io
import hep.dataforge.context.Context import hep.dataforge.context.Context
import hep.dataforge.descriptors.NodeDescriptor import hep.dataforge.meta.Meta
import hep.dataforge.meta.* import hep.dataforge.meta.MetaBuilder
import hep.dataforge.names.Name import hep.dataforge.meta.MetaItem
import hep.dataforge.names.plus import hep.dataforge.meta.descriptors.NodeDescriptor
import hep.dataforge.meta.setItem
import hep.dataforge.values.* import hep.dataforge.values.*
import kotlinx.io.core.Input import kotlinx.io.*
import kotlinx.io.core.Output import kotlinx.io.text.readUtf8String
import kotlinx.io.core.readText import kotlinx.io.text.writeUtf8String
import kotlinx.io.core.writeText
object BinaryMetaFormat : MetaFormat, MetaFormatFactory { object BinaryMetaFormat : MetaFormat, MetaFormatFactory {
override val name: Name = super.name + "bin" override val shortName: String = "bin"
override val key: Short = 0x4249//BI override val key: Short = 0x4249//BI
override fun invoke(meta: Meta, context: Context): MetaFormat = this override fun invoke(meta: Meta, context: Context): MetaFormat = this
@ -25,7 +25,7 @@ object BinaryMetaFormat : MetaFormat, MetaFormatFactory {
private fun Output.writeString(str: String) { private fun Output.writeString(str: String) {
writeInt(str.length) writeInt(str.length)
writeText(str) writeUtf8String(str)
} }
fun Output.writeValue(value: Value) { fun Output.writeValue(value: Value) {
@ -93,7 +93,7 @@ object BinaryMetaFormat : MetaFormat, MetaFormatFactory {
private fun Input.readString(): String { private fun Input.readString(): String {
val length = readInt() val length = readInt()
return readText(max = length) return readUtf8String(length)
} }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
@ -115,7 +115,7 @@ object BinaryMetaFormat : MetaFormat, MetaFormatFactory {
} }
'M' -> { 'M' -> {
val length = readInt() val length = readInt()
val meta = buildMeta { val meta = Meta {
(1..length).forEach { _ -> (1..length).forEach { _ ->
val name = readString() val name = readString()
val item = readMetaItem() val item = readMetaItem()

@ -1,11 +1,12 @@
package hep.dataforge.io 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.asName
import hep.dataforge.names.plus import hep.dataforge.names.plus
import kotlinx.io.core.Output import kotlinx.io.Binary
import kotlinx.io.core.buildPacket
import kotlinx.io.core.readBytes
interface Envelope { interface Envelope {
val meta: Meta val meta: Meta
@ -21,12 +22,13 @@ interface Envelope {
val ENVELOPE_DATA_TYPE_KEY = ENVELOPE_NODE_KEY + "dataType" val ENVELOPE_DATA_TYPE_KEY = ENVELOPE_NODE_KEY + "dataType"
val ENVELOPE_DATA_ID_KEY = ENVELOPE_NODE_KEY + "dataID" val ENVELOPE_DATA_ID_KEY = ENVELOPE_NODE_KEY + "dataID"
val ENVELOPE_DESCRIPTION_KEY = ENVELOPE_NODE_KEY + "description" val ENVELOPE_DESCRIPTION_KEY = ENVELOPE_NODE_KEY + "description"
val ENVELOPE_NAME_KEY = ENVELOPE_NODE_KEY + "name"
//const val ENVELOPE_TIME_KEY = "@envelope.time" //const val ENVELOPE_TIME_KEY = "@envelope.time"
/** /**
* Build a static envelope using provided builder * 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()
} }
} }
@ -82,33 +84,3 @@ fun Envelope.withMetaLayers(vararg layers: Meta): Envelope {
else -> ProxyEnvelope(this, *layers) else -> ProxyEnvelope(this, *layers)
} }
} }
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)
}

@ -0,0 +1,52 @@
package hep.dataforge.io
import hep.dataforge.meta.*
import kotlinx.io.ArrayBinary
import kotlinx.io.Binary
import kotlinx.io.ExperimentalIoApi
import kotlinx.io.Output
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)
}
/**
* 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)
/**
* Construct a data binary from given builder
*/
@ExperimentalIoApi
fun data(block: Output.() -> Unit) {
data = ArrayBinary.write(builder = block)
}
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)
//}

@ -7,8 +7,8 @@ import hep.dataforge.meta.Meta
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.asName import hep.dataforge.names.asName
import hep.dataforge.provider.Type import hep.dataforge.provider.Type
import kotlinx.io.core.Input import kotlinx.io.Input
import kotlinx.io.core.Output import kotlinx.io.Output
import kotlin.reflect.KClass import kotlin.reflect.KClass
/** /**
@ -23,15 +23,19 @@ interface EnvelopeFormat : IOFormat<Envelope> {
fun Input.readPartial(): PartialEnvelope 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 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) @Type(ENVELOPE_FORMAT_TYPE)
interface EnvelopeFormatFactory : IOFormatFactory<Envelope> { interface EnvelopeFormatFactory : IOFormatFactory<Envelope>, EnvelopeFormat {
override val name: Name get() = "envelope".asName() override val name: Name get() = "envelope".asName()
override val type: KClass<out Envelope> get() = Envelope::class override val type: KClass<out Envelope> get() = Envelope::class

@ -0,0 +1,125 @@
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_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()
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"
const val MULTIPART_DATA_SEPARATOR = "\r\n#~PART~#\r\n"
const val MULTIPART_DATA_TYPE = "envelope.multipart"
}
/**
* Append multiple serialized envelopes to the data block. Previous data is erased if it was present
*/
@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(formatMeta).run {
envelopes.forEach {
writeRawString(MULTIPART_DATA_SEPARATOR)
writeEnvelope(it)
}
}
}
}
/**
* Create a multipart partition in the envelope adding additional name-index mapping in meta
*/
@DFExperimental
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) ->
writeRawString(MULTIPART_DATA_SEPARATOR)
writeEnvelope(envelope)
meta {
append(INDEX_KEY, Meta {
"key" put key
"index" put counter
})
}
counter++
}
}
}
}
@DFExperimental
fun EnvelopeBuilder.multipart(
formatFactory: EnvelopeFormatFactory,
formatMeta: Meta = EmptyMeta,
builder: suspend SequenceScope<Envelope>.() -> Unit
) = 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 -> {
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) {
val separator = readRawString(MULTIPART_DATA_SEPARATOR.length)
if(separator!= MULTIPART_DATA_SEPARATOR) error("Separator is expected, but $separator found")
yield(readObject())
}
}
} ?: emptySequence()
}
}
else -> null
}
}

@ -10,13 +10,9 @@ import hep.dataforge.names.Name
import hep.dataforge.names.asName import hep.dataforge.names.asName
import hep.dataforge.provider.Type import hep.dataforge.provider.Type
import hep.dataforge.values.Value import hep.dataforge.values.Value
import kotlinx.io.core.* import kotlinx.io.*
import kotlinx.io.buffer.Buffer
import kotlinx.io.pool.ObjectPool import kotlinx.io.pool.ObjectPool
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 import kotlin.reflect.KClass
/** /**
@ -50,7 +46,7 @@ class ListIOFormat<T : Any>(val format: IOFormat<T>) : IOFormat<List<T>> {
val <T : Any> IOFormat<T>.list get() = ListIOFormat(this) 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() val buffer = borrow()
return try { return try {
buffer.apply(block) buffer.apply(block)
@ -72,41 +68,11 @@ interface IOFormatFactory<T : Any> : Factory<IOFormat<T>>, Named {
} }
} }
@Deprecated("To be removed in io-2") fun <T : Any> IOFormat<T>.writeBytes(obj: T): Bytes = buildBytes { writeObject(obj) }
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>.writePacket(obj: T): ByteReadPacket = buildPacket { writeObject(obj) }
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? { fun <T : Any> IOFormat<T>.writeByteArray(obj: T): ByteArray = buildBytes { writeObject(obj) }.toByteArray()
if (array.size - written <= 0) return null fun <T : Any> IOFormat<T>.readByteArray(array: ByteArray): T = array.asBinary().read { readObject() }
return IoBuffer.Pool.fill {
reserveEndGap(IoBuffer.ReservedSize)
val toWrite = min(capacity, array.size - written)
writeFully(array, written, toWrite)
written += toWrite
}
}
}
return byteArrayInput.readObject()
}
object DoubleIOFormat : IOFormat<Double>, IOFormatFactory<Double> { object DoubleIOFormat : IOFormat<Double>, IOFormatFactory<Double> {
override fun invoke(meta: Meta, context: Context): IOFormat<Double> = this override fun invoke(meta: Meta, context: Context): IOFormat<Double> = this
@ -140,25 +106,10 @@ object ValueIOFormat : IOFormat<Value>, IOFormatFactory<Value> {
} }
/** /**
* Experimental * Read given binary as object using given format
*/ */
@ImplicitReflectionSerializer fun <T : Any> Binary.readWith(format: IOFormat<T>): T = format.run {
class SerializerIOFormat<T : Any>( read {
type: KClass<T>, readObject()
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)
} }
} }

@ -20,12 +20,15 @@ class IOPlugin(meta: Meta) : AbstractPlugin(meta) {
metaFormatFactories.find { it.key == key }?.invoke(meta) metaFormatFactories.find { it.key == key }?.invoke(meta)
fun metaFormat(name: String, meta: Meta = EmptyMeta): MetaFormat? = fun metaFormat(name: String, meta: Meta = EmptyMeta): MetaFormat? =
metaFormatFactories.find { it.name.toString() == name }?.invoke(meta) metaFormatFactories.find { it.shortName == name }?.invoke(meta)
val envelopeFormatFactories by lazy { val envelopeFormatFactories by lazy {
context.content<EnvelopeFormatFactory>(ENVELOPE_FORMAT_TYPE).values 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> { override fun provideTop(target: String): Map<Name, Any> {
return when (target) { return when (target) {
META_FORMAT_TYPE -> defaultMetaFormats.toMap() META_FORMAT_TYPE -> defaultMetaFormats.toMap()
@ -49,7 +52,7 @@ class IOPlugin(meta: Meta) : AbstractPlugin(meta) {
companion object : PluginFactory<IOPlugin> { companion object : PluginFactory<IOPlugin> {
val defaultMetaFormats: List<MetaFormatFactory> = listOf(JsonMetaFormat, BinaryMetaFormat) 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) override val tag: PluginTag = PluginTag("io", group = PluginTag.DATAFORGE_GROUP)

@ -2,171 +2,50 @@
package hep.dataforge.io package hep.dataforge.io
import hep.dataforge.context.Context import hep.dataforge.context.Context
import hep.dataforge.descriptors.ItemDescriptor
import hep.dataforge.descriptors.NodeDescriptor
import hep.dataforge.descriptors.ValueDescriptor
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaBase import hep.dataforge.meta.descriptors.NodeDescriptor
import hep.dataforge.meta.MetaItem import hep.dataforge.meta.node
import hep.dataforge.names.Name import hep.dataforge.meta.toJson
import hep.dataforge.names.NameToken import hep.dataforge.meta.toMetaItem
import hep.dataforge.names.plus import kotlinx.io.Input
import hep.dataforge.names.toName import kotlinx.io.Output
import hep.dataforge.values.* import kotlinx.io.text.readUtf8String
import kotlinx.io.core.Input import kotlinx.io.text.writeUtf8String
import kotlinx.io.core.Output import kotlinx.serialization.UnstableDefault
import kotlinx.io.core.readText import kotlinx.serialization.json.Json
import kotlinx.io.core.writeText import kotlinx.serialization.json.JsonObjectSerializer
import kotlinx.serialization.json.*
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.set
@OptIn(UnstableDefault::class)
class JsonMetaFormat(private val json: Json = Json.indented) : MetaFormat { class JsonMetaFormat(private val json: Json = DEFAULT_JSON) : MetaFormat {
override fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor?) { override fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor?) {
val jsonObject = meta.toJson(descriptor) val jsonObject = meta.toJson(descriptor)
writeText(json.stringify(JsonObjectSerializer, jsonObject)) writeUtf8String(json.stringify(JsonObjectSerializer, jsonObject))
} }
override fun Input.readMeta(descriptor: NodeDescriptor?): Meta { override fun Input.readMeta(descriptor: NodeDescriptor?): Meta {
val str = readText() val str = readUtf8String()
val jsonElement = json.parseJson(str) val jsonElement = json.parseJson(str)
return jsonElement.toMeta() val item = jsonElement.toMetaItem(descriptor)
return item.node ?: Meta.EMPTY
} }
companion object : MetaFormatFactory { companion object : MetaFormatFactory {
val default = JsonMetaFormat() val DEFAULT_JSON = Json { prettyPrint = true }
override fun invoke(meta: Meta, context: Context): MetaFormat = default 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" override val key: Short = 0x4a53//"JS"
}
}
/** private val default = JsonMetaFormat()
* @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 override fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor?) =
private fun NameToken.toJsonKey(descriptor: ItemDescriptor?) = toString() default.run { writeMeta(meta, descriptor) }
private fun NodeDescriptor?.getDescriptor(key: String) = this?.items?.get(key) override fun Input.readMeta(descriptor: NodeDescriptor?): Meta =
default.run { readMeta(descriptor) }
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.getDescriptor(key)
//use name from descriptor in case descriptor name differs from json key
val name = itemDescriptor?.name ?: key
return when (value) {
is JsonPrimitive -> {
this[name] = MetaItem.ValueItem(value.toValue(itemDescriptor as? ValueDescriptor)) as MetaItem<JsonMeta>
}
is JsonObject -> {
this[name] = 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[name] = MetaItem.ValueItem(listValue) as MetaItem<JsonMeta>
}
else -> value.forEachIndexed { index, jsonElement ->
this["$name[$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()!! }
} }
} }

@ -1,13 +1,14 @@
package hep.dataforge.io package hep.dataforge.io
import hep.dataforge.context.Context 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.io.MetaFormatFactory.Companion.META_FORMAT_TYPE
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.asName import hep.dataforge.names.asName
import hep.dataforge.names.plus
import hep.dataforge.provider.Type import hep.dataforge.provider.Type
import kotlinx.io.core.* import kotlinx.io.*
import kotlin.reflect.KClass import kotlin.reflect.KClass
/** /**
@ -27,12 +28,14 @@ interface MetaFormat : IOFormat<Meta> {
} }
@Type(META_FORMAT_TYPE) @Type(META_FORMAT_TYPE)
interface MetaFormatFactory : IOFormatFactory<Meta> { 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 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 override operator fun invoke(meta: Meta, context: Context): MetaFormat
@ -41,24 +44,16 @@ interface MetaFormatFactory : IOFormatFactory<Meta> {
} }
} }
fun Meta.toString(format: MetaFormat): String = buildPacket { fun Meta.toString(format: MetaFormat): String = buildBytes {
format.run { writeObject(this@toString) } format.run { writeObject(this@toString) }
}.readText() }.toByteArray().decodeToString()
fun Meta.toString(formatFactory: MetaFormatFactory): String = toString(formatFactory()) fun Meta.toString(formatFactory: MetaFormatFactory): String = toString(formatFactory())
fun Meta.toBytes(format: MetaFormat = JsonMetaFormat.default): ByteReadPacket = buildPacket {
format.run { writeObject(this@toBytes) }
}
fun MetaFormat.parse(str: String): Meta { 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 MetaFormatFactory.parse(str: String, formatMeta: Meta): Meta = invoke(formatMeta).parse(str)
fun MetaFormat.fromBytes(packet: ByteReadPacket): Meta {
return packet.readObject()
}

@ -7,43 +7,51 @@ import hep.dataforge.meta.string
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.plus import hep.dataforge.names.plus
import hep.dataforge.names.toName import hep.dataforge.names.toName
import kotlinx.io.charsets.Charsets import kotlinx.io.*
import kotlinx.io.core.* import kotlinx.io.text.readRawString
import kotlinx.io.text.writeRawString
@ExperimentalUnsignedTypes @ExperimentalIoApi
class TaggedEnvelopeFormat( class TaggedEnvelopeFormat(
val io: IOPlugin, val io: IOPlugin,
val version: VERSION = TaggedEnvelopeFormat.VERSION.DF02 val version: VERSION = VERSION.DF02
) : EnvelopeFormat { ) : EnvelopeFormat {
// private val metaFormat = io.metaFormat(metaFormatKey) // private val metaFormat = io.metaFormat(metaFormatKey)
// ?: error("Meta format with key $metaFormatKey could not be resolved in $io") // ?: error("Meta format with key $metaFormatKey could not be resolved in $io")
private fun Tag.toBytes(): ByteReadPacket = buildPacket(24) { private fun Tag.toBytes() = buildBytes(24) {
writeText(START_SEQUENCE) writeRawString(START_SEQUENCE)
writeText(version.name) writeRawString(version.name)
writeShort(metaFormatKey) writeShort(metaFormatKey)
writeUInt(metaSize) writeUInt(metaSize)
when (version) { when (version) {
TaggedEnvelopeFormat.VERSION.DF02 -> { VERSION.DF02 -> {
writeUInt(dataSize.toUInt()) writeUInt(dataSize.toUInt())
} }
TaggedEnvelopeFormat.VERSION.DF03 -> { VERSION.DF03 -> {
writeULong(dataSize) writeULong(dataSize)
} }
} }
writeText(END_SEQUENCE) writeRawString(END_SEQUENCE)
} }
override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) { override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) {
val metaFormat = metaFormatFactory.invoke(formatMeta, io.context) val metaFormat = metaFormatFactory.invoke(formatMeta, io.context)
val metaBytes = metaFormat.writeBytes(envelope.meta) 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) {
writePacket(tag.toBytes()) 0
writeFully(metaBytes) } else {
writeText("\r\n") envelope.data?.size ?: Binary.INFINITE
envelope.data?.read { copyTo(this@writeEnvelope) } }.toULong()
val tag = Tag(metaFormatFactory.key, metaBytes.size.toUInt() + 2u, actualSize)
writeBinary(tag.toBytes())
writeBinary(metaBytes)
writeRawString("\r\n")
envelope.data?.let {
writeBinary(it)
}
flush() flush()
} }
@ -59,11 +67,15 @@ class TaggedEnvelopeFormat(
val metaFormat = io.metaFormat(tag.metaFormatKey) val metaFormat = io.metaFormat(tag.metaFormatKey)
?: error("Meta format with key ${tag.metaFormatKey} not found") ?: error("Meta format with key ${tag.metaFormatKey} not found")
val metaPacket = ByteReadPacket(readBytes(tag.metaSize.toInt())) val meta: Meta = limit(tag.metaSize.toInt()).run {
val dataBytes = readBytes(tag.dataSize.toInt()) metaFormat.run {
readObject()
}
}
val meta = metaFormat.run { metaPacket.readObject() } val data = ByteArray(tag.dataSize.toInt()).also { readArray(it) }.asBinary()
return SimpleEnvelope(meta, ArrayBinary(dataBytes))
return SimpleEnvelope(meta, data)
} }
override fun Input.readPartial(): PartialEnvelope { override fun Input.readPartial(): PartialEnvelope {
@ -72,8 +84,11 @@ class TaggedEnvelopeFormat(
val metaFormat = io.metaFormat(tag.metaFormatKey) val metaFormat = io.metaFormat(tag.metaFormatKey)
?: error("Meta format with key ${tag.metaFormatKey} not found") ?: error("Meta format with key ${tag.metaFormatKey} not found")
val metaPacket = ByteReadPacket(readBytes(tag.metaSize.toInt())) val meta: Meta = limit(tag.metaSize.toInt()).run {
val meta = metaFormat.run { metaPacket.readObject() } metaFormat.run {
readObject()
}
}
return PartialEnvelope(meta, version.tagSize + tag.metaSize, tag.dataSize) return PartialEnvelope(meta, version.tagSize + tag.metaSize, tag.dataSize)
} }
@ -99,16 +114,16 @@ class TaggedEnvelopeFormat(
val io = context.io val io = context.io
val metaFormatName = meta["name"].string?.toName() ?: JsonMetaFormat.name val metaFormatName = meta["name"].string?.toName() ?: JsonMetaFormat.name
val metaFormatFactory = io.metaFormatFactories.find { it.name == metaFormatName } //Check if appropriate factory exists
?: error("Meta format could not be resolved") io.metaFormatFactories.find { it.name == metaFormatName } ?: error("Meta format could not be resolved")
return TaggedEnvelopeFormat(io) return TaggedEnvelopeFormat(io)
} }
private fun Input.readTag(version: VERSION): Tag { 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") 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") if (version.name != versionString) error("Wrong version of DataForge: expected $version but found $versionString")
val metaFormatKey = readShort() val metaFormatKey = readShort()
val metaLength = readUInt() val metaLength = readUInt()
@ -116,14 +131,14 @@ class TaggedEnvelopeFormat(
VERSION.DF02 -> readUInt().toULong() VERSION.DF02 -> readUInt().toULong()
VERSION.DF03 -> readULong() 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") if (end != END_SEQUENCE) error("The input is not an envelope")
return Tag(metaFormatKey, metaLength, dataLength) return Tag(metaFormatKey, metaLength, dataLength)
} }
override fun peekFormat(io: IOPlugin, input: Input): EnvelopeFormat? { override fun peekFormat(io: IOPlugin, input: Input): EnvelopeFormat? {
return try { return try {
val header = input.readTextExactBytes(6) val header = input.readRawString(6)
when (header.substring(2..5)) { when (header.substring(2..5)) {
VERSION.DF02.name -> TaggedEnvelopeFormat(io, VERSION.DF02) VERSION.DF02.name -> TaggedEnvelopeFormat(io, VERSION.DF02)
VERSION.DF03.name -> TaggedEnvelopeFormat(io, VERSION.DF03) VERSION.DF03.name -> TaggedEnvelopeFormat(io, VERSION.DF03)
@ -134,7 +149,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() }
} }
} }

@ -3,9 +3,13 @@ package hep.dataforge.io
import hep.dataforge.context.Context import hep.dataforge.context.Context
import hep.dataforge.meta.* import hep.dataforge.meta.*
import hep.dataforge.names.asName import hep.dataforge.names.asName
import kotlinx.io.core.* import kotlinx.io.*
import kotlinx.serialization.toUtf8Bytes import kotlinx.io.text.readRawString
import kotlinx.io.text.readUtf8Line
import kotlinx.io.text.writeRawString
import kotlinx.io.text.writeUtf8String
@ExperimentalIoApi
class TaglessEnvelopeFormat( class TaglessEnvelopeFormat(
val io: IOPlugin, val io: IOPlugin,
meta: Meta = EmptyMeta meta: Meta = EmptyMeta
@ -15,40 +19,46 @@ class TaglessEnvelopeFormat(
private val dataStart = meta[DATA_START_PROPERTY].string ?: DEFAULT_DATA_START private val dataStart = meta[DATA_START_PROPERTY].string ?: DEFAULT_DATA_START
private fun Output.writeProperty(key: String, value: Any) { 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) { override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) {
val metaFormat = metaFormatFactory(formatMeta, io.context) val metaFormat = metaFormatFactory(formatMeta, io.context)
//printing header //printing header
writeText(TAGLESS_ENVELOPE_HEADER + "\r\n") writeRawString(TAGLESS_ENVELOPE_HEADER + "\r\n")
//printing all properties //printing all properties
writeProperty(META_TYPE_PROPERTY, metaFormatFactory.type) writeProperty(META_TYPE_PROPERTY, metaFormatFactory.shortName)
//TODO add optional metaFormat properties //TODO add optional metaFormat properties
writeProperty(DATA_LENGTH_PROPERTY, envelope.data?.size ?: 0) val actualSize: Int = if (envelope.data == null) {
0
} else {
envelope.data?.size ?: Binary.INFINITE
}
writeProperty(DATA_LENGTH_PROPERTY, actualSize)
//Printing meta //Printing meta
if (!envelope.meta.isEmpty()) { if (!envelope.meta.isEmpty()) {
val metaBytes = metaFormat.writeBytes(envelope.meta) val metaBytes = metaFormat.writeBytes(envelope.meta)
writeProperty(META_LENGTH_PROPERTY, metaBytes.size) writeProperty(META_LENGTH_PROPERTY, metaBytes.size + 2)
writeText(metaStart + "\r\n") writeUtf8String(metaStart + "\r\n")
writeFully(metaBytes) writeBinary(metaBytes)
writeText("\r\n") writeRawString("\r\n")
} }
//Printing data //Printing data
envelope.data?.let { data -> envelope.data?.let { data ->
writeText(dataStart + "\r\n") writeUtf8String(dataStart + "\r\n")
writeFully(data.toBytes()) writeBinary(data)
} }
} }
override fun Input.readObject(): Envelope { override fun Input.readObject(): Envelope {
var line: String = "" var line: String
do { 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)) } while (!line.startsWith(TAGLESS_ENVELOPE_HEADER))
val properties = HashMap<String, String>() val properties = HashMap<String, String>()
@ -60,19 +70,20 @@ class TaglessEnvelopeFormat(
val (key, value) = match.destructured val (key, value) = match.destructured
properties[key] = value properties[key] = value
} }
line = readUTF8Line() ?: 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 var meta: Meta = EmptyMeta
if (line.startsWith(metaStart)) { 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() val metaSize = properties[META_LENGTH_PROPERTY]?.toInt()
meta = if (metaSize != null) { meta = if (metaSize != null) {
val metaPacket = buildPacket { limit(metaSize).run {
writeFully(readBytes(metaSize)) metaFormat.run { readObject() }
} }
metaFormat.run { metaPacket.readObject() }
} else { } else {
metaFormat.run { metaFormat.run {
readObject() readObject()
@ -81,17 +92,22 @@ class TaglessEnvelopeFormat(
} }
do { do {
line = readUTF8Line() ?: return SimpleEnvelope(meta, null) try {
line = readUtf8Line()
} catch (ex: EOFException) {
//returning an Envelope without data if end of input is reached //returning an Envelope without data if end of input is reached
return SimpleEnvelope(meta, null)
}
} while (!line.startsWith(dataStart)) } while (!line.startsWith(dataStart))
val data: Binary? = if (properties.containsKey(DATA_LENGTH_PROPERTY)) { val data: Binary? = if (properties.containsKey(DATA_LENGTH_PROPERTY)) {
val bytes = ByteArray(properties[DATA_LENGTH_PROPERTY]!!.toInt()) val bytes = ByteArray(properties[DATA_LENGTH_PROPERTY]!!.toInt())
readFully(bytes) readArray(bytes)
bytes.asBinary() bytes.asBinary()
} else { } else {
val bytes = readBytes() ArrayBinary.write {
bytes.asBinary() writeInput(this@readObject)
}
} }
return SimpleEnvelope(meta, data) return SimpleEnvelope(meta, data)
@ -99,10 +115,10 @@ class TaglessEnvelopeFormat(
override fun Input.readPartial(): PartialEnvelope { override fun Input.readPartial(): PartialEnvelope {
var offset = 0u var offset = 0u
var line: String = "" var line: String
do { 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() offset += line.encodeToByteArray().size.toUInt()
} while (!line.startsWith(TAGLESS_ENVELOPE_HEADER)) } while (!line.startsWith(TAGLESS_ENVELOPE_HEADER))
val properties = HashMap<String, String>() val properties = HashMap<String, String>()
@ -114,30 +130,32 @@ class TaglessEnvelopeFormat(
val (key, value) = match.destructured val (key, value) = match.destructured
properties[key] = value properties[key] = value
} }
line = readUTF8Line() ?: return PartialEnvelope(Meta.empty, offset.toUInt(), 0.toULong()) try {
offset += line.toUtf8Bytes().size.toUInt() line = readUtf8Line()
offset += line.encodeToByteArray().size.toUInt()
} catch (ex: EOFException) {
return PartialEnvelope(Meta.EMPTY, offset.toUInt(), 0.toULong())
}
} }
var meta: Meta = EmptyMeta var meta: Meta = EmptyMeta
if (line.startsWith(metaStart)) { 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[META_LENGTH_PROPERTY]?.toInt()
val metaSize = properties.get(META_LENGTH_PROPERTY)?.toInt()
meta = if (metaSize != null) { meta = if (metaSize != null) {
val metaPacket = buildPacket {
writeFully(readBytes(metaSize))
}
offset += metaSize.toUInt() offset += metaSize.toUInt()
metaFormat.run { metaPacket.readObject() } limit(metaSize).run {
metaFormat.run { readObject() }
}
} else { } else {
error("Can't partially read an envelope with undefined meta size") error("Can't partially read an envelope with undefined meta size")
} }
} }
do { 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() offset += line.encodeToByteArray().size.toUInt()
//returning an Envelope without data if end of input is reached //returning an Envelope without data if end of input is reached
} while (!line.startsWith(dataStart)) } while (!line.startsWith(dataStart))
@ -170,13 +188,21 @@ class TaglessEnvelopeFormat(
return TaglessEnvelopeFormat(context.io, meta) 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? { override fun peekFormat(io: IOPlugin, input: Input): EnvelopeFormat? {
return try { return try {
val buffer = ByteArray(TAGLESS_ENVELOPE_HEADER.length) val string = input.readRawString(TAGLESS_ENVELOPE_HEADER.length)
input.readFully(buffer) return if (string == TAGLESS_ENVELOPE_HEADER) {
return if (buffer.toString() == TAGLESS_ENVELOPE_HEADER) {
TaglessEnvelopeFormat(io) TaglessEnvelopeFormat(io)
} else { } else {
null null

@ -6,6 +6,7 @@ import hep.dataforge.io.*
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.meta.get import hep.dataforge.meta.get
import hep.dataforge.meta.int import hep.dataforge.meta.int
import hep.dataforge.meta.scheme.int
import kotlin.reflect.KClass import kotlin.reflect.KClass
class RemoteFunctionClient(override val context: Context, val responder: Responder) : FunctionServer, ContextAware { class RemoteFunctionClient(override val context: Context, val responder: Responder) : FunctionServer, ContextAware {

@ -8,6 +8,7 @@ import hep.dataforge.io.Responder
import hep.dataforge.io.type import hep.dataforge.io.type
import hep.dataforge.meta.get import hep.dataforge.meta.get
import hep.dataforge.meta.int import hep.dataforge.meta.int
import hep.dataforge.meta.scheme.int
class RemoteFunctionServer( class RemoteFunctionServer(
override val context: Context, override val context: Context,

@ -1,141 +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)
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") {
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).toConfig()
}
override fun serialize(encoder: Encoder, obj: Config) {
MetaSerializer.serialize(encoder, obj)
}
}

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

@ -1,80 +0,0 @@
package hep.dataforge.io.serialization
import kotlinx.serialization.CompositeDecoder
import kotlinx.serialization.Decoder
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialDescriptor
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)
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()
fun Decoder.decodeStructure(
desc: SerialDescriptor,
vararg typeParams: KSerializer<*> = emptyArray(),
block: CompositeDecoder.() -> Unit
) {
beginStructure(desc, *typeParams).apply(block).endStructure(desc)
}

@ -1,5 +1,7 @@
package hep.dataforge.io package hep.dataforge.io
import kotlinx.io.readDouble
import kotlinx.io.writeDouble
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -12,16 +14,18 @@ class EnvelopeFormatTest {
} }
data{ data{
writeDouble(22.2) writeDouble(22.2)
// repeat(2000){
// writeInt(it)
// }
} }
} }
@ExperimentalStdlibApi
@Test @Test
fun testTaggedFormat(){ fun testTaggedFormat(){
TaggedEnvelopeFormat.default.run { TaggedEnvelopeFormat.run {
val bytes = writeBytes(envelope) val byteArray = this.writeByteArray(envelope)
println(bytes.decodeToString()) //println(byteArray.decodeToString())
val res = readBytes(bytes) val res = readByteArray(byteArray)
assertEquals(envelope.meta,res.meta) assertEquals(envelope.meta,res.meta)
val double = res.data?.read { val double = res.data?.read {
readDouble() readDouble()
@ -32,10 +36,10 @@ class EnvelopeFormatTest {
@Test @Test
fun testTaglessFormat(){ fun testTaglessFormat(){
TaglessEnvelopeFormat.default.run { TaglessEnvelopeFormat.run {
val bytes = writeBytes(envelope) val byteArray = writeByteArray(envelope)
println(bytes.decodeToString()) //println(byteArray.decodeToString())
val res = readBytes(bytes) val res = readByteArray(byteArray)
assertEquals(envelope.meta,res.meta) assertEquals(envelope.meta,res.meta)
val double = res.data?.read { val double = res.data?.read {
readDouble() readDouble()

@ -1,16 +1,26 @@
package hep.dataforge.io package hep.dataforge.io
import hep.dataforge.meta.* import hep.dataforge.meta.*
import kotlinx.io.Bytes
import kotlinx.io.buildBytes
import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.json import kotlinx.serialization.json.json
import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonArray
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals 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 { class MetaFormatTest {
@Test @Test
fun testBinaryMetaFormat() { fun testBinaryMetaFormat() {
val meta = buildMeta { val meta = Meta {
"a" put 22 "a" put 22
"node" put { "node" put {
"b" put "DDD" "b" put "DDD"
@ -25,7 +35,7 @@ class MetaFormatTest {
@Test @Test
fun testJsonMetaFormat() { fun testJsonMetaFormat() {
val meta = buildMeta { val meta = Meta {
"a" put 22 "a" put 22
"node" put { "node" put {
"b" put "DDD" "b" put "DDD"

@ -1,21 +1,17 @@
package hep.dataforge.io package hep.dataforge.io
import hep.dataforge.io.serialization.MetaItemSerializer import hep.dataforge.meta.Meta
import hep.dataforge.io.serialization.MetaSerializer import hep.dataforge.meta.MetaItem
import hep.dataforge.io.serialization.NameSerializer import hep.dataforge.meta.MetaSerializer
import hep.dataforge.meta.buildMeta import hep.dataforge.names.Name
import hep.dataforge.names.toName import hep.dataforge.names.toName
import kotlinx.io.charsets.Charsets
import kotlinx.io.core.String
import kotlinx.serialization.cbor.Cbor import kotlinx.serialization.cbor.Cbor
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
class MetaSerializerTest { class MetaSerializerTest {
@Test val meta = Meta {
fun testMetaSerialization() {
val meta = buildMeta {
"a" put 22 "a" put 22
"node" put { "node" put {
"b" put "DDD" "b" put "DDD"
@ -24,6 +20,8 @@ class MetaSerializerTest {
} }
} }
@Test
fun testMetaSerialization() {
val string = Json.indented.stringify(MetaSerializer, meta) val string = Json.indented.stringify(MetaSerializer, meta)
val restored = Json.plain.parse(MetaSerializer, string) val restored = Json.plain.parse(MetaSerializer, string)
assertEquals(restored, meta) assertEquals(restored, meta)
@ -31,17 +29,8 @@ class MetaSerializerTest {
@Test @Test
fun testCborSerialization() { fun testCborSerialization() {
val meta = buildMeta {
"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) val bytes = Cbor.dump(MetaSerializer, meta)
println(String(bytes, charset = Charsets.ISO_8859_1)) println(bytes.contentToString())
val restored = Cbor.load(MetaSerializer, bytes) val restored = Cbor.load(MetaSerializer, bytes)
assertEquals(restored, meta) assertEquals(restored, meta)
} }
@ -49,13 +38,13 @@ class MetaSerializerTest {
@Test @Test
fun testNameSerialization() { fun testNameSerialization() {
val name = "a.b.c".toName() val name = "a.b.c".toName()
val string = Json.indented.stringify(NameSerializer, name) val string = Json.indented.stringify(Name.serializer(), name)
val restored = Json.plain.parse(NameSerializer, string) val restored = Json.plain.parse(Name.serializer(), string)
assertEquals(restored, name) assertEquals(restored, name)
} }
@Test @Test
fun testMetaItemDescriptor() { fun testMetaItemDescriptor() {
val descriptor = MetaItemSerializer.descriptor.getElementDescriptor(0) val descriptor = MetaItem.serializer(MetaSerializer).descriptor.getElementDescriptor(0)
} }
} }

@ -0,0 +1,48 @@
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
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
@DFExperimental
class MultipartTest {
val envelopes = (0..5).map {
Envelope {
meta {
"value" put it
}
data {
writeUtf8String("Hello World $it")
repeat(300) {
writeRawString("$it ")
}
}
}
}
val partsEnvelope = Envelope {
multipart(envelopes, TaggedEnvelopeFormat)
}
@Test
fun testParts() {
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)
}
}
}

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

@ -1,52 +1,22 @@
package hep.dataforge.io package hep.dataforge.io
import hep.dataforge.meta.EmptyMeta
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import kotlinx.io.nio.asInput import kotlinx.io.Binary
import kotlinx.io.nio.asOutput import kotlinx.io.ExperimentalIoApi
import java.nio.file.Files import kotlinx.io.FileBinary
import kotlinx.io.read
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.StandardOpenOption
@ExperimentalIoApi
class FileEnvelope internal constructor(val path: Path, val format: EnvelopeFormat) : Envelope { class FileEnvelope internal constructor(val path: Path, val format: EnvelopeFormat) : Envelope {
//TODO do not like this constructor. Hope to replace it later //TODO do not like this constructor. Hope to replace it later
private val partialEnvelope: PartialEnvelope private val partialEnvelope: PartialEnvelope = path.read {
format.run { readPartial() }
init {
val input = Files.newByteChannel(path, StandardOpenOption.READ).asInput()
partialEnvelope = format.run { input.use { it.readPartial()} }
} }
override val meta: Meta get() = partialEnvelope.meta 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())
}
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)
}
} }

@ -0,0 +1,193 @@
package hep.dataforge.io
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
import java.nio.file.Path
import kotlin.reflect.full.isSuperclassOf
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>?
}
/**
* 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 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 {
actualPath.read{
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,
meta: Meta,
metaFormat: MetaFormatFactory = JsonMetaFormat,
descriptor: NodeDescriptor? = null
) {
val actualPath = if (Files.isDirectory(path)) {
path.resolve("@" + metaFormat.name.toString())
} else {
path
}
metaFormat.run {
actualPath.write{
writeMeta(meta, descriptor)
}
}
}
/**
* 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(path: Path): EnvelopeFormat? {
val binary = path.asBinary()
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")
}
}
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.
*
* 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.readEnvelopeFile(
path: Path,
readNonEnvelopes: Boolean = false,
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(IOPlugin.META_FILE_NAME) }
val meta = if (metaFile == null) {
EmptyMeta
} else {
readMetaFile(metaFile)
}
val dataFile = path.resolve(IOPlugin.DATA_FILE_NAME)
val data: Binary? = if (Files.exists(dataFile)) {
dataFile.asBinary()
} else {
null
}
return SimpleEnvelope(meta, data)
}
return formatPeeker(path)?.let { format ->
FileEnvelope(path, format)
} ?: if (readNonEnvelopes) { // if no format accepts file, read it as binary
SimpleEnvelope(Meta.EMPTY, path.asBinary())
} else null
}
/**
* Write a binary into file. Throws an error if file already exists
*/
fun <T : Any> IOFormat<T>.writeToFile(path: Path, obj: T) {
path.write {
writeObject(obj)
}
}
/**
* Write envelope file to given [path] using [envelopeFormat] and optional [metaFormat]
*/
@DFExperimental
fun IOPlugin.writeEnvelopeFile(
path: Path,
envelope: Envelope,
envelopeFormat: EnvelopeFormat = TaggedEnvelopeFormat,
metaFormat: MetaFormatFactory? = null
) {
path.write {
with(envelopeFormat) {
writeEnvelope(envelope, metaFormat ?: envelopeFormat.defaultMetaFormat)
}
}
}
/**
* 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.write {
envelope.data?.read {
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")
}
}
}
}

@ -0,0 +1,28 @@
package hep.dataforge.io
import hep.dataforge.io.functions.FunctionServer
import hep.dataforge.io.functions.function
import hep.dataforge.meta.Meta
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 = 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()
}
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)
}

@ -1,51 +0,0 @@
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.Meta
import hep.dataforge.meta.buildMeta
import hep.dataforge.names.Name
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
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")
}
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)
}
/**
* Write meta to file in a given [format]
*/
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)
}
}

@ -7,7 +7,6 @@ import hep.dataforge.meta.EmptyMeta
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.io.streams.writePacket
import java.net.Socket import java.net.Socket
import java.util.concurrent.Executors import java.util.concurrent.Executors
import kotlin.time.ExperimentalTime import kotlin.time.ExperimentalTime
@ -39,7 +38,7 @@ class EnvelopeClient(
suspend fun close() { suspend fun close() {
try { try {
respond( respond(
Envelope.invoke { Envelope {
type = EnvelopeServer.SHUTDOWN_ENVELOPE_TYPE type = EnvelopeServer.SHUTDOWN_ENVELOPE_TYPE
} }
) )
@ -52,14 +51,14 @@ class EnvelopeClient(
override suspend fun respond(request: Envelope): Envelope = withContext(dispatcher) { override suspend fun respond(request: Envelope): Envelope = withContext(dispatcher) {
//val address = InetSocketAddress(host,port) //val address = InetSocketAddress(host,port)
val socket = Socket(host, port) val socket = Socket(host, port)
val input = socket.getInputStream().asInput() val inputStream = socket.getInputStream()
val output = socket.getOutputStream() val outputStream = socket.getOutputStream()
format.run { format.run {
output.writePacket { outputStream.write {
writeObject(request) writeObject(request)
} }
logger.debug { "Sent request with type ${request.type} to ${socket.remoteSocketAddress}" } 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}" } logger.debug { "Received response with type ${res.type} from ${socket.remoteSocketAddress}" }
return@withContext res return@withContext res
} }

@ -9,7 +9,6 @@ import hep.dataforge.io.type
import hep.dataforge.meta.EmptyMeta import hep.dataforge.meta.EmptyMeta
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.io.streams.writePacket
import java.net.ServerSocket import java.net.ServerSocket
import java.net.Socket import java.net.Socket
import kotlin.concurrent.thread import kotlin.concurrent.thread
@ -71,14 +70,17 @@ class EnvelopeServer(
private fun readSocket(socket: Socket) { private fun readSocket(socket: Socket) {
thread { thread {
val input = socket.getInputStream().asInput() val inputStream = socket.getInputStream()
val outputStream = socket.getOutputStream() val outputStream = socket.getOutputStream()
format.run { format.run {
while (socket.isConnected) { while (socket.isConnected) {
val request = input.readObject() val request = inputStream.readBlocking { readObject() }
logger.debug { "Accepted request with type ${request.type} from ${socket.remoteSocketAddress}" } logger.debug { "Accepted request with type ${request.type} from ${socket.remoteSocketAddress}" }
if (request.type == SHUTDOWN_ENVELOPE_TYPE) { if (request.type == SHUTDOWN_ENVELOPE_TYPE) {
//Echo shutdown command //Echo shutdown command
outputStream.write{
writeObject(request)
}
logger.info { "Accepted graceful shutdown signal from ${socket.inetAddress}" } logger.info { "Accepted graceful shutdown signal from ${socket.inetAddress}" }
socket.close() socket.close()
return@thread return@thread
@ -86,7 +88,7 @@ class EnvelopeServer(
} }
runBlocking { runBlocking {
val response = responder.respond(request) val response = responder.respond(request)
outputStream.writePacket { outputStream.write {
writeObject(response) writeObject(response)
} }
logger.debug { "Sent response with type ${response.type} to ${socket.remoteSocketAddress}" } logger.debug { "Sent response with type ${response.type} to ${socket.remoteSocketAddress}" }

@ -1,33 +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.IoBuffer.Companion.NoPool
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 = NoPool) {
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)

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

@ -1,6 +1,9 @@
package hep.dataforge.io package hep.dataforge.io
import hep.dataforge.context.Global import hep.dataforge.context.Global
import kotlinx.io.asBinary
import kotlinx.io.toByteArray
import kotlinx.io.writeDouble
import java.nio.file.Files import java.nio.file.Files
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -21,7 +24,7 @@ class FileBinaryTest {
@Test @Test
fun testSize() { fun testSize() {
val binary = envelope.data val binary = envelope.data
assertEquals(binary?.size?.toInt(), binary?.toBytes()?.size) assertEquals(binary?.size?.toInt(), binary?.toByteArray()?.size)
} }
@Test @Test
@ -34,12 +37,12 @@ class FileBinaryTest {
"b" put 22.2 "b" put 22.2
} }
dataType = "hep.dataforge.satellite" dataType = "hep.dataforge.satellite"
dataID = "cellDepositTest" // добавил только что dataID = "cellDepositTest"
data = dataFile.asBinary() data = dataFile.asBinary()
} }
val binary = envelopeFromFile.data!! val binary = envelopeFromFile.data!!
println(binary.toBytes().size) println(binary.toByteArray().size)
assertEquals(binary.size.toInt(), binary.toBytes().size) assertEquals(binary.size.toInt(), binary.toByteArray().size)
} }
@ -49,8 +52,7 @@ class FileBinaryTest {
val tmpPath = Files.createTempFile("dataforge_test", ".df") val tmpPath = Files.createTempFile("dataforge_test", ".df")
Global.io.writeEnvelopeFile(tmpPath, envelope) Global.io.writeEnvelopeFile(tmpPath, envelope)
val binary = Global.io.readEnvelopeFile(tmpPath).data!! val binary = Global.io.readEnvelopeFile(tmpPath)?.data!!
assertEquals(binary.size.toInt(), binary.toBytes().size) assertEquals(binary.size.toInt(), binary.toByteArray().size)
} }
} }

@ -1,6 +1,7 @@
package hep.dataforge.io package hep.dataforge.io
import hep.dataforge.context.Global import hep.dataforge.context.Global
import kotlinx.io.writeDouble
import java.nio.file.Files import java.nio.file.Files
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertTrue import kotlin.test.assertTrue
@ -22,10 +23,23 @@ class FileEnvelopeTest {
@Test @Test
fun testFileWriteRead() { fun testFileWriteRead() {
Global.io.run {
val tmpPath = Files.createTempFile("dataforge_test", ".df") val tmpPath = Files.createTempFile("dataforge_test", ".df")
Global.io.writeEnvelopeFile(tmpPath,envelope) writeEnvelopeFile(tmpPath, envelope)
println(tmpPath.toUri()) println(tmpPath.toUri())
val restored: Envelope = Global.io.readEnvelopeFile(tmpPath) val restored: Envelope = readEnvelopeFile(tmpPath)!!
assertTrue { envelope.contentEquals(restored) } assertTrue { envelope.contentEquals(restored) }
} }
} }
@Test
fun testFileWriteReadTagless() {
Global.io.run {
val tmpPath = Files.createTempFile("dataforge_test_tagless", ".df")
writeEnvelopeFile(tmpPath, envelope, envelopeFormat = TaglessEnvelopeFormat)
println(tmpPath.toUri())
val restored: Envelope = readEnvelopeFile(tmpPath)!!
assertTrue { envelope.contentEquals(restored) }
}
}
}

@ -4,19 +4,20 @@ import hep.dataforge.context.Global
import hep.dataforge.io.Envelope import hep.dataforge.io.Envelope
import hep.dataforge.io.Responder import hep.dataforge.io.Responder
import hep.dataforge.io.TaggedEnvelopeFormat import hep.dataforge.io.TaggedEnvelopeFormat
import hep.dataforge.io.writeBytes import hep.dataforge.io.writeByteArray
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.io.writeDouble
import org.junit.AfterClass import org.junit.AfterClass
import org.junit.BeforeClass import org.junit.BeforeClass
import org.junit.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.time.ExperimentalTime import kotlin.time.ExperimentalTime
@ExperimentalStdlibApi @ExperimentalStdlibApi
object EchoResponder : Responder { object EchoResponder : Responder {
override suspend fun respond(request: Envelope): Envelope { override suspend fun respond(request: Envelope): Envelope {
val string = TaggedEnvelopeFormat().run { writeBytes(request).decodeToString() } val string = TaggedEnvelopeFormat().run { writeByteArray(request).decodeToString() }
println("ECHO:") println("ECHO:")
println(string) println(string)
return request return request
@ -43,7 +44,7 @@ class EnvelopeServerTest {
} }
} }
@Test @Test(timeout = 1000)
fun doEchoTest() { fun doEchoTest() {
val request = Envelope.invoke { val request = Envelope.invoke {
type = "test.echo" type = "test.echo"

@ -1,5 +1,9 @@
import scientifik.serialization
plugins { plugins {
id("scientifik.mpp") id("scientifik.mpp")
} }
serialization()
description = "Meta definition and basic operations on meta" description = "Meta definition and basic operations on meta"

@ -1,29 +0,0 @@
package hep.dataforge.descriptors
import hep.dataforge.descriptors.Described.Companion.DESCRIPTOR_NODE
import hep.dataforge.meta.Meta
import hep.dataforge.meta.get
import hep.dataforge.meta.node
/**
* An object which provides its descriptor
*/
interface Described {
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 Meta.descriptor: NodeDescriptor?
get() {
return if (this is Described) {
descriptor
} else {
get(DESCRIPTOR_NODE).node?.let { NodeDescriptor.wrap(it) }
}
}

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

@ -4,6 +4,7 @@ import hep.dataforge.names.Name
import hep.dataforge.names.NameToken import hep.dataforge.names.NameToken
import hep.dataforge.names.asName import hep.dataforge.names.asName
import hep.dataforge.names.plus import hep.dataforge.names.plus
import kotlinx.serialization.*
//TODO add validator to configuration //TODO add validator to configuration
@ -12,10 +13,16 @@ data class MetaListener(
val action: (name: Name, oldItem: MetaItem<*>?, newItem: MetaItem<*>?) -> Unit 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 * Mutable meta representing object state
*/ */
class Config : AbstractMutableMeta<Config>() { @Serializable
class Config : AbstractMutableMeta<Config>(), ObservableMeta {
private val listeners = HashSet<MetaListener>() private val listeners = HashSet<MetaListener>()
@ -26,14 +33,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 * 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)) listeners.add(MetaListener(owner, action))
} }
/** /**
* Remove all listeners belonging to given owner * Remove all listeners belonging to given owner
*/ */
fun removeListener(owner: Any?) { override fun removeListener(owner: Any?) {
listeners.removeAll { it.owner === owner } listeners.removeAll { it.owner === owner }
} }
@ -57,33 +64,34 @@ class Config : AbstractMutableMeta<Config>() {
/** /**
* Attach configuration node instead of creating one * 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() override fun empty(): Config = Config()
companion object { @Serializer(Config::class)
companion object ConfigSerializer : KSerializer<Config> {
fun empty(): Config = 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)
}
} }
} }
operator fun Config.get(token: NameToken): MetaItem<Config>? = items[token] 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 -> this.items.mapValues { entry ->
val item = entry.value val item = entry.value
builder[entry.key.asName()] = when (item) { builder[entry.key.asName()] = when (item) {
is MetaItem.ValueItem -> item.value 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

@ -0,0 +1,132 @@
package hep.dataforge.meta
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 JsonObject.toMeta(descriptor: NodeDescriptor? = null): Meta = JsonMeta(this, descriptor)
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()!! }
}
}

@ -1,9 +1,11 @@
package hep.dataforge.meta package hep.dataforge.meta
import hep.dataforge.names.Name
import hep.dataforge.names.NameToken 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].
* If [layers] list contains a [Laminate] it is flat-mapped.
*/ */
class Laminate(layers: List<Meta>) : MetaBase() { class Laminate(layers: List<Meta>) : MetaBase() {
@ -17,10 +19,11 @@ class Laminate(layers: List<Meta>) : MetaBase() {
constructor(vararg layers: Meta?) : this(layers.filterNotNull()) constructor(vararg layers: Meta?) : this(layers.filterNotNull())
override val items: Map<NameToken, MetaItem<Meta>> override val items: Map<NameToken, MetaItem<Meta>> by lazy {
get() = layers.map { it.items.keys }.flatten().associateWith { key -> layers.map { it.items.keys }.flatten().associateWith { key ->
layers.asSequence().map { it.items[key] }.filterNotNull().let(replaceRule) layers.asSequence().map { it.items[key] }.filterNotNull().let(replaceRule)
} }
}
/** /**
* Generate sealed meta using [mergeRule] * Generate sealed meta using [mergeRule]
@ -61,7 +64,7 @@ class Laminate(layers: List<Meta>) : MetaBase() {
} }
else -> map { else -> map {
when (it) { 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 is MetaItem.NodeItem -> it
} }
}.merge() }.merge()
@ -77,6 +80,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 * Create a new [Laminate] adding given layer to the top
*/ */

@ -4,9 +4,8 @@ import hep.dataforge.meta.Meta.Companion.VALUE_KEY
import hep.dataforge.meta.MetaItem.NodeItem import hep.dataforge.meta.MetaItem.NodeItem
import hep.dataforge.meta.MetaItem.ValueItem import hep.dataforge.meta.MetaItem.ValueItem
import hep.dataforge.names.* import hep.dataforge.names.*
import hep.dataforge.values.EnumValue import hep.dataforge.values.*
import hep.dataforge.values.Value import kotlinx.serialization.*
import hep.dataforge.values.boolean
/** /**
@ -14,13 +13,51 @@ import hep.dataforge.values.boolean
* * a [ValueItem] (leaf) * * a [ValueItem] (leaf)
* * a [NodeItem] (node) * * a [NodeItem] (node)
*/ */
@Serializable
sealed class MetaItem<out M : Meta> { sealed class MetaItem<out M : Meta> {
@Serializable
data class ValueItem(val value: Value) : MetaItem<Nothing>() { data class ValueItem(val value: Value) : MetaItem<Nothing>() {
override fun toString(): String = value.toString() 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)
}
}
} }
data class NodeItem<M : Meta>(val node: M) : MetaItem<M>() { @Serializable
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() override fun toString(): String = node.toString()
@Serializer(NodeItem::class)
companion object : KSerializer<NodeItem<out Meta>> {
override val descriptor: SerialDescriptor get() = MetaSerializer.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 {
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 +82,7 @@ interface Meta : MetaRepr {
*/ */
val items: Map<NameToken, MetaItem<*>> val items: Map<NameToken, MetaItem<*>>
override fun toMeta(): Meta = this override fun toMeta(): Meta = seal()
override fun equals(other: Any?): Boolean override fun equals(other: Any?): Boolean
@ -55,19 +92,26 @@ interface Meta : MetaRepr {
companion object { companion object {
const val TYPE = "meta" const val TYPE = "meta"
/** /**
* A key for single value node * A key for single value node
*/ */
const val VALUE_KEY = "@value" const val VALUE_KEY = "@value"
val empty: EmptyMeta = EmptyMeta val EMPTY: EmptyMeta = EmptyMeta
} }
} }
/* Get operations*/ /* 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 return current [Meta] as a [NodeItem]
*/
operator fun Meta?.get(name: Name): MetaItem<*>? { operator fun Meta?.get(name: Name): MetaItem<*>? {
if (this == null) return null if (this == null) return null
if (name.isEmpty()) return NodeItem(this)
return name.first()?.let { token -> return name.first()?.let { token ->
val tail = name.cutFirst() val tail = name.cutFirst()
when (tail.length) { when (tail.length) {
@ -78,6 +122,10 @@ operator fun Meta?.get(name: Name): MetaItem<*>? {
} }
operator fun Meta?.get(token: NameToken): MetaItem<*>? = this?.items?.get(token) 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()) operator fun Meta?.get(key: String): MetaItem<*>? = get(key.toName())
/** /**
@ -113,32 +161,19 @@ 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 * 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>> override val items: Map<NameToken, MetaItem<M>>
} }
operator fun <M : MetaNode<M>> MetaNode<M>?.get(name: Name): MetaItem<M>? { /**
if (this == null) return null * The same as [Meta.get], but with specific node type
return name.first()?.let { token -> */
val tail = name.cutFirst() @Suppress("UNCHECKED_CAST")
when (tail.length) { operator fun <M : MetaNode<M>> M?.get(name: Name): MetaItem<M>? = (this as Meta)[name] as MetaItem<M>?
0 -> items[token]
else -> items[token]?.node?.get(tail)
}
}
}
operator fun <M : MetaNode<M>> MetaNode<M>?.get(key: String): MetaItem<M>? = if (this == null) { operator fun <M : MetaNode<M>> M?.get(key: String): MetaItem<M>? = this[key.toName()]
null
} else {
this[key.toName()]
}
operator fun <M : MetaNode<M>> MetaNode<M>?.get(key: NameToken): MetaItem<M>? = if (this == null) { operator fun <M : MetaNode<M>> M?.get(key: NameToken): MetaItem<M>? = this[key.asName()]
null
} else {
this[key.asName()]
}
/** /**
* Equals, hashcode and to string for any meta * Equals, hashcode and to string for any meta
@ -153,7 +188,7 @@ abstract class MetaBase : Meta {
override fun hashCode(): Int = items.hashCode() override fun hashCode(): Int = items.hashCode()
override fun toString(): String = items.toString() override fun toString(): String = toJson().toString()
} }
/** /**
@ -166,8 +201,9 @@ abstract class AbstractMetaNode<M : MetaNode<M>> : MetaNode<M>, MetaBase()
* *
* If the argument is possibly mutable node, it is copied on creation * If the argument is possibly mutable node, it is copied on creation
*/ */
class SealedMeta internal constructor(override val items: Map<NameToken, MetaItem<SealedMeta>>) : class SealedMeta internal constructor(
AbstractMetaNode<SealedMeta>() override val items: Map<NameToken, MetaItem<SealedMeta>>
) : AbstractMetaNode<SealedMeta>()
/** /**
* Generate sealed node from [this]. If it is already sealed return it as is * Generate sealed node from [this]. If it is already sealed return it as is
@ -200,7 +236,7 @@ val MetaItem<*>?.int get() = number?.toInt()
val MetaItem<*>?.long get() = number?.toLong() val MetaItem<*>?.long get() = number?.toLong()
val MetaItem<*>?.short get() = number?.toShort() 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 this.value.value as E
} else { } else {
string?.let { enumValueOf<E>(it) } string?.let { enumValueOf<E>(it) }

@ -15,6 +15,10 @@ class MetaBuilder : AbstractMutableMeta<MetaBuilder>() {
override fun wrapNode(meta: Meta): MetaBuilder = if (meta is MetaBuilder) meta else meta.builder() override fun wrapNode(meta: Meta): MetaBuilder = if (meta is MetaBuilder) meta else meta.builder()
override fun empty(): MetaBuilder = MetaBuilder() override fun empty(): MetaBuilder = MetaBuilder()
infix fun String.put(item: MetaItem<*>?) {
set(this, item)
}
infix fun String.put(value: Value?) { infix fun String.put(value: Value?) {
set(this, value) set(this, value)
} }
@ -141,9 +145,11 @@ fun Meta.edit(builder: MetaBuilder.() -> Unit): MetaBuilder = builder().apply(bu
/** /**
* Build a [MetaBuilder] using given transformation * Build a [MetaBuilder] using given transformation
*/ */
@Deprecated("To be replaced with fake constructor", ReplaceWith("Meta"))
fun buildMeta(builder: MetaBuilder.() -> Unit): MetaBuilder = MetaBuilder().apply(builder) fun buildMeta(builder: MetaBuilder.() -> Unit): MetaBuilder = MetaBuilder().apply(builder)
/** /**
* Build meta using given source meta as a base * Build a [MetaBuilder] using given transformation
*/ */
fun buildMeta(source: Meta, builder: MetaBuilder.() -> Unit): MetaBuilder = source.builder().apply(builder) @Suppress("FunctionName")
fun Meta(builder: MetaBuilder.() -> Unit): MetaBuilder = MetaBuilder().apply(builder)

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

@ -0,0 +1,40 @@
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
/**
* 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 {
object : MetaBase() {
override val items: Map<NameToken, MetaItem<*>> = 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)
}
}
}

@ -1,9 +1,10 @@
package hep.dataforge.meta package hep.dataforge.meta
import hep.dataforge.meta.scheme.Configurable
import hep.dataforge.names.* import hep.dataforge.names.*
import hep.dataforge.values.Value 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>> override val items: Map<NameToken, MetaItem<M>>
operator fun set(name: Name, item: MetaItem<*>?) operator fun set(name: Name, item: MetaItem<*>?)
// fun onChange(owner: Any? = null, action: (Name, MetaItem<*>?, MetaItem<*>?) -> Unit) // fun onChange(owner: Any? = null, action: (Name, MetaItem<*>?, MetaItem<*>?) -> Unit)
@ -54,7 +55,8 @@ abstract class AbstractMutableMeta<M : MutableMeta<M>> : AbstractMetaNode<M>(),
0 -> error("Can't setValue meta item for empty name") 0 -> error("Can't setValue meta item for empty name")
1 -> { 1 -> {
val token = name.first()!! 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 -> { else -> {
val token = name.first()!! val token = name.first()!!
@ -71,6 +73,7 @@ abstract class AbstractMutableMeta<M : MutableMeta<M>> : AbstractMetaNode<M>(),
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun MutableMeta<*>.remove(name: Name) = set(name, null) inline fun MutableMeta<*>.remove(name: Name) = set(name, null)
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun MutableMeta<*>.remove(name: String) = remove(name.toName()) inline fun MutableMeta<*>.remove(name: String) = remove(name.toName())
@ -103,7 +106,7 @@ operator fun MutableMeta<*>.set(name: Name, value: Any?) {
null -> remove(name) null -> remove(name)
is MetaItem<*> -> setItem(name, value) is MetaItem<*> -> setItem(name, value)
is Meta -> setNode(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)) else -> setValue(name, Value.of(value))
} }
} }
@ -112,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, 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: * Update existing mutable node with another node. The rules are following:
* * value replaces anything * * value replaces anything
@ -158,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 * 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" } require(!name.isEmpty()) { "Name could not be empty for append operation" }
val newIndex = name.last()!!.index val newIndex = name.last()!!.index
if (newIndex.isNotEmpty()) { if (newIndex.isNotEmpty()) {
@ -169,4 +174,4 @@ fun MutableMeta<*>.append(name: Name, value: Any?) {
} }
} }
fun MutableMeta<*>.append(name: String, value: Any?) = append(name.toName(), value) fun <M : MutableMeta<M>> M.append(name: String, value: Any?) = append(name.toName(), value)

@ -0,0 +1,118 @@
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): 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) })
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>()!! }

@ -1,73 +0,0 @@
package hep.dataforge.meta
import hep.dataforge.names.Name
import kotlin.jvm.JvmName
/**
* 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]
/**
* 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)
}
fun build(action: T.() -> Unit) = update(Config(), action)
fun empty() = build { }
/**
* 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) }
fun <C : Specific> Specific.spec(
spec: Specification<C>,
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)}
@JvmName("configSpec")
fun <T: Specific> MetaItem<Config>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(it)}

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

@ -6,6 +6,6 @@ package hep.dataforge.meta
@DslMarker @DslMarker
annotation class DFBuilder annotation class DFBuilder
@Experimental(level = Experimental.Level.WARNING) @RequiresOptIn(level = RequiresOptIn.Level.WARNING)
@Retention(AnnotationRetention.BINARY) @Retention(AnnotationRetention.BINARY)
annotation class DFExperimental annotation class DFExperimental

@ -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.node(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.node(key: Name? = null, converter: (Meta) -> T) =
MutableMorphDelegate(config, key, converter)

@ -0,0 +1,24 @@
package hep.dataforge.meta.descriptors
/**
* An object which provides its descriptor
*/
interface Described {
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) }
// }
// }

@ -0,0 +1,28 @@
package hep.dataforge.meta.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()
}
}

@ -1,21 +1,17 @@
package hep.dataforge.descriptors package hep.dataforge.meta.descriptors
import hep.dataforge.meta.* import hep.dataforge.meta.*
import hep.dataforge.meta.scheme.*
import hep.dataforge.names.Name
import hep.dataforge.names.NameToken import hep.dataforge.names.NameToken
import hep.dataforge.names.toName import hep.dataforge.names.asName
import hep.dataforge.names.isEmpty
import hep.dataforge.values.False import hep.dataforge.values.False
import hep.dataforge.values.True import hep.dataforge.values.True
import hep.dataforge.values.Value import hep.dataforge.values.Value
import hep.dataforge.values.ValueType import hep.dataforge.values.ValueType
sealed class ItemDescriptor(override val config: Config) : Specific { sealed class ItemDescriptor : Scheme() {
/**
* 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 * True if same name siblings with this name are allowed
@ -36,7 +32,7 @@ sealed class ItemDescriptor(override val config: Config) : Specific {
* *
* @return * @return
*/ */
var attributes by node() var attributes by config()
/** /**
* True if the item is required * True if the item is required
@ -46,13 +42,33 @@ sealed class ItemDescriptor(override val config: Config) : Specific {
abstract var required: Boolean 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 * Descriptor for meta node. Could contain additional information for viewing
* and editing. * and editing.
* *
* @author Alexander Nozik * @author Alexander Nozik
*/ */
class NodeDescriptor(config: Config) : ItemDescriptor(config) { @DFBuilder
class NodeDescriptor : ItemDescriptor() {
/** /**
* True if the node is required * True if the node is required
@ -66,46 +82,74 @@ class NodeDescriptor(config: Config) : ItemDescriptor(config) {
* *
* @return * @return
*/ */
var default: Config? by node() var default by node()
/**
* The list of value descriptors
*/
val values: Map<String, ValueDescriptor>
get() = config.getIndexed(VALUE_KEY.toName()).entries.associate { (name, node) ->
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.build { this.name = name }.apply(block))
}
/** /**
* The map of children node descriptors * The map of children node descriptors
*/ */
val nodes: Map<String, NodeDescriptor> val nodes: Map<String, NodeDescriptor>
get() = config.getIndexed(NODE_KEY.toName()).entries.associate { (name, node) -> get() = config.getIndexed(NODE_KEY.asName()).entries.associate { (name, node) ->
name to wrap(node.node ?: error("Node descriptor must be a node")) 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") 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 config[token] = descriptor.config
} }
fun node(name: String, block: NodeDescriptor.() -> Unit) {
node(name, build { this.name = name }.apply(block)) fun defineNode(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 defineNode(name: Name, block: NodeDescriptor.() -> Unit) {
buildNode(name).apply(block)
}
/**
* The list of value descriptors
*/
val values: Map<String, ValueDescriptor>
get() = config.getIndexed(VALUE_KEY.asName()).entries.associate { (name, node) ->
name to ValueDescriptor.wrap(node.node ?: error("Value descriptor must be a node"))
}
/**
* Add a value descriptor using block for
*/
fun defineValue(name: String, block: ValueDescriptor.() -> Unit) {
defineItem(name, ValueDescriptor(block))
}
fun defineValue(name: Name, block: ValueDescriptor.() -> Unit) {
require(name.length >= 1) { "Name length for value descriptor must be non-empty" }
buildNode(name.cutLast()).defineValue(name.last().toString(), block)
} }
val items: Map<String, ItemDescriptor> get() = nodes + values val items: Map<String, ItemDescriptor> get() = nodes + values
@ -113,18 +157,28 @@ class NodeDescriptor(config: Config) : ItemDescriptor(config) {
//override val descriptor: NodeDescriptor = empty("descriptor") //override val descriptor: NodeDescriptor = empty("descriptor")
companion object : Specification<NodeDescriptor> { companion object : SchemeSpec<NodeDescriptor>(::NodeDescriptor) {
// const val ITEM_KEY = "item" // const val ITEM_KEY = "item"
const val NODE_KEY = "node" const val NODE_KEY = "node"
const val VALUE_KEY = "value" 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 //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 * A descriptor for meta value
@ -133,7 +187,7 @@ class NodeDescriptor(config: Config) : ItemDescriptor(config) {
* *
* @author Alexander Nozik * @author Alexander Nozik
*/ */
class ValueDescriptor(config: Config) : ItemDescriptor(config) { class ValueDescriptor : ItemDescriptor() {
/** /**
@ -159,8 +213,8 @@ class ValueDescriptor(config: Config) : ItemDescriptor(config) {
* *
* @return * @return
*/ */
var type: List<ValueType> by value { var type: List<ValueType> by item {
it?.list?.map { v -> ValueType.valueOf(v.string) } ?: emptyList() it?.value?.list?.map { v -> ValueType.valueOf(v.string) } ?: emptyList()
} }
fun type(vararg t: ValueType) { fun type(vararg t: ValueType) {
@ -201,16 +255,11 @@ class ValueDescriptor(config: Config) : ItemDescriptor(config) {
this.allowedValues = v.map { Value.of(it) } this.allowedValues = v.map { Value.of(it) }
} }
companion object : Specification<ValueDescriptor> { companion object : SchemeSpec<ValueDescriptor>(::ValueDescriptor) {
// inline fun <reified E : Enum<E>> enum(name: String) = ValueDescriptor {
override fun wrap(config: Config): ValueDescriptor = ValueDescriptor(config) // type(ValueType.STRING)
// this.allowedValues = enumValues<E>().map { Value.of(it.name) }
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) }
}
// /** // /**
// * Build a value descriptor from annotation // * Build a value descriptor from annotation

@ -1,16 +1,8 @@
package hep.dataforge.meta package hep.dataforge.meta
import hep.dataforge.descriptors.NodeDescriptor import hep.dataforge.meta.descriptors.NodeDescriptor
import hep.dataforge.values.Value 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 * Convert meta to map of maps
*/ */
@ -28,7 +20,7 @@ fun Meta.toMap(descriptor: NodeDescriptor? = null): Map<String, Any?> {
* Convert map of maps to meta * Convert map of maps to meta
*/ */
@DFExperimental @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) -> entries.forEach { (key, value) ->
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
when (value) { when (value) {

@ -1,432 +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.node(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 MutableMorphDelegate<M : MutableMeta<M>, T : Configurable>(
val meta: M,
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,
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.node(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) }

@ -26,26 +26,13 @@ fun Meta.getIndexed(name: Name): Map<String, MetaItem<*>> {
@DFExperimental @DFExperimental
fun Meta.getIndexed(name: String): Map<String, MetaItem<*>> = this@getIndexed.getIndexed(name.toName()) fun Meta.getIndexed(name: String): Map<String, MetaItem<*>> = this@getIndexed.getIndexed(name.toName())
/** /**
* Get all items matching given name. * Get all items matching given name.
*/ */
@Suppress("UNCHECKED_CAST")
@DFExperimental @DFExperimental
fun <M : MetaNode<M>> M.getIndexed(name: Name): Map<String, MetaItem<M>> { fun <M : MetaNode<M>> M.getIndexed(name: Name): Map<String, MetaItem<M>> =
val root: MetaNode<M>? = when (name.length) { (this as Meta).getIndexed(name) as Map<String, MetaItem<M>>
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()
}
@DFExperimental @DFExperimental
fun <M : MetaNode<M>> M.getIndexed(name: String): Map<String, MetaItem<M>> = getIndexed(name.toName()) fun <M : MetaNode<M>> M.getIndexed(name: String): Map<String, MetaItem<M>> = getIndexed(name.toName())

@ -0,0 +1,68 @@
package hep.dataforge.meta.scheme
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.
* 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 : Described {
/**
* Backing config
*/
val config: Config
/**
* Default meta item provider
*/
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
/**
* Get a property with default
*/
fun getProperty(name: Name): MetaItem<*>? =
config[name] ?: getDefaultItem(name) ?: descriptor?.get(name)?.defaultItem()
/**
* 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.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) }

@ -0,0 +1,236 @@
package hep.dataforge.meta.scheme
import 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)
}
}
class LazyConfigurableDelegate(
configurable: Configurable,
key: Name? = null,
defaultProvider: () -> MetaItem<*>? = { null }
) : ConfigurableDelegate(configurable, key) {
override val default by lazy(defaultProvider)
}
/**
* A property delegate that uses custom key
*/
fun Configurable.item(default: Any? = null, key: Name? = null): ConfigurableDelegate =
ConfigurableDelegate(
this,
key,
default?.let { MetaItem.of(it) })
/**
* Generation of item delegate with lazy default.
* Lazy default could be used also for validation
*/
fun Configurable.lazyItem(key: Name? = null, default: () -> Any?): ConfigurableDelegate =
LazyConfigurableDelegate(this, key) {
default()?.let {
MetaItem.of(it)
}
}
fun <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) }).map(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) }
).map(
reader = { reader(it.value) },
writer = { value -> writer(value)?.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
*/
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()
}
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.config(key: Name? = null): ReadWriteProperty<Any?, Config?> =
config.node(key)
fun Configurable.node(key: Name? = null): ReadWriteProperty<Any?, Meta?> = item(key).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?> {
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
}
}

@ -0,0 +1,90 @@
package hep.dataforge.meta.scheme
import hep.dataforge.meta.*
import hep.dataforge.meta.descriptors.*
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, MetaRepr {
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 var config: Config = Config()
internal set
var defaultProvider: (Name) -> MetaItem<*>? = { null }
internal set
final override var descriptor: NodeDescriptor? = null
internal set
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)
override fun toMeta(): Laminate = Laminate(config, defaultLayer)
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()
}
}
inline operator fun <T : Scheme> T.invoke(block: T.() -> Unit) = apply(block)
/**
* 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
}
}
}
/**
* A scheme that uses [Meta] as a default layer
*/
open class MetaScheme(
val meta: Meta,
descriptor: NodeDescriptor? = null,
config: Config = Config()
) : Scheme(config, meta::get) {
init {
this.descriptor = descriptor
}
override val defaultLayer: Meta
get() = Laminate(meta, descriptor?.defaultItem().node)
}
fun Meta.asScheme() =
MetaScheme(this)
fun <T : Configurable> Meta.toScheme(spec: Specification<T>, block: T.() -> Unit) = spec.wrap(this).apply(block)

@ -0,0 +1,67 @@
package hep.dataforge.meta.scheme
import 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) }

@ -0,0 +1,65 @@
package hep.dataforge.meta
import kotlinx.serialization.*
import kotlinx.serialization.builtins.DoubleArraySerializer
import kotlinx.serialization.builtins.serializer
fun SerialDescriptorBuilder.boolean(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
element(name, Boolean.serializer().descriptor, isOptional = isOptional, annotations = annotations.toList())
fun SerialDescriptorBuilder.string(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
element(name, String.serializer().descriptor, isOptional = isOptional, annotations = annotations.toList())
fun SerialDescriptorBuilder.int(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
element(name, Int.serializer().descriptor, isOptional = isOptional, annotations = annotations.toList())
fun SerialDescriptorBuilder.double(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
element(name, Double.serializer().descriptor, isOptional = isOptional, annotations = annotations.toList())
fun SerialDescriptorBuilder.float(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
element(name, Float.serializer().descriptor, isOptional = isOptional, annotations = annotations.toList())
fun SerialDescriptorBuilder.long(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
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
) {
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())
}
@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
}
@DFExperimental
inline fun Encoder.encodeStructure(
desc: SerialDescriptor,
vararg typeParams: KSerializer<*> = emptyArray(),
block: CompositeEncoder.() -> Unit
) {
val encoder = beginStructure(desc, *typeParams)
encoder.block()
encoder.endStructure(desc)
}

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

@ -1,5 +1,6 @@
package hep.dataforge.meta package hep.dataforge.meta.transformations
import hep.dataforge.meta.*
import hep.dataforge.names.Name import hep.dataforge.names.Name
/** /**
@ -8,7 +9,7 @@ import hep.dataforge.names.Name
interface TransformationRule { 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 fun matches(name: Name, item: MetaItem<*>?): Boolean
@ -29,7 +30,8 @@ interface TransformationRule {
/** /**
* A transformation which keeps all elements, matching [selector] unchanged. * 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 { override fun matches(name: Name, item: MetaItem<*>?): Boolean {
return selector(name) return selector(name)
} }
@ -87,7 +89,8 @@ inline class MetaTransformation(val transformations: Collection<TransformationRu
/** /**
* Produce new meta using only those items that match transformation rules * Produce new meta using only those items that match transformation rules
*/ */
fun transform(source: Meta): Meta = buildMeta { fun transform(source: Meta): Meta =
Meta {
transformations.forEach { rule -> transformations.forEach { rule ->
rule.selectItems(source).forEach { name -> rule.selectItems(source).forEach { name ->
rule.transformItem(name, source[name], this) rule.transformItem(name, source[name], this)
@ -98,7 +101,8 @@ inline class MetaTransformation(val transformations: Collection<TransformationRu
/** /**
* Transform a meta, replacing all elements found in rules with transformed entries * Transform a meta, replacing all elements found in rules with transformed entries
*/ */
fun apply(source: Meta): Meta = buildMeta(source) { fun apply(source: Meta): Meta =
source.edit {
transformations.forEach { rule -> transformations.forEach { rule ->
rule.selectItems(source).forEach { name -> rule.selectItems(source).forEach { name ->
remove(name) remove(name)
@ -150,7 +154,8 @@ class MetaTransformationBuilder {
* Keep nodes by regex * Keep nodes by regex
*/ */
fun keep(regex: String) { fun keep(regex: String) {
transformations.add(RegexItemTransformationRule(regex.toRegex()) { name, _, metaItem -> transformations.add(
RegexItemTransformationRule(regex.toRegex()) { name, _, metaItem ->
setItem(name, metaItem) setItem(name, metaItem)
}) })
} }

@ -1,11 +1,14 @@
package hep.dataforge.names package hep.dataforge.names
import kotlinx.serialization.*
/** /**
* The general interface for working with names. * The general interface for working with names.
* The name is a dot separated list of strings like `token1.token2.token3`. * The name is a dot separated list of strings like `token1.token2.token3`.
* Each token could contain additional index in square brackets. * Each token could contain additional index in square brackets.
*/ */
@Serializable
class Name(val tokens: List<NameToken>) { class Name(val tokens: List<NameToken>) {
val length get() = tokens.size val length get() = tokens.size
@ -50,9 +53,21 @@ class Name(val tokens: List<NameToken>) {
} }
} }
@Serializer(Name::class)
companion object { companion object : KSerializer<Name> {
const val NAME_SEPARATOR = "." const val NAME_SEPARATOR = "."
val EMPTY = Name(emptyList())
override val descriptor: SerialDescriptor = PrimitiveDescriptor("hep.dataforge.names.Name", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): Name {
return decoder.decodeString().toName()
}
override fun serialize(encoder: Encoder, value: Name) {
encoder.encodeString(value.toString())
}
} }
} }
@ -61,6 +76,7 @@ class Name(val tokens: List<NameToken>) {
* Following symbols are prohibited in name tokens: `{}.:\`. * Following symbols are prohibited in name tokens: `{}.:\`.
* A name token could have appendix in square brackets called *index* * A name token could have appendix in square brackets called *index*
*/ */
@Serializable
data class NameToken(val body: String, val index: String = "") { data class NameToken(val body: String, val index: String = "") {
init { init {
@ -80,6 +96,19 @@ data class NameToken(val body: String, val index: String = "") {
} }
fun hasIndex() = index.isNotEmpty() fun hasIndex() = index.isNotEmpty()
@Serializer(NameToken::class)
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()!!
}
override fun serialize(encoder: Encoder, value: NameToken) {
encoder.encodeString(value.toString())
}
}
} }
/** /**
@ -87,7 +116,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. * This operation is rather heavy so it should be used with care in high performance code.
*/ */
fun String.toName(): Name { fun String.toName(): Name {
if (isBlank()) return EmptyName if (isBlank()) return Name.EMPTY
val tokens = sequence { val tokens = sequence {
var bodyBuilder = StringBuilder() var bodyBuilder = StringBuilder()
var queryBuilder = StringBuilder() var queryBuilder = StringBuilder()
@ -139,7 +168,7 @@ fun String.toName(): Name {
* Convert the [String] to a [Name] by simply wrapping it in a single name token without parsing. * 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. * 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) operator fun NameToken.plus(other: Name): Name = Name(listOf(this) + other.tokens)
@ -153,8 +182,6 @@ fun Name.appendLeft(other: String): Name = NameToken(other) + this
fun NameToken.asName() = Name(listOf(this)) fun NameToken.asName() = Name(listOf(this))
val EmptyName = Name(emptyList())
fun Name.isEmpty(): Boolean = this.length == 0 fun Name.isEmpty(): Boolean = this.length == 0
/** /**
@ -182,6 +209,8 @@ fun Name.startsWith(token: NameToken): Boolean = first() == token
fun Name.endsWith(token: NameToken): Boolean = last() == 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 fun Name.endsWith(name: Name): Boolean =
this.length >= name.length && tokens.subList(length - name.length, length) == name.tokens

@ -1,5 +1,7 @@
package hep.dataforge.values package hep.dataforge.values
import kotlinx.serialization.Serializable
/** /**
* The list of supported Value types. * The list of supported Value types.
@ -7,6 +9,7 @@ package hep.dataforge.values
* Time value and binary value are represented by string * Time value and binary value are represented by string
* *
*/ */
@Serializable
enum class ValueType { enum class ValueType {
NUMBER, STRING, BOOLEAN, NULL NUMBER, STRING, BOOLEAN, NULL
} }
@ -228,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 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 * Create Value from String using closest match conversion

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

@ -1,7 +1,6 @@
package hep.dataforge.values package hep.dataforge.values
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.meta.buildMeta
/** /**
* Check if value is null * Check if value is null
@ -22,6 +21,7 @@ val Value.boolean
val Value.int get() = number.toInt() val Value.int get() = number.toInt()
val Value.double get() = number.toDouble() val Value.double get() = number.toDouble()
val Value.float get() = number.toFloat() val Value.float get() = number.toFloat()
val Value.short get() = number.toShort()
val Value.long get() = number.toLong() val Value.long get() = number.toLong()
val Value.stringList: List<String> get() = list.map { it.string } val Value.stringList: List<String> get() = list.map { it.string }
@ -34,4 +34,4 @@ val Value.doubleArray: DoubleArray
} }
fun Value.toMeta() = buildMeta { Meta.VALUE_KEY put this } fun Value.toMeta() = Meta { Meta.VALUE_KEY put this }

@ -1,5 +1,6 @@
package hep.dataforge.meta package hep.dataforge.meta
import hep.dataforge.meta.scheme.*
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -10,26 +11,29 @@ class MetaDelegateTest {
NO 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) { enum<TestEnum>() }
var inner by spec(InnerSpec)
companion object : SchemeSpec<TestScheme>(::TestScheme)
}
@Test @Test
fun delegateTest() { fun delegateTest() {
class InnerSpec(override val config: Config) : Specific { val testObject = TestScheme.empty()
var innerValue by string()
}
val innerSpec = specification(::InnerSpec)
val testObject = object : Specific {
override val config: Config = Config()
var myValue by string()
var safeValue by double(2.2)
var enumValue by enum(TestEnum.YES)
var inner by spec(innerSpec)
}
testObject.config["myValue"] = "theString" testObject.config["myValue"] = "theString"
testObject.enumValue = TestEnum.NO testObject.enumValue = TestEnum.NO
testObject.inner = innerSpec.build { innerValue = "ddd"} testObject.inner = InnerSpec { innerValue = "ddd" }
assertEquals("theString", testObject.myValue) assertEquals("theString", testObject.myValue)
assertEquals(TestEnum.NO, testObject.enumValue) assertEquals(TestEnum.NO, testObject.enumValue)

@ -11,12 +11,12 @@ class MetaExtensionTest {
@Test @Test
fun testEnum(){ fun testEnum(){
val meta = buildMeta{"enum" put TestEnum.test} val meta = Meta{"enum" put TestEnum.test}
meta["enum"].enum<TestEnum>() meta["enum"].enum<TestEnum>()
} }
@Test @Test
fun testEnumByString(){ fun testEnumByString(){
val meta = buildMeta{"enum" put TestEnum.test.name} val meta = Meta{"enum" put TestEnum.test.name}
println(meta["enum"].enum<TestEnum>()) println(meta["enum"].enum<TestEnum>())
} }

@ -16,13 +16,13 @@ class MetaTest {
@Test @Test
fun metaEqualityTest() { fun metaEqualityTest() {
val meta1 = buildMeta { val meta1 = Meta {
"a" put 22 "a" put 22
"b" put { "b" put {
"c" put "ddd" "c" put "ddd"
} }
} }
val meta2 = buildMeta { val meta2 = Meta {
"b" put { "b" put {
"c" put "ddd" "c" put "ddd"
} }
@ -33,13 +33,13 @@ class MetaTest {
@Test @Test
fun metaToMap(){ fun metaToMap(){
val meta = buildMeta { val meta = Meta {
"a" put 22 "a" put 22
"b" put { "b" put {
"c" put "ddd" "c" put "ddd"
} }
"list" put (0..4).map { "list" put (0..4).map {
buildMeta { Meta {
"value" put it "value" put it
} }
} }

@ -6,7 +6,7 @@ import kotlin.test.assertEquals
class MutableMetaTest{ class MutableMetaTest{
@Test @Test
fun testRemove(){ fun testRemove(){
val meta = buildMeta { val meta = Meta {
"aNode" put { "aNode" put {
"innerNode" put { "innerNode" put {
"innerValue" put true "innerValue" put true
@ -14,7 +14,7 @@ class MutableMetaTest{
"b" put 22 "b" put 22
"c" put "StringValue" "c" put "StringValue"
} }
}.toConfig() }.asConfig()
meta.remove("aNode.c") meta.remove("aNode.c")
assertEquals(meta["aNode.c"], null) assertEquals(meta["aNode.c"], null)

@ -1,22 +1,27 @@
package hep.dataforge.meta package hep.dataforge.meta
import hep.dataforge.meta.scheme.asScheme
import hep.dataforge.meta.scheme.getProperty
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
class StyledTest{ class SchemeTest{
@Test @Test
fun testSNS(){ fun testMetaScheme(){
val meta = buildMeta { val styled = Meta {
repeat(10){ repeat(10){
"b.a[$it]" put { "b.a[$it]" put {
"d" put it "d" put it
} }
} }
}.seal().withStyle() }.asScheme()
val meta = styled.toMeta()
assertEquals(10, meta.values().count()) assertEquals(10, meta.values().count())
val bNode = meta["b"].node val bNode = styled.getProperty("b").node
val aNodes = bNode?.getIndexed("a") val aNodes = bNode?.getIndexed("a")

@ -1,21 +1,29 @@
package hep.dataforge.meta 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.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
class SpecificationTest { 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) var list by numberList(1, 2, 3)
companion object : Specification<TestSpecific> { companion object : Specification<TestStyled> {
override fun wrap(config: Config): TestSpecific = TestSpecific(config) override fun wrap(
config: Config,
defaultProvider: (Name) -> MetaItem<*>?
): TestStyled = TestStyled(config, defaultProvider)
} }
} }
@Test @Test
fun testSpecific() { fun testSpecific() {
val testObject = TestSpecific.build { val testObject = TestStyled {
list = emptyList() list = emptyList()
} }
assertEquals(emptyList(), testObject.list) assertEquals(emptyList(), testObject.list)

@ -1,4 +1,4 @@
package hep.dataforge.descriptors package hep.dataforge.meta.descriptors
import hep.dataforge.values.ValueType import hep.dataforge.values.ValueType
import kotlin.test.Test import kotlin.test.Test
@ -6,15 +6,15 @@ import kotlin.test.assertEquals
class DescriptorTest { class DescriptorTest {
val descriptor = NodeDescriptor.build { val descriptor = NodeDescriptor {
node("aNode") { defineNode("aNode") {
info = "A root demo node" info = "A root demo node"
value("b") { defineValue("b") {
info = "b number value" info = "b number value"
type(ValueType.NUMBER) type(ValueType.NUMBER)
} }
node("otherNode") { defineNode("otherNode") {
value("otherValue") { defineValue("otherValue") {
type(ValueType.BOOLEAN) type(ValueType.BOOLEAN)
default(false) default(false)
info = "default value" info = "default value"

@ -3,7 +3,8 @@ package hep.dataforge.output.html
import hep.dataforge.context.Context import hep.dataforge.context.Context
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.output.Output 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.output.html.HtmlBuilder.Companion.HTML_CONVERTER_TYPE
import hep.dataforge.provider.Type import hep.dataforge.provider.Type
import hep.dataforge.provider.top import hep.dataforge.provider.top
@ -14,11 +15,11 @@ import kotlinx.html.p
import kotlin.reflect.KClass 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<*>>() 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) { 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 [kotlinx.io.core.Output] * A text or binary renderer based on [Renderer]
*/ */
@Type(HTML_CONVERTER_TYPE) @Type(HTML_CONVERTER_TYPE)
interface HtmlBuilder<T : Any> { interface HtmlBuilder<T : Any> {

@ -1,13 +1,9 @@
package hep.dataforge.output package hep.dataforge.output
import hep.dataforge.context.AbstractPlugin import hep.dataforge.context.*
import hep.dataforge.context.Context
import hep.dataforge.context.PluginFactory
import hep.dataforge.context.PluginTag
import hep.dataforge.context.PluginTag.Companion.DATAFORGE_GROUP import hep.dataforge.context.PluginTag.Companion.DATAFORGE_GROUP
import hep.dataforge.meta.EmptyMeta import hep.dataforge.meta.EmptyMeta
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.names.EmptyName
import hep.dataforge.names.Name import hep.dataforge.names.Name
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -22,14 +18,14 @@ interface OutputManager {
* Get an output specialized for given type, name and stage. * 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 stage represents the node or directory for the output. Empty means root node.
* @param name represents the name inside the 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( operator fun <T : Any> get(
type: KClass<out T>, type: KClass<out T>,
name: Name, name: Name,
stage: Name = EmptyName, stage: Name = Name.EMPTY,
meta: Meta = EmptyMeta meta: Meta = EmptyMeta
): Output<T> ): Renderer<T>
} }
/** /**
@ -42,28 +38,35 @@ val Context.output: OutputManager get() = plugins.get() ?: ConsoleOutputManager(
*/ */
inline operator fun <reified T : Any> OutputManager.get( inline operator fun <reified T : Any> OutputManager.get(
name: Name, name: Name,
stage: Name = EmptyName, stage: Name = Name.EMPTY,
meta: Meta = EmptyMeta meta: Meta = EmptyMeta
): Output<T> { ): Renderer<T> {
return get(T::class, name, stage, meta) return get(T::class, name, stage, meta)
} }
/** /**
* Directly render an object using the most suitable renderer * 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) get(obj::class, name, stage).render(obj, meta)
/** /**
* System console output. * 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 { class ConsoleOutputManager : AbstractPlugin(), OutputManager {
override val tag: PluginTag get() = ConsoleOutputManager.tag 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> { companion object : PluginFactory<ConsoleOutputManager> {
override val tag = PluginTag("output.console", group = DATAFORGE_GROUP) override val tag = PluginTag("output.console", group = DATAFORGE_GROUP)

Some files were not shown because too many files have changed in this diff Show More