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]

@ -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)
![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 #
In this section we will try to cover DataForge main ideas in the form of questions and answers.

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

@ -1,10 +1,12 @@
import scientifik.coroutines
plugins {
id("scientifik.mpp")
}
description = "Context and provider definitions"
val coroutinesVersion: String = Scientifik.coroutinesVersion
coroutines()
kotlin {
sourceSets {
@ -12,21 +14,18 @@ kotlin {
dependencies {
api(project(":dataforge-meta"))
api(kotlin("reflect"))
api("io.github.microutils:kotlin-logging-common:1.7.2")
api("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$coroutinesVersion")
api("io.github.microutils:kotlin-logging-common:1.7.8")
}
}
val jvmMain by getting {
dependencies {
api("io.github.microutils:kotlin-logging:1.7.2")
api("io.github.microutils:kotlin-logging:1.7.8")
api("ch.qos.logback:logback-classic:1.2.3")
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
}
}
val jsMain by getting {
dependencies {
api("io.github.microutils:kotlin-logging-js:1.7.2")
api("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$coroutinesVersion")
api("io.github.microutils:kotlin-logging-js:1.7.8")
}
}
}

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

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

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

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

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

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

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

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

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

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

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

@ -1,5 +1,6 @@
package hep.dataforge.data
import hep.dataforge.meta.Meta
import hep.dataforge.names.NameToken
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.
*/
class TypeFilteredDataNode<out T : Any>(val origin: DataNode<*>, override val type: KClass<out T>) : DataNode<T> {
override val meta: Meta get() = origin.meta
override val items: Map<NameToken, DataItem<T>> by lazy {
origin.items.mapNotNull { (key, item) ->
when (item) {
is DataItem.Leaf -> {
(item.value.filterIsInstance(type))?.let {
(item.data.filterIsInstance(type))?.let {
key to DataItem.Leaf(it)
}
}
is DataItem.Node -> {
key to DataItem.Node(item.value.filterIsInstance(type))
key to DataItem.Node(item.node.filterIsInstance(type))
}
}
}.associate { it }

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

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

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

@ -5,7 +5,10 @@ import hep.dataforge.io.*
import hep.dataforge.meta.DFExperimental
import hep.dataforge.meta.EmptyMeta
import hep.dataforge.meta.Meta
import kotlinx.io.core.*
import kotlinx.io.*
import kotlinx.io.text.readUtf8Line
import kotlinx.io.text.writeRawString
import kotlinx.io.text.writeUtf8String
import kotlinx.serialization.toUtf8Bytes
@DFExperimental
@ -18,52 +21,61 @@ class FrontMatterEnvelopeFormat(
var line: String = ""
var offset = 0u
do {
line = readUTF8Line() ?: error("Input does not contain front matter separator")
line = readUtf8Line() //?: error("Input does not contain front matter separator")
offset += line.toUtf8Bytes().size.toUInt()
} while (!line.startsWith(SEPARATOR))
val readMetaFormat =
metaTypeRegex.matchEntire(line)?.groupValues?.first()
?.let { io.metaFormat(it) } ?: YamlMetaFormat.default
?.let { io.metaFormat(it) } ?: YamlMetaFormat
val metaBlock = buildPacket {
val meta = buildBytes {
do {
line = readUTF8Line() ?: error("Input does not contain closing front matter separator")
appendln(line)
line = readUtf8Line()
writeUtf8String(line + "\r\n")
offset += line.toUtf8Bytes().size.toUInt()
} while (!line.startsWith(SEPARATOR))
}.read {
readMetaFormat.run {
readMeta()
}
}
val meta = readMetaFormat.fromBytes(metaBlock)
return PartialEnvelope(meta, offset, null)
}
override fun Input.readObject(): Envelope {
var line: String = ""
do {
line = readUTF8Line() ?: error("Input does not contain front matter separator")
line = readUtf8Line() //?: error("Input does not contain front matter separator")
} while (!line.startsWith(SEPARATOR))
val readMetaFormat =
metaTypeRegex.matchEntire(line)?.groupValues?.first()
?.let { io.metaFormat(it) } ?: YamlMetaFormat.default
?.let { io.metaFormat(it) } ?: YamlMetaFormat
val metaBlock = buildPacket {
val meta = buildBytes {
do {
appendln(readUTF8Line() ?: error("Input does not contain closing front matter separator"))
writeUtf8String(readUtf8Line() + "\r\n")
} while (!line.startsWith(SEPARATOR))
}.read {
readMetaFormat.run {
readMeta()
}
}
val meta = readMetaFormat.fromBytes(metaBlock)
val bytes = readBytes()
val bytes = readRemaining()
val data = bytes.asBinary()
return SimpleEnvelope(meta, data)
}
override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) {
val metaFormat = metaFormatFactory(formatMeta, io.context)
writeText("$SEPARATOR\r\n")
writeRawString("$SEPARATOR\r\n")
metaFormat.run { writeObject(envelope.meta) }
writeText("$SEPARATOR\r\n")
envelope.data?.read { copyTo(this@writeEnvelope) }
writeRawString("$SEPARATOR\r\n")
//Printing data
envelope.data?.let { data ->
writeBinary(data)
}
}
companion object : EnvelopeFormatFactory {
@ -72,17 +84,28 @@ class FrontMatterEnvelopeFormat(
private val metaTypeRegex = "---(\\w*)\\s*".toRegex()
override fun invoke(meta: Meta, context: Context): EnvelopeFormat {
return FrontMatterEnvelopeFormat(context.io, meta)
return FrontMatterEnvelopeFormat(context.io, meta)
}
override fun peekFormat(io: IOPlugin, input: Input): EnvelopeFormat? {
val line = input.readUTF8Line(3, 30)
return if (line != null && line.startsWith("---")) {
val line = input.readUtf8Line()
return if (line.startsWith("---")) {
invoke()
} else {
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
import hep.dataforge.context.Context
import hep.dataforge.descriptors.NodeDescriptor
import hep.dataforge.io.MetaFormat
import hep.dataforge.io.MetaFormatFactory
import hep.dataforge.meta.DFExperimental
import hep.dataforge.meta.Meta
import hep.dataforge.meta.descriptors.NodeDescriptor
import hep.dataforge.meta.toMap
import hep.dataforge.meta.toMeta
import hep.dataforge.names.Name
import hep.dataforge.names.plus
import kotlinx.io.core.Input
import kotlinx.io.core.Output
import kotlinx.io.core.readUByte
import kotlinx.io.core.writeText
import kotlinx.io.Input
import kotlinx.io.Output
import kotlinx.io.readUByte
import kotlinx.io.text.writeUtf8String
import org.yaml.snakeyaml.Yaml
import java.io.InputStream
private class InputAsStream(val input: Input) : InputStream() {
override fun read(): Int {
if (input.endOfInput) return -1
if (input.eof()) return -1
return input.readUByte().toInt()
}
@ -36,7 +34,7 @@ class YamlMetaFormat(val meta: Meta) : MetaFormat {
override fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor?) {
val string = yaml.dump(meta.toMap(descriptor))
writeText(string)
writeUtf8String(string)
}
override fun Input.readMeta(descriptor: NodeDescriptor?): Meta {
@ -45,12 +43,18 @@ class YamlMetaFormat(val meta: Meta) : MetaFormat {
}
companion object : MetaFormatFactory {
val default = YamlMetaFormat()
override fun invoke(meta: Meta, context: Context): MetaFormat = YamlMetaFormat(meta)
override val name: Name = super.name + "yaml"
override val shortName = "yaml"
override val key: Short = 0x594d //YM
private val default = YamlMetaFormat()
override fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor?) =
default.run { writeMeta(meta, descriptor) }
override fun Input.readMeta(descriptor: NodeDescriptor?): Meta =
default.run { readMeta(descriptor) }
}
}

@ -3,17 +3,16 @@ package hep.dataforge.io.yaml
import hep.dataforge.io.parse
import hep.dataforge.io.toString
import hep.dataforge.meta.Meta
import hep.dataforge.meta.buildMeta
import hep.dataforge.meta.get
import hep.dataforge.meta.seal
import org.junit.Test
import kotlin.test.Test
import kotlin.test.assertEquals
class YamlMetaFormatTest{
class YamlMetaFormatTest {
@Test
fun testYamlMetaFormat(){
val meta = buildMeta {
fun testYamlMetaFormat() {
val meta = Meta {
"a" put 22
"node" put {
"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
import hep.dataforge.context.Context
import hep.dataforge.descriptors.NodeDescriptor
import hep.dataforge.meta.*
import hep.dataforge.names.Name
import hep.dataforge.names.plus
import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaBuilder
import hep.dataforge.meta.MetaItem
import hep.dataforge.meta.descriptors.NodeDescriptor
import hep.dataforge.meta.setItem
import hep.dataforge.values.*
import kotlinx.io.core.Input
import kotlinx.io.core.Output
import kotlinx.io.core.readText
import kotlinx.io.core.writeText
import kotlinx.io.*
import kotlinx.io.text.readUtf8String
import kotlinx.io.text.writeUtf8String
object BinaryMetaFormat : MetaFormat, MetaFormatFactory {
override val name: Name = super.name + "bin"
override val shortName: String = "bin"
override val key: Short = 0x4249//BI
override fun invoke(meta: Meta, context: Context): MetaFormat = this
@ -25,7 +25,7 @@ object BinaryMetaFormat : MetaFormat, MetaFormatFactory {
private fun Output.writeString(str: String) {
writeInt(str.length)
writeText(str)
writeUtf8String(str)
}
fun Output.writeValue(value: Value) {
@ -93,7 +93,7 @@ object BinaryMetaFormat : MetaFormat, MetaFormatFactory {
private fun Input.readString(): String {
val length = readInt()
return readText(max = length)
return readUtf8String(length)
}
@Suppress("UNCHECKED_CAST")
@ -115,7 +115,7 @@ object BinaryMetaFormat : MetaFormat, MetaFormatFactory {
}
'M' -> {
val length = readInt()
val meta = buildMeta {
val meta = Meta {
(1..length).forEach { _ ->
val name = readString()
val item = readMetaItem()

@ -1,11 +1,12 @@
package hep.dataforge.io
import hep.dataforge.meta.*
import hep.dataforge.meta.Laminate
import hep.dataforge.meta.Meta
import hep.dataforge.meta.get
import hep.dataforge.meta.string
import hep.dataforge.names.asName
import hep.dataforge.names.plus
import kotlinx.io.core.Output
import kotlinx.io.core.buildPacket
import kotlinx.io.core.readBytes
import kotlinx.io.Binary
interface Envelope {
val meta: Meta
@ -21,12 +22,13 @@ interface Envelope {
val ENVELOPE_DATA_TYPE_KEY = ENVELOPE_NODE_KEY + "dataType"
val ENVELOPE_DATA_ID_KEY = ENVELOPE_NODE_KEY + "dataID"
val ENVELOPE_DESCRIPTION_KEY = ENVELOPE_NODE_KEY + "description"
val ENVELOPE_NAME_KEY = ENVELOPE_NODE_KEY + "name"
//const val ENVELOPE_TIME_KEY = "@envelope.time"
/**
* 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)
}
}
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.asName
import hep.dataforge.provider.Type
import kotlinx.io.core.Input
import kotlinx.io.core.Output
import kotlinx.io.Input
import kotlinx.io.Output
import kotlin.reflect.KClass
/**
@ -23,15 +23,19 @@ interface EnvelopeFormat : IOFormat<Envelope> {
fun Input.readPartial(): PartialEnvelope
fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta = EmptyMeta)
fun Output.writeEnvelope(
envelope: Envelope,
metaFormatFactory: MetaFormatFactory = defaultMetaFormat,
formatMeta: Meta = EmptyMeta
)
override fun Input.readObject(): Envelope
override fun Output.writeObject(obj: Envelope): Unit = writeEnvelope(obj, defaultMetaFormat)
override fun Output.writeObject(obj: Envelope): Unit = writeEnvelope(obj)
}
@Type(ENVELOPE_FORMAT_TYPE)
interface EnvelopeFormatFactory : IOFormatFactory<Envelope> {
interface EnvelopeFormatFactory : IOFormatFactory<Envelope>, EnvelopeFormat {
override val name: Name get() = "envelope".asName()
override val type: KClass<out Envelope> get() = Envelope::class

@ -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.provider.Type
import hep.dataforge.values.Value
import kotlinx.io.core.*
import kotlinx.io.*
import kotlinx.io.buffer.Buffer
import kotlinx.io.pool.ObjectPool
import kotlinx.serialization.ImplicitReflectionSerializer
import kotlinx.serialization.KSerializer
import kotlinx.serialization.cbor.Cbor
import kotlinx.serialization.serializer
import kotlin.math.min
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)
fun ObjectPool<IoBuffer>.fill(block: IoBuffer.() -> Unit): IoBuffer {
fun ObjectPool<Buffer>.fill(block: Buffer.() -> Unit): Buffer {
val buffer = borrow()
return try {
buffer.apply(block)
@ -72,41 +68,11 @@ interface IOFormatFactory<T : Any> : Factory<IOFormat<T>>, Named {
}
}
@Deprecated("To be removed in io-2")
inline fun buildPacketWithoutPool(headerSizeHint: Int = 0, block: BytePacketBuilder.() -> Unit): ByteReadPacket {
val builder = BytePacketBuilder(headerSizeHint, IoBuffer.NoPool)
block(builder)
return builder.build()
}
fun <T : Any> IOFormat<T>.writeBytes(obj: T): Bytes = buildBytes { writeObject(obj) }
fun <T : Any> IOFormat<T>.writePacket(obj: T): ByteReadPacket = buildPacket { writeObject(obj) }
fun <T : Any> IOFormat<T>.writeBytes(obj: T): ByteArray = buildPacket { writeObject(obj) }.readBytes()
fun <T : Any> IOFormat<T>.readBytes(array: ByteArray): T {
//= ByteReadPacket(array).readThis()
val byteArrayInput: Input = object : AbstractInput(
IoBuffer.Pool.borrow(),
remaining = array.size.toLong(),
pool = IoBuffer.Pool
) {
var written = 0
override fun closeSource() {
// do nothing
}
override fun fill(): IoBuffer? {
if (array.size - written <= 0) return null
return IoBuffer.Pool.fill {
reserveEndGap(IoBuffer.ReservedSize)
val toWrite = min(capacity, array.size - written)
writeFully(array, written, toWrite)
written += toWrite
}
}
}
return byteArrayInput.readObject()
}
fun <T : Any> IOFormat<T>.writeByteArray(obj: T): ByteArray = buildBytes { writeObject(obj) }.toByteArray()
fun <T : Any> IOFormat<T>.readByteArray(array: ByteArray): T = array.asBinary().read { readObject() }
object DoubleIOFormat : IOFormat<Double>, IOFormatFactory<Double> {
override fun invoke(meta: Meta, context: Context): IOFormat<Double> = this
@ -140,25 +106,10 @@ object ValueIOFormat : IOFormat<Value>, IOFormatFactory<Value> {
}
/**
* Experimental
* Read given binary as object using given format
*/
@ImplicitReflectionSerializer
class SerializerIOFormat<T : Any>(
type: KClass<T>,
val serializer: KSerializer<T> = type.serializer()
) : IOFormat<T> {
//override val name: Name = type.simpleName?.toName() ?: EmptyName
override fun Output.writeObject(obj: T) {
val bytes = Cbor.plain.dump(serializer, obj)
writeFully(bytes)
}
override fun Input.readObject(): T {
//FIXME reads the whole input
val bytes = readBytes()
return Cbor.plain.load(serializer, bytes)
fun <T : Any> Binary.readWith(format: IOFormat<T>): T = format.run {
read {
readObject()
}
}

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

@ -2,171 +2,50 @@
package hep.dataforge.io
import hep.dataforge.context.Context
import hep.dataforge.descriptors.ItemDescriptor
import hep.dataforge.descriptors.NodeDescriptor
import hep.dataforge.descriptors.ValueDescriptor
import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaBase
import hep.dataforge.meta.MetaItem
import hep.dataforge.names.Name
import hep.dataforge.names.NameToken
import hep.dataforge.names.plus
import hep.dataforge.names.toName
import hep.dataforge.values.*
import kotlinx.io.core.Input
import kotlinx.io.core.Output
import kotlinx.io.core.readText
import kotlinx.io.core.writeText
import kotlinx.serialization.json.*
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.set
import hep.dataforge.meta.descriptors.NodeDescriptor
import hep.dataforge.meta.node
import hep.dataforge.meta.toJson
import hep.dataforge.meta.toMetaItem
import kotlinx.io.Input
import kotlinx.io.Output
import kotlinx.io.text.readUtf8String
import kotlinx.io.text.writeUtf8String
import kotlinx.serialization.UnstableDefault
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObjectSerializer
class JsonMetaFormat(private val json: Json = Json.indented) : MetaFormat {
@OptIn(UnstableDefault::class)
class JsonMetaFormat(private val json: Json = DEFAULT_JSON) : MetaFormat {
override fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor?) {
val jsonObject = meta.toJson(descriptor)
writeText(json.stringify(JsonObjectSerializer, jsonObject))
writeUtf8String(json.stringify(JsonObjectSerializer, jsonObject))
}
override fun Input.readMeta(descriptor: NodeDescriptor?): Meta {
val str = readText()
val str = readUtf8String()
val jsonElement = json.parseJson(str)
return jsonElement.toMeta()
val item = jsonElement.toMetaItem(descriptor)
return item.node ?: Meta.EMPTY
}
companion object : MetaFormatFactory {
val default = JsonMetaFormat()
val DEFAULT_JSON = Json { prettyPrint = true }
override fun invoke(meta: Meta, context: Context): MetaFormat = default
override val name: Name = super.name + "json"
override val shortName = "json"
override val key: Short = 0x4a53//"JS"
private val default = JsonMetaFormat()
override fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor?) =
default.run { writeMeta(meta, descriptor) }
override fun Input.readMeta(descriptor: NodeDescriptor?): Meta =
default.run { readMeta(descriptor) }
}
}
/**
* @param descriptor reserved for custom serialization in future
*/
fun Value.toJson(descriptor: ValueDescriptor? = null): JsonElement {
return if (isList()) {
JsonArray(list.map { it.toJson() })
} else {
when (type) {
ValueType.NUMBER -> JsonPrimitive(number)
ValueType.STRING -> JsonPrimitive(string)
ValueType.BOOLEAN -> JsonPrimitive(boolean)
ValueType.NULL -> JsonNull
}
}
}
//Use these methods to customize JSON key mapping
private fun NameToken.toJsonKey(descriptor: ItemDescriptor?) = toString()
private fun NodeDescriptor?.getDescriptor(key: String) = this?.items?.get(key)
fun Meta.toJson(descriptor: NodeDescriptor? = null): JsonObject {
//TODO search for same name siblings and arrange them into arrays
val map = this.items.entries.associate { (name, item) ->
val itemDescriptor = descriptor?.items?.get(name.body)
val key = name.toJsonKey(itemDescriptor)
val value = when (item) {
is MetaItem.ValueItem -> {
item.value.toJson(itemDescriptor as? ValueDescriptor)
}
is MetaItem.NodeItem -> {
item.node.toJson(itemDescriptor as? NodeDescriptor)
}
}
key to value
}
return JsonObject(map)
}
fun JsonElement.toMeta(descriptor: NodeDescriptor? = null): Meta {
return when (val item = toMetaItem(descriptor)) {
is MetaItem.NodeItem<*> -> item.node
is MetaItem.ValueItem ->item.value.toMeta()
}
}
fun JsonPrimitive.toValue(descriptor: ValueDescriptor?): Value {
return when (this) {
JsonNull -> Null
else -> this.content.parseValue() // Optimize number and boolean parsing
}
}
fun JsonElement.toMetaItem(descriptor: ItemDescriptor? = null): MetaItem<JsonMeta> = when (this) {
is JsonPrimitive -> {
val value = this.toValue(descriptor as? ValueDescriptor)
MetaItem.ValueItem(value)
}
is JsonObject -> {
val meta = JsonMeta(this, descriptor as? NodeDescriptor)
MetaItem.NodeItem(meta)
}
is JsonArray -> {
if (this.all { it is JsonPrimitive }) {
val value = if (isEmpty()) {
Null
} else {
ListValue(
map<JsonElement, Value> {
//We already checked that all values are primitives
(it as JsonPrimitive).toValue(descriptor as? ValueDescriptor)
}
)
}
MetaItem.ValueItem(value)
} else {
json {
"@value" to this@toMetaItem
}.toMetaItem(descriptor)
}
}
}
class JsonMeta(val json: JsonObject, val descriptor: NodeDescriptor? = null) : MetaBase() {
@Suppress("UNCHECKED_CAST")
private operator fun MutableMap<String, MetaItem<JsonMeta>>.set(key: String, value: JsonElement): Unit {
val itemDescriptor = descriptor.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
import hep.dataforge.context.Context
import hep.dataforge.descriptors.NodeDescriptor
import hep.dataforge.meta.descriptors.NodeDescriptor
import hep.dataforge.io.MetaFormatFactory.Companion.META_FORMAT_TYPE
import hep.dataforge.meta.Meta
import hep.dataforge.names.Name
import hep.dataforge.names.asName
import hep.dataforge.names.plus
import hep.dataforge.provider.Type
import kotlinx.io.core.*
import kotlinx.io.*
import kotlin.reflect.KClass
/**
@ -27,12 +28,14 @@ interface MetaFormat : IOFormat<Meta> {
}
@Type(META_FORMAT_TYPE)
interface MetaFormatFactory : IOFormatFactory<Meta> {
override val name: Name get() = "meta".asName()
interface MetaFormatFactory : IOFormatFactory<Meta>, MetaFormat {
val shortName: String
override val name: Name get() = "meta".asName() + shortName
override val type: KClass<out Meta> get() = Meta::class
val key: Short
val key: Short get() = name.hashCode().toShort()
override operator fun invoke(meta: Meta, context: Context): MetaFormat
@ -41,24 +44,16 @@ interface MetaFormatFactory : IOFormatFactory<Meta> {
}
}
fun Meta.toString(format: MetaFormat): String = buildPacket {
fun Meta.toString(format: MetaFormat): String = buildBytes {
format.run { writeObject(this@toString) }
}.readText()
}.toByteArray().decodeToString()
fun Meta.toString(formatFactory: MetaFormatFactory): String = toString(formatFactory())
fun Meta.toBytes(format: MetaFormat = JsonMetaFormat.default): ByteReadPacket = buildPacket {
format.run { writeObject(this@toBytes) }
}
fun MetaFormat.parse(str: String): Meta {
return buildPacket { writeText(str) }.readObject()
return str.encodeToByteArray().read { readObject() }
}
fun MetaFormatFactory.parse(str: String): Meta = invoke().parse(str)
fun MetaFormat.fromBytes(packet: ByteReadPacket): Meta {
return packet.readObject()
}
fun MetaFormatFactory.parse(str: String, formatMeta: Meta): Meta = invoke(formatMeta).parse(str)

@ -7,43 +7,51 @@ import hep.dataforge.meta.string
import hep.dataforge.names.Name
import hep.dataforge.names.plus
import hep.dataforge.names.toName
import kotlinx.io.charsets.Charsets
import kotlinx.io.core.*
import kotlinx.io.*
import kotlinx.io.text.readRawString
import kotlinx.io.text.writeRawString
@ExperimentalUnsignedTypes
@ExperimentalIoApi
class TaggedEnvelopeFormat(
val io: IOPlugin,
val version: VERSION = TaggedEnvelopeFormat.VERSION.DF02
val version: VERSION = VERSION.DF02
) : EnvelopeFormat {
// private val metaFormat = io.metaFormat(metaFormatKey)
// ?: error("Meta format with key $metaFormatKey could not be resolved in $io")
private fun Tag.toBytes(): ByteReadPacket = buildPacket(24) {
writeText(START_SEQUENCE)
writeText(version.name)
private fun Tag.toBytes() = buildBytes(24) {
writeRawString(START_SEQUENCE)
writeRawString(version.name)
writeShort(metaFormatKey)
writeUInt(metaSize)
when (version) {
TaggedEnvelopeFormat.VERSION.DF02 -> {
VERSION.DF02 -> {
writeUInt(dataSize.toUInt())
}
TaggedEnvelopeFormat.VERSION.DF03 -> {
VERSION.DF03 -> {
writeULong(dataSize)
}
}
writeText(END_SEQUENCE)
writeRawString(END_SEQUENCE)
}
override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) {
val metaFormat = metaFormatFactory.invoke(formatMeta, io.context)
val metaBytes = metaFormat.writeBytes(envelope.meta)
val tag = Tag(metaFormatFactory.key, metaBytes.size.toUInt() + 2u, envelope.data?.size ?: 0.toULong())
writePacket(tag.toBytes())
writeFully(metaBytes)
writeText("\r\n")
envelope.data?.read { copyTo(this@writeEnvelope) }
val actualSize: ULong = if (envelope.data == null) {
0
} else {
envelope.data?.size ?: Binary.INFINITE
}.toULong()
val tag = Tag(metaFormatFactory.key, metaBytes.size.toUInt() + 2u, actualSize)
writeBinary(tag.toBytes())
writeBinary(metaBytes)
writeRawString("\r\n")
envelope.data?.let {
writeBinary(it)
}
flush()
}
@ -59,11 +67,15 @@ class TaggedEnvelopeFormat(
val metaFormat = io.metaFormat(tag.metaFormatKey)
?: error("Meta format with key ${tag.metaFormatKey} not found")
val metaPacket = ByteReadPacket(readBytes(tag.metaSize.toInt()))
val dataBytes = readBytes(tag.dataSize.toInt())
val meta: Meta = limit(tag.metaSize.toInt()).run {
metaFormat.run {
readObject()
}
}
val meta = metaFormat.run { metaPacket.readObject() }
return SimpleEnvelope(meta, ArrayBinary(dataBytes))
val data = ByteArray(tag.dataSize.toInt()).also { readArray(it) }.asBinary()
return SimpleEnvelope(meta, data)
}
override fun Input.readPartial(): PartialEnvelope {
@ -72,8 +84,11 @@ class TaggedEnvelopeFormat(
val metaFormat = io.metaFormat(tag.metaFormatKey)
?: error("Meta format with key ${tag.metaFormatKey} not found")
val metaPacket = ByteReadPacket(readBytes(tag.metaSize.toInt()))
val meta = metaFormat.run { metaPacket.readObject() }
val meta: Meta = limit(tag.metaSize.toInt()).run {
metaFormat.run {
readObject()
}
}
return PartialEnvelope(meta, version.tagSize + tag.metaSize, tag.dataSize)
}
@ -99,16 +114,16 @@ class TaggedEnvelopeFormat(
val io = context.io
val metaFormatName = meta["name"].string?.toName() ?: JsonMetaFormat.name
val metaFormatFactory = io.metaFormatFactories.find { it.name == metaFormatName }
?: error("Meta format could not be resolved")
//Check if appropriate factory exists
io.metaFormatFactories.find { it.name == metaFormatName } ?: error("Meta format could not be resolved")
return TaggedEnvelopeFormat(io)
}
private fun Input.readTag(version: VERSION): Tag {
val start = readTextExactBytes(2, charset = Charsets.ISO_8859_1)
val start = readRawString(2)
if (start != START_SEQUENCE) error("The input is not an envelope")
val versionString = readTextExactBytes(4, charset = Charsets.ISO_8859_1)
val versionString = readRawString(4)
if (version.name != versionString) error("Wrong version of DataForge: expected $version but found $versionString")
val metaFormatKey = readShort()
val metaLength = readUInt()
@ -116,14 +131,14 @@ class TaggedEnvelopeFormat(
VERSION.DF02 -> readUInt().toULong()
VERSION.DF03 -> readULong()
}
val end = readTextExactBytes(4, charset = Charsets.ISO_8859_1)
val end = readRawString(4)
if (end != END_SEQUENCE) error("The input is not an envelope")
return Tag(metaFormatKey, metaLength, dataLength)
}
override fun peekFormat(io: IOPlugin, input: Input): EnvelopeFormat? {
return try {
val header = input.readTextExactBytes(6)
val header = input.readRawString(6)
when (header.substring(2..5)) {
VERSION.DF02.name -> TaggedEnvelopeFormat(io, VERSION.DF02)
VERSION.DF03.name -> TaggedEnvelopeFormat(io, VERSION.DF03)
@ -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.meta.*
import hep.dataforge.names.asName
import kotlinx.io.core.*
import kotlinx.serialization.toUtf8Bytes
import kotlinx.io.*
import kotlinx.io.text.readRawString
import kotlinx.io.text.readUtf8Line
import kotlinx.io.text.writeRawString
import kotlinx.io.text.writeUtf8String
@ExperimentalIoApi
class TaglessEnvelopeFormat(
val io: IOPlugin,
meta: Meta = EmptyMeta
@ -15,40 +19,46 @@ class TaglessEnvelopeFormat(
private val dataStart = meta[DATA_START_PROPERTY].string ?: DEFAULT_DATA_START
private fun Output.writeProperty(key: String, value: Any) {
writeText("#? $key: $value;\r\n")
writeUtf8String("#? $key: $value;\r\n")
}
override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) {
val metaFormat = metaFormatFactory(formatMeta, io.context)
//printing header
writeText(TAGLESS_ENVELOPE_HEADER + "\r\n")
writeRawString(TAGLESS_ENVELOPE_HEADER + "\r\n")
//printing all properties
writeProperty(META_TYPE_PROPERTY, metaFormatFactory.type)
writeProperty(META_TYPE_PROPERTY, metaFormatFactory.shortName)
//TODO add optional metaFormat properties
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
if (!envelope.meta.isEmpty()) {
val metaBytes = metaFormat.writeBytes(envelope.meta)
writeProperty(META_LENGTH_PROPERTY, metaBytes.size)
writeText(metaStart + "\r\n")
writeFully(metaBytes)
writeText("\r\n")
writeProperty(META_LENGTH_PROPERTY, metaBytes.size + 2)
writeUtf8String(metaStart + "\r\n")
writeBinary(metaBytes)
writeRawString("\r\n")
}
//Printing data
envelope.data?.let { data ->
writeText(dataStart + "\r\n")
writeFully(data.toBytes())
writeUtf8String(dataStart + "\r\n")
writeBinary(data)
}
}
override fun Input.readObject(): Envelope {
var line: String = ""
var line: String
do {
line = readUTF8Line() ?: error("Input does not contain tagless envelope header")
line = readUtf8Line() // ?: error("Input does not contain tagless envelope header")
} while (!line.startsWith(TAGLESS_ENVELOPE_HEADER))
val properties = HashMap<String, String>()
@ -60,19 +70,20 @@ class TaglessEnvelopeFormat(
val (key, value) = match.destructured
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
if (line.startsWith(metaStart)) {
val metaFormat = properties[META_TYPE_PROPERTY]?.let { io.metaFormat(it) } ?: JsonMetaFormat.default
val metaSize = properties.get(META_LENGTH_PROPERTY)?.toInt()
val metaFormat = properties[META_TYPE_PROPERTY]?.let { io.metaFormat(it) } ?: JsonMetaFormat
val metaSize = properties[META_LENGTH_PROPERTY]?.toInt()
meta = if (metaSize != null) {
val metaPacket = buildPacket {
writeFully(readBytes(metaSize))
limit(metaSize).run {
metaFormat.run { readObject() }
}
metaFormat.run { metaPacket.readObject() }
} else {
metaFormat.run {
readObject()
@ -81,17 +92,22 @@ class TaglessEnvelopeFormat(
}
do {
line = readUTF8Line() ?: return SimpleEnvelope(meta, null)
//returning an Envelope without data if end of input is reached
try {
line = readUtf8Line()
} catch (ex: EOFException) {
//returning an Envelope without data if end of input is reached
return SimpleEnvelope(meta, null)
}
} while (!line.startsWith(dataStart))
val data: Binary? = if (properties.containsKey(DATA_LENGTH_PROPERTY)) {
val bytes = ByteArray(properties[DATA_LENGTH_PROPERTY]!!.toInt())
readFully(bytes)
readArray(bytes)
bytes.asBinary()
} else {
val bytes = readBytes()
bytes.asBinary()
ArrayBinary.write {
writeInput(this@readObject)
}
}
return SimpleEnvelope(meta, data)
@ -99,10 +115,10 @@ class TaglessEnvelopeFormat(
override fun Input.readPartial(): PartialEnvelope {
var offset = 0u
var line: String = ""
var line: String
do {
line = readUTF8Line() ?: error("Input does not contain tagless envelope header")
offset += line.toUtf8Bytes().size.toUInt()
line = readUtf8Line()// ?: error("Input does not contain tagless envelope header")
offset += line.encodeToByteArray().size.toUInt()
} while (!line.startsWith(TAGLESS_ENVELOPE_HEADER))
val properties = HashMap<String, String>()
@ -114,30 +130,32 @@ class TaglessEnvelopeFormat(
val (key, value) = match.destructured
properties[key] = value
}
line = readUTF8Line() ?: return PartialEnvelope(Meta.empty, offset.toUInt(), 0.toULong())
offset += line.toUtf8Bytes().size.toUInt()
try {
line = readUtf8Line()
offset += line.encodeToByteArray().size.toUInt()
} catch (ex: EOFException) {
return PartialEnvelope(Meta.EMPTY, offset.toUInt(), 0.toULong())
}
}
var meta: Meta = EmptyMeta
if (line.startsWith(metaStart)) {
val metaFormat = properties[META_TYPE_PROPERTY]?.let { io.metaFormat(it) } ?: JsonMetaFormat.default
val metaSize = properties.get(META_LENGTH_PROPERTY)?.toInt()
val metaFormat = properties[META_TYPE_PROPERTY]?.let { io.metaFormat(it) } ?: JsonMetaFormat
val metaSize = properties[META_LENGTH_PROPERTY]?.toInt()
meta = if (metaSize != null) {
val metaPacket = buildPacket {
writeFully(readBytes(metaSize))
}
offset += metaSize.toUInt()
metaFormat.run { metaPacket.readObject() }
limit(metaSize).run {
metaFormat.run { readObject() }
}
} else {
error("Can't partially read an envelope with undefined meta size")
}
}
do {
line = readUTF8Line() ?: return PartialEnvelope(Meta.empty, offset.toUInt(), 0.toULong())
offset += line.toUtf8Bytes().size.toUInt()
line = readUtf8Line() //?: return PartialEnvelope(Meta.EMPTY, offset.toUInt(), 0.toULong())
offset += line.encodeToByteArray().size.toUInt()
//returning an Envelope without data if end of input is reached
} while (!line.startsWith(dataStart))
@ -170,13 +188,21 @@ class TaglessEnvelopeFormat(
return TaglessEnvelopeFormat(context.io, meta)
}
val default by lazy { invoke() }
private val default by lazy { invoke() }
override fun Input.readPartial(): PartialEnvelope =
default.run { readPartial() }
override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) =
default.run { writeEnvelope(envelope, metaFormatFactory, formatMeta) }
override fun Input.readObject(): Envelope =
default.run { readObject() }
override fun peekFormat(io: IOPlugin, input: Input): EnvelopeFormat? {
return try {
val buffer = ByteArray(TAGLESS_ENVELOPE_HEADER.length)
input.readFully(buffer)
return if (buffer.toString() == TAGLESS_ENVELOPE_HEADER) {
val string = input.readRawString(TAGLESS_ENVELOPE_HEADER.length)
return if (string == TAGLESS_ENVELOPE_HEADER) {
TaglessEnvelopeFormat(io)
} else {
null

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

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

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

@ -1,16 +1,26 @@
package hep.dataforge.io
import hep.dataforge.meta.*
import kotlinx.io.Bytes
import kotlinx.io.buildBytes
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.json
import kotlinx.serialization.json.jsonArray
import kotlin.test.Test
import kotlin.test.assertEquals
fun Meta.toBytes(format: MetaFormat = JsonMetaFormat): Bytes = buildBytes {
format.run { writeObject(this@toBytes) }
}
fun MetaFormat.fromBytes(packet: Bytes): Meta {
return packet.read { readObject() }
}
class MetaFormatTest {
@Test
fun testBinaryMetaFormat() {
val meta = buildMeta {
val meta = Meta {
"a" put 22
"node" put {
"b" put "DDD"
@ -25,7 +35,7 @@ class MetaFormatTest {
@Test
fun testJsonMetaFormat() {
val meta = buildMeta {
val meta = Meta {
"a" put 22
"node" put {
"b" put "DDD"

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

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

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

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

@ -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
import hep.dataforge.context.Global
import kotlinx.io.asBinary
import kotlinx.io.toByteArray
import kotlinx.io.writeDouble
import java.nio.file.Files
import kotlin.test.Test
import kotlin.test.assertEquals
@ -21,11 +24,11 @@ class FileBinaryTest {
@Test
fun testSize() {
val binary = envelope.data
assertEquals(binary?.size?.toInt(), binary?.toBytes()?.size)
assertEquals(binary?.size?.toInt(), binary?.toByteArray()?.size)
}
@Test
fun testFileData(){
fun testFileData() {
val dataFile = Files.createTempFile("dataforge_test_bin", ".bin")
dataFile.toFile().writeText("This is my binary")
val envelopeFromFile = Envelope {
@ -34,12 +37,12 @@ class FileBinaryTest {
"b" put 22.2
}
dataType = "hep.dataforge.satellite"
dataID = "cellDepositTest" // добавил только что
dataID = "cellDepositTest"
data = dataFile.asBinary()
}
val binary = envelopeFromFile.data!!
println(binary.toBytes().size)
assertEquals(binary.size.toInt(), binary.toBytes().size)
println(binary.toByteArray().size)
assertEquals(binary.size.toInt(), binary.toByteArray().size)
}
@ -49,8 +52,7 @@ class FileBinaryTest {
val tmpPath = Files.createTempFile("dataforge_test", ".df")
Global.io.writeEnvelopeFile(tmpPath, envelope)
val binary = Global.io.readEnvelopeFile(tmpPath).data!!
assertEquals(binary.size.toInt(), binary.toBytes().size)
val binary = Global.io.readEnvelopeFile(tmpPath)?.data!!
assertEquals(binary.size.toInt(), binary.toByteArray().size)
}
}

@ -1,6 +1,7 @@
package hep.dataforge.io
import hep.dataforge.context.Global
import kotlinx.io.writeDouble
import java.nio.file.Files
import kotlin.test.Test
import kotlin.test.assertTrue
@ -22,10 +23,23 @@ class FileEnvelopeTest {
@Test
fun testFileWriteRead() {
val tmpPath = Files.createTempFile("dataforge_test", ".df")
Global.io.writeEnvelopeFile(tmpPath,envelope)
println(tmpPath.toUri())
val restored: Envelope = Global.io.readEnvelopeFile(tmpPath)
assertTrue { envelope.contentEquals(restored) }
Global.io.run {
val tmpPath = Files.createTempFile("dataforge_test", ".df")
writeEnvelopeFile(tmpPath, envelope)
println(tmpPath.toUri())
val restored: Envelope = readEnvelopeFile(tmpPath)!!
assertTrue { envelope.contentEquals(restored) }
}
}
@Test
fun testFileWriteReadTagless() {
Global.io.run {
val tmpPath = Files.createTempFile("dataforge_test_tagless", ".df")
writeEnvelopeFile(tmpPath, envelope, 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.Responder
import hep.dataforge.io.TaggedEnvelopeFormat
import hep.dataforge.io.writeBytes
import hep.dataforge.io.writeByteArray
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.runBlocking
import kotlinx.io.writeDouble
import org.junit.AfterClass
import org.junit.BeforeClass
import org.junit.Test
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.time.ExperimentalTime
@ExperimentalStdlibApi
object EchoResponder : Responder {
override suspend fun respond(request: Envelope): Envelope {
val string = TaggedEnvelopeFormat().run { writeBytes(request).decodeToString() }
val string = TaggedEnvelopeFormat().run { writeByteArray(request).decodeToString() }
println("ECHO:")
println(string)
return request
@ -43,7 +44,7 @@ class EnvelopeServerTest {
}
}
@Test
@Test(timeout = 1000)
fun doEchoTest() {
val request = Envelope.invoke {
type = "test.echo"

@ -1,5 +1,9 @@
import scientifik.serialization
plugins {
id("scientifik.mpp")
}
serialization()
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.asName
import hep.dataforge.names.plus
import kotlinx.serialization.*
//TODO add validator to configuration
@ -12,10 +13,16 @@ data class MetaListener(
val action: (name: Name, oldItem: MetaItem<*>?, newItem: MetaItem<*>?) -> Unit
)
interface ObservableMeta : Meta {
fun onChange(owner: Any?, action: (Name, MetaItem<*>?, MetaItem<*>?) -> Unit)
fun removeListener(owner: Any?)
}
/**
* Mutable meta representing object state
*/
class Config : AbstractMutableMeta<Config>() {
@Serializable
class Config : AbstractMutableMeta<Config>(), ObservableMeta {
private val listeners = HashSet<MetaListener>()
@ -26,21 +33,21 @@ class Config : AbstractMutableMeta<Config>() {
/**
* Add change listener to this meta. Owner is declared to be able to remove listeners later. Listener without owner could not be removed
*/
fun onChange(owner: Any?, action: (Name, MetaItem<*>?, MetaItem<*>?) -> Unit) {
override fun onChange(owner: Any?, action: (Name, MetaItem<*>?, MetaItem<*>?) -> Unit) {
listeners.add(MetaListener(owner, action))
}
/**
* Remove all listeners belonging to given owner
*/
fun removeListener(owner: Any?) {
override fun removeListener(owner: Any?) {
listeners.removeAll { it.owner === owner }
}
override fun replaceItem(key: NameToken, oldItem: MetaItem<Config>?, newItem: MetaItem<Config>?) {
if (newItem == null) {
_items.remove(key)
if(oldItem!= null && oldItem is MetaItem.NodeItem<Config>) {
if (oldItem != null && oldItem is MetaItem.NodeItem<Config>) {
oldItem.node.removeListener(this)
}
} else {
@ -57,33 +64,34 @@ class Config : AbstractMutableMeta<Config>() {
/**
* Attach configuration node instead of creating one
*/
override fun wrapNode(meta: Meta): Config = meta.toConfig()
override fun wrapNode(meta: Meta): Config = meta.asConfig()
override fun empty(): Config = Config()
companion object {
@Serializer(Config::class)
companion object ConfigSerializer : KSerializer<Config> {
fun empty(): Config = Config()
override val descriptor: SerialDescriptor get() = MetaSerializer.descriptor
override fun deserialize(decoder: Decoder): Config {
return MetaSerializer.deserialize(decoder).asConfig()
}
override fun serialize(encoder: Encoder, value: Config) {
MetaSerializer.serialize(encoder, value)
}
}
}
operator fun Config.get(token: NameToken): MetaItem<Config>? = items[token]
fun Meta.toConfig(): Config = this as? Config ?: Config().also { builder ->
fun Meta.asConfig(): Config = this as? Config ?: Config().also { builder ->
this.items.mapValues { entry ->
val item = entry.value
builder[entry.key.asName()] = when (item) {
is MetaItem.ValueItem -> item.value
is MetaItem.NodeItem -> MetaItem.NodeItem(item.node.toConfig())
is MetaItem.NodeItem -> MetaItem.NodeItem(item.node.asConfig())
}
}
}
interface Configurable {
val config: Config
}
fun <T : Configurable> T.configure(meta: Meta): T = this.apply { config.update(meta) }
fun <T : Configurable> T.configure(action: MetaBuilder.() -> Unit): T = configure(buildMeta(action))
open class SimpleConfigurable(override val config: Config) : Configurable
}

@ -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
import hep.dataforge.names.Name
import hep.dataforge.names.NameToken
/**
* A meta laminate consisting of multiple immutable meta layers. For mutable front layer, use [Styled].
* A meta laminate consisting of multiple immutable meta layers. For mutable front layer, use [Scheme].
* If [layers] list contains a [Laminate] it is flat-mapped.
*/
class Laminate(layers: List<Meta>) : MetaBase() {
@ -17,10 +19,11 @@ class Laminate(layers: List<Meta>) : MetaBase() {
constructor(vararg layers: Meta?) : this(layers.filterNotNull())
override val items: Map<NameToken, MetaItem<Meta>>
get() = layers.map { it.items.keys }.flatten().associateWith { key ->
override val items: Map<NameToken, MetaItem<Meta>> by lazy {
layers.map { it.items.keys }.flatten().associateWith { key ->
layers.asSequence().map { it.items[key] }.filterNotNull().let(replaceRule)
}
}
/**
* Generate sealed meta using [mergeRule]
@ -61,7 +64,7 @@ class Laminate(layers: List<Meta>) : MetaBase() {
}
else -> map {
when (it) {
is MetaItem.ValueItem -> MetaItem.NodeItem(buildMeta { Meta.VALUE_KEY put it.value })
is MetaItem.ValueItem -> MetaItem.NodeItem(Meta { Meta.VALUE_KEY put it.value })
is MetaItem.NodeItem -> it
}
}.merge()
@ -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
*/

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

@ -15,6 +15,10 @@ class MetaBuilder : AbstractMutableMeta<MetaBuilder>() {
override fun wrapNode(meta: Meta): MetaBuilder = if (meta is MetaBuilder) meta else meta.builder()
override fun empty(): MetaBuilder = MetaBuilder()
infix fun String.put(item: MetaItem<*>?) {
set(this, item)
}
infix fun String.put(value: Value?) {
set(this, value)
}
@ -141,9 +145,11 @@ fun Meta.edit(builder: MetaBuilder.() -> Unit): MetaBuilder = builder().apply(bu
/**
* Build a [MetaBuilder] using given transformation
*/
@Deprecated("To be replaced with fake constructor", ReplaceWith("Meta"))
fun buildMeta(builder: MetaBuilder.() -> Unit): MetaBuilder = MetaBuilder().apply(builder)
/**
* Build 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
import hep.dataforge.meta.scheme.Configurable
import hep.dataforge.names.*
import hep.dataforge.values.Value
interface MutableMeta<M : MutableMeta<M>> : MetaNode<M> {
interface MutableMeta<out M : MutableMeta<M>> : MetaNode<M> {
override val items: Map<NameToken, MetaItem<M>>
operator fun set(name: Name, item: MetaItem<*>?)
// fun onChange(owner: Any? = null, action: (Name, MetaItem<*>?, MetaItem<*>?) -> Unit)
@ -54,13 +55,14 @@ abstract class AbstractMutableMeta<M : MutableMeta<M>> : AbstractMetaNode<M>(),
0 -> error("Can't setValue meta item for empty name")
1 -> {
val token = name.first()!!
replaceItem(token, get(name), wrapItem(item))
@Suppress("UNCHECKED_CAST") val oldItem: MetaItem<M>? = get(name) as? MetaItem<M>
replaceItem(token, oldItem, wrapItem(item))
}
else -> {
val token = name.first()!!
//get existing or create new node. Query is ignored for new node
if(items[token] == null){
replaceItem(token,null, MetaItem.NodeItem(empty()))
if (items[token] == null) {
replaceItem(token, null, MetaItem.NodeItem(empty()))
}
items[token]?.node!![name.cutFirst()] = item
}
@ -71,6 +73,7 @@ abstract class AbstractMutableMeta<M : MutableMeta<M>> : AbstractMetaNode<M>(),
@Suppress("NOTHING_TO_INLINE")
inline fun MutableMeta<*>.remove(name: Name) = set(name, null)
@Suppress("NOTHING_TO_INLINE")
inline fun MutableMeta<*>.remove(name: String) = remove(name.toName())
@ -103,7 +106,7 @@ operator fun MutableMeta<*>.set(name: Name, value: Any?) {
null -> remove(name)
is MetaItem<*> -> setItem(name, value)
is Meta -> setNode(name, value)
is Specific -> setNode(name, value.config)
is Configurable -> setNode(name, value.config)
else -> setValue(name, Value.of(value))
}
}
@ -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, index: String, value: Any?) = set(key.toName().withIndex(index), value)
/**
* Update existing mutable node with another node. The rules are following:
* * 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
*/
fun MutableMeta<*>.append(name: Name, value: Any?) {
fun <M : MutableMeta<M>> M.append(name: Name, value: Any?) {
require(!name.isEmpty()) { "Name could not be empty for append operation" }
val newIndex = name.last()!!.index
if (newIndex.isNotEmpty()) {
@ -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
annotation class DFBuilder
@Experimental(level = Experimental.Level.WARNING)
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
@Retention(AnnotationRetention.BINARY)
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.scheme.*
import hep.dataforge.names.Name
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.True
import hep.dataforge.values.Value
import hep.dataforge.values.ValueType
sealed class ItemDescriptor(override val config: Config) : Specific {
/**
* The name of this item
*
* @return
*/
var name: String by string { error("Anonymous descriptors are not allowed") }
sealed class ItemDescriptor : Scheme() {
/**
* True if same name siblings with this name are allowed
@ -36,7 +32,7 @@ sealed class ItemDescriptor(override val config: Config) : Specific {
*
* @return
*/
var attributes by node()
var attributes by config()
/**
* True if the item is required
@ -46,13 +42,33 @@ sealed class ItemDescriptor(override val config: Config) : Specific {
abstract var required: Boolean
}
/**
* Configure attributes of the descriptor
*/
fun ItemDescriptor.attributes(block: Config.() -> Unit) {
(attributes ?: Config().also { this.config = it }).apply(block)
}
/**
* Check if given item suits the descriptor
*/
fun ItemDescriptor.validateItem(item: MetaItem<*>?): Boolean {
return when (this) {
is ValueDescriptor -> isAllowedValue(item.value ?: return false)
is NodeDescriptor -> items.all { (key, d) ->
d.validateItem(item.node[key])
}
}
}
/**
* Descriptor for meta node. Could contain additional information for viewing
* and editing.
*
* @author Alexander Nozik
*/
class NodeDescriptor(config: Config) : ItemDescriptor(config) {
@DFBuilder
class NodeDescriptor : ItemDescriptor() {
/**
* True if the node is required
@ -66,65 +82,103 @@ class NodeDescriptor(config: Config) : ItemDescriptor(config) {
*
* @return
*/
var default: Config? 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))
}
var default by node()
/**
* The map of children node descriptors
*/
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"))
}
fun node(name: String, descriptor: NodeDescriptor) {
/**
* Define a child item descriptor for this node
*/
fun defineItem(name: String, descriptor: ItemDescriptor) {
if (items.keys.contains(name)) error("The key $name already exists in descriptor")
val token = NameToken(NODE_KEY, name)
val token = when (descriptor) {
is NodeDescriptor -> NameToken(NODE_KEY, name)
is ValueDescriptor -> NameToken(VALUE_KEY, name)
}
config[token] = descriptor.config
}
fun node(name: String, block: NodeDescriptor.() -> Unit) {
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
//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 NODE_KEY = "node"
const val VALUE_KEY = "value"
override fun wrap(config: Config): NodeDescriptor = NodeDescriptor(config)
//override fun wrap(config: Config): NodeDescriptor = NodeDescriptor(config)
//TODO infer descriptor from spec
}
}
/**
* Get a descriptor item associated with given name or null if item for given name not provided
*/
operator fun ItemDescriptor.get(name: Name): ItemDescriptor? {
if (name.isEmpty()) return this
return when (this) {
is ValueDescriptor -> null // empty name already checked
is NodeDescriptor -> items[name.first()!!.toString()]?.get(name.cutFirst())
}
}
/**
* A descriptor for meta value
@ -133,7 +187,7 @@ class NodeDescriptor(config: Config) : ItemDescriptor(config) {
*
* @author Alexander Nozik
*/
class ValueDescriptor(config: Config) : ItemDescriptor(config) {
class ValueDescriptor : ItemDescriptor() {
/**
@ -159,8 +213,8 @@ class ValueDescriptor(config: Config) : ItemDescriptor(config) {
*
* @return
*/
var type: List<ValueType> by value {
it?.list?.map { v -> ValueType.valueOf(v.string) } ?: emptyList()
var type: List<ValueType> by item {
it?.value?.list?.map { v -> ValueType.valueOf(v.string) } ?: emptyList()
}
fun type(vararg t: ValueType) {
@ -201,16 +255,11 @@ class ValueDescriptor(config: Config) : ItemDescriptor(config) {
this.allowedValues = v.map { Value.of(it) }
}
companion object : Specification<ValueDescriptor> {
override fun wrap(config: Config): ValueDescriptor = ValueDescriptor(config)
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) }
}
companion object : SchemeSpec<ValueDescriptor>(::ValueDescriptor) {
// inline fun <reified E : Enum<E>> enum(name: String) = ValueDescriptor {
// type(ValueType.STRING)
// this.allowedValues = enumValues<E>().map { Value.of(it.name) }
// }
// /**
// * Build a value descriptor from annotation
@ -270,4 +319,4 @@ class ValueDescriptor(config: Config) : ItemDescriptor(config) {
// return ValueDescriptor(Laminate(primary.meta, secondary.meta))
// }
}
}
}

@ -1,16 +1,8 @@
package hep.dataforge.meta
import hep.dataforge.descriptors.NodeDescriptor
import hep.dataforge.meta.descriptors.NodeDescriptor
import hep.dataforge.values.Value
///**
// * Find all elements with given body
// */
//private fun Meta.byBody(body: String): Map<String, MetaItem<*>> =
// items.filter { it.key.body == body }.mapKeys { it.key.index }
//
//private fun Meta.distinctNames() = items.keys.map { it.body }.distinct()
/**
* Convert meta to map of maps
*/
@ -28,7 +20,7 @@ fun Meta.toMap(descriptor: NodeDescriptor? = null): Map<String, Any?> {
* Convert map of maps to meta
*/
@DFExperimental
fun Map<String, Any?>.toMeta(descriptor: NodeDescriptor? = null): Meta = buildMeta {
fun Map<String, Any?>.toMeta(descriptor: NodeDescriptor? = null): Meta = Meta {
entries.forEach { (key, value) ->
@Suppress("UNCHECKED_CAST")
when (value) {

@ -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
fun Meta.getIndexed(name: String): Map<String, MetaItem<*>> = this@getIndexed.getIndexed(name.toName())
/**
* Get all items matching given name.
*/
@Suppress("UNCHECKED_CAST")
@DFExperimental
fun <M : MetaNode<M>> M.getIndexed(name: Name): Map<String, MetaItem<M>> {
val root: MetaNode<M>? = when (name.length) {
0 -> error("Can't use empty name for that")
1 -> this
else -> (this[name.cutLast()] as? MetaItem.NodeItem<M>)?.node
}
val (body, index) = name.last()!!
val regex = index.toRegex()
return root?.items
?.filter { it.key.body == body && (index.isEmpty() || regex.matches(it.key.index)) }
?.mapKeys { it.key.index }
?: emptyMap()
}
fun <M : MetaNode<M>> M.getIndexed(name: Name): Map<String, MetaItem<M>> =
(this as Meta).getIndexed(name) as Map<String, MetaItem<M>>
@DFExperimental
fun <M : MetaNode<M>> M.getIndexed(name: String): Map<String, MetaItem<M>> = getIndexed(name.toName())

@ -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
/**
@ -8,7 +9,7 @@ import hep.dataforge.names.Name
interface TransformationRule {
/**
* Check if this transformation
* Check if this transformation should be applied to a node with given name and value
*/
fun matches(name: Name, item: MetaItem<*>?): Boolean
@ -29,7 +30,8 @@ interface TransformationRule {
/**
* A transformation which keeps all elements, matching [selector] unchanged.
*/
data class KeepTransformationRule(val selector: (Name) -> Boolean) : TransformationRule {
data class KeepTransformationRule(val selector: (Name) -> Boolean) :
TransformationRule {
override fun matches(name: Name, item: MetaItem<*>?): Boolean {
return selector(name)
}
@ -87,25 +89,27 @@ inline class MetaTransformation(val transformations: Collection<TransformationRu
/**
* Produce new meta using only those items that match transformation rules
*/
fun transform(source: Meta): Meta = buildMeta {
transformations.forEach { rule ->
rule.selectItems(source).forEach { name ->
rule.transformItem(name, source[name], this)
fun transform(source: Meta): Meta =
Meta {
transformations.forEach { rule ->
rule.selectItems(source).forEach { name ->
rule.transformItem(name, source[name], this)
}
}
}
}
/**
* Transform a meta, replacing all elements found in rules with transformed entries
*/
fun apply(source: Meta): Meta = buildMeta(source) {
transformations.forEach { rule ->
rule.selectItems(source).forEach { name ->
remove(name)
rule.transformItem(name, source[name], this)
fun apply(source: Meta): Meta =
source.edit {
transformations.forEach { rule ->
rule.selectItems(source).forEach { name ->
remove(name)
rule.transformItem(name, source[name], this)
}
}
}
}
/**
* Listens for changes in the source node and translates them into second node if transformation set contains a corresponding rule.
@ -150,9 +154,10 @@ class MetaTransformationBuilder {
* Keep nodes by regex
*/
fun keep(regex: String) {
transformations.add(RegexItemTransformationRule(regex.toRegex()) { name, _, metaItem ->
setItem(name, metaItem)
})
transformations.add(
RegexItemTransformationRule(regex.toRegex()) { name, _, metaItem ->
setItem(name, metaItem)
})
}
/**

@ -1,11 +1,14 @@
package hep.dataforge.names
import kotlinx.serialization.*
/**
* The general interface for working with names.
* The name is a dot separated list of strings like `token1.token2.token3`.
* Each token could contain additional index in square brackets.
*/
@Serializable
class Name(val tokens: List<NameToken>) {
val length get() = tokens.size
@ -50,9 +53,21 @@ class Name(val tokens: List<NameToken>) {
}
}
companion object {
@Serializer(Name::class)
companion object : KSerializer<Name> {
const val NAME_SEPARATOR = "."
val EMPTY = Name(emptyList())
override val descriptor: SerialDescriptor = PrimitiveDescriptor("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: `{}.:\`.
* A name token could have appendix in square brackets called *index*
*/
@Serializable
data class NameToken(val body: String, val index: String = "") {
init {
@ -80,6 +96,19 @@ data class NameToken(val body: String, val index: String = "") {
}
fun hasIndex() = index.isNotEmpty()
@Serializer(NameToken::class)
companion object : KSerializer<NameToken> {
override val descriptor: SerialDescriptor = PrimitiveDescriptor("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.
*/
fun String.toName(): Name {
if (isBlank()) return EmptyName
if (isBlank()) return Name.EMPTY
val tokens = sequence {
var bodyBuilder = 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.
* The input string could contain dots and braces, but they are just escaped, not parsed.
*/
fun String.asName(): Name = if (isBlank()) EmptyName else NameToken(this).asName()
fun String.asName(): Name = if (isBlank()) Name.EMPTY else NameToken(this).asName()
operator fun NameToken.plus(other: Name): Name = Name(listOf(this) + other.tokens)
@ -153,8 +182,6 @@ fun Name.appendLeft(other: String): Name = NameToken(other) + this
fun NameToken.asName() = Name(listOf(this))
val EmptyName = Name(emptyList())
fun Name.isEmpty(): Boolean = this.length == 0
/**
@ -182,6 +209,8 @@ fun Name.startsWith(token: NameToken): Boolean = first() == token
fun Name.endsWith(token: NameToken): Boolean = last() == token
fun Name.startsWith(name: Name): Boolean = tokens.subList(0, name.length) == name.tokens
fun Name.startsWith(name: Name): Boolean =
this.length >= name.length && tokens.subList(0, name.length) == name.tokens
fun Name.endsWith(name: Name): Boolean = tokens.subList(length - name.length, length) == name.tokens
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
import kotlinx.serialization.Serializable
/**
* The list of supported Value types.
@ -7,6 +9,7 @@ package hep.dataforge.values
* Time value and binary value are represented by string
*
*/
@Serializable
enum class ValueType {
NUMBER, STRING, BOOLEAN, NULL
}
@ -41,7 +44,7 @@ interface Value {
* get this value represented as List
*/
val list: List<Value>
get() = if(this == Null) emptyList() else listOf(this)
get() = if (this == Null) emptyList() else listOf(this)
override fun equals(other: Any?): Boolean
@ -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 <E : Enum<E>> E.asValue(): Value = EnumValue(this)
/**
* 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
import hep.dataforge.meta.Meta
import hep.dataforge.meta.buildMeta
/**
* Check if value is null
@ -22,6 +21,7 @@ val Value.boolean
val Value.int get() = number.toInt()
val Value.double get() = number.toDouble()
val Value.float get() = number.toFloat()
val Value.short get() = number.toShort()
val Value.long get() = number.toLong()
val Value.stringList: List<String> get() = list.map { it.string }
@ -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
import hep.dataforge.meta.scheme.*
import kotlin.test.Test
import kotlin.test.assertEquals
@ -10,26 +11,29 @@ class MetaDelegateTest {
NO
}
class InnerSpec : Scheme() {
var innerValue by string()
companion object : SchemeSpec<InnerSpec>(::InnerSpec)
}
class TestScheme : Scheme() {
var myValue by string()
var safeValue by double(2.2)
var enumValue by enum(TestEnum.YES) { enum<TestEnum>() }
var inner by spec(InnerSpec)
companion object : SchemeSpec<TestScheme>(::TestScheme)
}
@Test
fun delegateTest() {
class InnerSpec(override val config: Config) : Specific {
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)
}
val testObject = TestScheme.empty()
testObject.config["myValue"] = "theString"
testObject.enumValue = TestEnum.NO
testObject.inner = innerSpec.build { innerValue = "ddd"}
testObject.inner = InnerSpec { innerValue = "ddd" }
assertEquals("theString", testObject.myValue)
assertEquals(TestEnum.NO, testObject.enumValue)

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

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

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

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

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

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

@ -3,7 +3,8 @@ package hep.dataforge.output.html
import hep.dataforge.context.Context
import hep.dataforge.meta.Meta
import hep.dataforge.output.Output
import hep.dataforge.output.TextRenderer
import hep.dataforge.output.Renderer
import hep.dataforge.output.TextFormat
import hep.dataforge.output.html.HtmlBuilder.Companion.HTML_CONVERTER_TYPE
import hep.dataforge.provider.Type
import hep.dataforge.provider.top
@ -14,11 +15,11 @@ import kotlinx.html.p
import kotlin.reflect.KClass
class HtmlOutput<T : Any>(override val context: Context, private val consumer: TagConsumer<*>) : Output<T> {
class HtmlRenderer<T : Any>(override val context: Context, private val consumer: TagConsumer<*>) : Renderer<T> {
private val cache = HashMap<KClass<*>, HtmlBuilder<*>>()
/**
* Find the first [TextRenderer] matching the given object type.
* Find the first [TextFormat] matching the given object type.
*/
override fun render(obj: T, meta: Meta) {
@ -47,7 +48,7 @@ class HtmlOutput<T : Any>(override val context: Context, private val consumer: T
}
/**
* A text or binary renderer based on [kotlinx.io.core.Output]
* A text or binary renderer based on [Renderer]
*/
@Type(HTML_CONVERTER_TYPE)
interface HtmlBuilder<T : Any> {

@ -1,13 +1,9 @@
package hep.dataforge.output
import hep.dataforge.context.AbstractPlugin
import hep.dataforge.context.Context
import hep.dataforge.context.PluginFactory
import hep.dataforge.context.PluginTag
import hep.dataforge.context.*
import hep.dataforge.context.PluginTag.Companion.DATAFORGE_GROUP
import hep.dataforge.meta.EmptyMeta
import hep.dataforge.meta.Meta
import hep.dataforge.names.EmptyName
import hep.dataforge.names.Name
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
@ -22,14 +18,14 @@ interface OutputManager {
* Get an output specialized for given type, name and stage.
* @param stage represents the node or directory for the output. Empty means root node.
* @param name represents the name inside the node.
* @param meta configuration for [Output] (not for rendered object)
* @param meta configuration for [Renderer] (not for rendered object)
*/
operator fun <T : Any> get(
type: KClass<out T>,
name: Name,
stage: Name = EmptyName,
stage: Name = Name.EMPTY,
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(
name: Name,
stage: Name = EmptyName,
stage: Name = Name.EMPTY,
meta: Meta = EmptyMeta
): Output<T> {
): Renderer<T> {
return get(T::class, name, stage, meta)
}
/**
* 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)
/**
* System console output.
* The [ConsoleOutput] is used when no other [OutputManager] is provided.
* The [CONSOLE_RENDERER] is used when no other [OutputManager] is provided.
*/
expect val ConsoleOutput: Output<Any>
val CONSOLE_RENDERER: Renderer<Any> = object : Renderer<Any> {
override fun render(obj: Any, meta: Meta) {
println(obj)
}
override val context: Context get() = Global
}
class ConsoleOutputManager : AbstractPlugin(), OutputManager {
override val tag: PluginTag get() = ConsoleOutputManager.tag
override fun <T : Any> get(type: KClass<out T>, name: Name, stage: Name, meta: Meta): Output<T> = ConsoleOutput
override fun <T : Any> get(type: KClass<out T>, name: Name, stage: Name, meta: Meta): Renderer<T> = CONSOLE_RENDERER
companion object : PluginFactory<ConsoleOutputManager> {
override val tag = PluginTag("output.console", group = DATAFORGE_GROUP)

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