Merge pull request #41 from mipt-npm/dev
0.1.5
This commit is contained in:
commit
7dde03ee68
.github/workflows
README.mdbuild.gradle.ktsdataforge-context
dataforge-data/src
commonMain/kotlin/hep/dataforge/data
jvmMain/kotlin/hep/dataforge/data
dataforge-io
build.gradle.kts
dataforge-io-yaml
src
commonMain/kotlin/hep/dataforge/io
Binary.ktBinaryMetaFormat.ktEnvelope.ktEnvelopeBuilder.ktEnvelopeFormat.ktEnvelopeParts.ktIOFormat.ktIOPlugin.ktJsonMetaFormat.ktMetaFormat.ktTaggedEnvelopeFormat.ktTaglessEnvelopeFormat.kt
functions
serialization
commonTest/kotlin/hep/dataforge/io
jvmMain/kotlin/hep/dataforge/io
jvmTest/kotlin/hep/dataforge/io
dataforge-meta
build.gradle.kts
src
commonMain/kotlin/hep/dataforge
descriptors
meta
Config.ktJsonMeta.ktLaminate.ktMeta.ktMetaBuilder.ktMetaDelegate.ktMetaSerializer.ktMutableMeta.ktMutableMetaDelegate.ktSpecific.ktStyled.ktannotations.ktconfigDelegates.kt
descriptors
mapMeta.ktmetaDelegates.ktmetaMatcher.ktscheme
serializationUtils.kttransformations
names
values
commonTest/kotlin/hep/dataforge/meta
dataforge-output
dataforge-output-html
src/commonMain/kotlin/hep/dataforge/output
2
.github/workflows/gradle.yml
vendored
2
.github/workflows/gradle.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: Java CI
|
||||
name: Gradle build
|
||||
|
||||
on: [push]
|
||||
|
||||
|
@ -1,6 +1,12 @@
|
||||
|
||||
[](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
|
||||
[](https://zenodo.org/badge/latestdoi/148831678)
|
||||
|
||||

|
||||
|
||||
[  ](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 = "")
|
65
dataforge-context/src/jvmMain/kotlin/hep/dataforge/descriptors/reflectiveDescriptors.kt
Normal file
65
dataforge-context/src/jvmMain/kotlin/hep/dataforge/descriptors/reflectiveDescriptors.kt
Normal file
@ -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")
|
||||
}
|
||||
|
61
dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt
61
dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt
@ -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())
|
||||
}
|
||||
|
||||
|
193
dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt
Normal file
193
dataforge-io/src/jvmMain/kotlin/hep/dataforge/io/fileIO.kt
Normal file
@ -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) }
|
||||
// }
|
||||
// }
|
28
dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/DescriptorMeta.kt
Normal file
28
dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/DescriptorMeta.kt
Normal file
@ -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) }
|
236
dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt
Normal file
236
dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt
Normal file
@ -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)
|
||||
}
|
75
dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaCaster.kt
Normal file
75
dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/transformations/MetaCaster.kt
Normal file
@ -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
Loading…
x
Reference in New Issue
Block a user