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]
|
on: [push]
|
||||||
|
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
|
[](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
|
||||||
[](https://zenodo.org/badge/latestdoi/148831678)
|
[](https://zenodo.org/badge/latestdoi/148831678)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
[  ](https://bintray.com/mipt-npm/dataforge/dataforge-meta/_latestVersion)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Questions and Answers #
|
# Questions and Answers #
|
||||||
|
|
||||||
In this section we will try to cover DataForge main ideas in the form of questions and answers.
|
In this section we will try to cover DataForge main ideas in the form of questions and answers.
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("scientifik.mpp") version "0.2.1" apply false
|
val toolsVersion = "0.4.0"
|
||||||
id("scientifik.jvm") version "0.2.1" apply false
|
id("scientifik.mpp") version toolsVersion apply false
|
||||||
id("scientifik.publish") version "0.2.1" apply false
|
id("scientifik.jvm") version toolsVersion apply false
|
||||||
|
id("scientifik.publish") version toolsVersion apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
val dataforgeVersion by extra("0.1.4")
|
val dataforgeVersion by extra("0.1.5")
|
||||||
|
|
||||||
val bintrayRepo by extra("dataforge")
|
val bintrayRepo by extra("dataforge")
|
||||||
val githubProject by extra("dataforge-core")
|
val githubProject by extra("dataforge-core")
|
||||||
@ -12,10 +14,12 @@ val githubProject by extra("dataforge-core")
|
|||||||
allprojects {
|
allprojects {
|
||||||
group = "hep.dataforge"
|
group = "hep.dataforge"
|
||||||
version = dataforgeVersion
|
version = dataforgeVersion
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
if (name.startsWith("dataforge")) {
|
|
||||||
apply(plugin = "scientifik.publish")
|
apply(plugin = "scientifik.publish")
|
||||||
}
|
}
|
||||||
}
|
|
@ -1,10 +1,12 @@
|
|||||||
|
import scientifik.coroutines
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("scientifik.mpp")
|
id("scientifik.mpp")
|
||||||
}
|
}
|
||||||
|
|
||||||
description = "Context and provider definitions"
|
description = "Context and provider definitions"
|
||||||
|
|
||||||
val coroutinesVersion: String = Scientifik.coroutinesVersion
|
coroutines()
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
@ -12,21 +14,18 @@ kotlin {
|
|||||||
dependencies {
|
dependencies {
|
||||||
api(project(":dataforge-meta"))
|
api(project(":dataforge-meta"))
|
||||||
api(kotlin("reflect"))
|
api(kotlin("reflect"))
|
||||||
api("io.github.microutils:kotlin-logging-common:1.7.2")
|
api("io.github.microutils:kotlin-logging-common:1.7.8")
|
||||||
api("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$coroutinesVersion")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val jvmMain by getting {
|
val jvmMain by getting {
|
||||||
dependencies {
|
dependencies {
|
||||||
api("io.github.microutils:kotlin-logging:1.7.2")
|
api("io.github.microutils:kotlin-logging:1.7.8")
|
||||||
api("ch.qos.logback:logback-classic:1.2.3")
|
api("ch.qos.logback:logback-classic:1.2.3")
|
||||||
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val jsMain by getting {
|
val jsMain by getting {
|
||||||
dependencies {
|
dependencies {
|
||||||
api("io.github.microutils:kotlin-logging-js:1.7.2")
|
api("io.github.microutils:kotlin-logging-js:1.7.8")
|
||||||
api("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$coroutinesVersion")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,7 +103,7 @@ open class Context(
|
|||||||
plugins.forEach { it.detach() }
|
plugins.forEach { it.detach() }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toMeta(): Meta = buildMeta {
|
override fun toMeta(): Meta = Meta {
|
||||||
"parent" to parent?.name
|
"parent" to parent?.name
|
||||||
"properties" put properties.seal()
|
"properties" put properties.seal()
|
||||||
"plugins" put plugins.map { it.toMeta() }
|
"plugins" put plugins.map { it.toMeta() }
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package hep.dataforge.context
|
package hep.dataforge.context
|
||||||
|
|
||||||
|
import hep.dataforge.meta.DFBuilder
|
||||||
|
import hep.dataforge.meta.Meta
|
||||||
import hep.dataforge.meta.MetaBuilder
|
import hep.dataforge.meta.MetaBuilder
|
||||||
import hep.dataforge.meta.buildMeta
|
import hep.dataforge.meta.buildMeta
|
||||||
import hep.dataforge.names.toName
|
import hep.dataforge.names.toName
|
||||||
@ -7,6 +9,7 @@ import hep.dataforge.names.toName
|
|||||||
/**
|
/**
|
||||||
* A convenience builder for context
|
* A convenience builder for context
|
||||||
*/
|
*/
|
||||||
|
@DFBuilder
|
||||||
class ContextBuilder(var name: String = "@anonymous", val parent: Context = Global) {
|
class ContextBuilder(var name: String = "@anonymous", val parent: Context = Global) {
|
||||||
private val plugins = ArrayList<Plugin>()
|
private val plugins = ArrayList<Plugin>()
|
||||||
private var meta = MetaBuilder()
|
private var meta = MetaBuilder()
|
||||||
@ -20,11 +23,11 @@ class ContextBuilder(var name: String = "@anonymous", val parent: Context = Glob
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun plugin(tag: PluginTag, action: MetaBuilder.() -> Unit = {}) {
|
fun plugin(tag: PluginTag, action: MetaBuilder.() -> Unit = {}) {
|
||||||
plugins.add(PluginRepository.fetch(tag, buildMeta(action)))
|
plugins.add(PluginRepository.fetch(tag, Meta(action)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun plugin(builder: PluginFactory<*>, action: MetaBuilder.() -> Unit = {}) {
|
fun plugin(builder: PluginFactory<*>, action: MetaBuilder.() -> Unit = {}) {
|
||||||
plugins.add(builder.invoke(buildMeta(action)))
|
plugins.add(builder.invoke(Meta(action)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun plugin(name: String, group: String = "", version: String = "", action: MetaBuilder.() -> Unit = {}) {
|
fun plugin(name: String, group: String = "", version: String = "", action: MetaBuilder.() -> Unit = {}) {
|
||||||
|
@ -65,7 +65,7 @@ interface Plugin : Named, ContextAware, Provider, MetaRepr {
|
|||||||
*/
|
*/
|
||||||
fun detach()
|
fun detach()
|
||||||
|
|
||||||
override fun toMeta(): Meta = buildMeta {
|
override fun toMeta(): Meta = Meta {
|
||||||
"context" put context.name.toString()
|
"context" put context.name.toString()
|
||||||
"type" to this::class.simpleName
|
"type" to this::class.simpleName
|
||||||
"tag" put tag
|
"tag" put tag
|
||||||
|
@ -62,7 +62,7 @@ class PluginManager(override val context: Context) : ContextAware, Iterable<Plug
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
operator fun <T : Any> get(type: KClass<T>, tag: PluginTag? = null, recursive: Boolean = true): T? =
|
operator fun <T : Any> get(type: KClass<out T>, tag: PluginTag? = null, recursive: Boolean = true): T? =
|
||||||
find(recursive) { type.isInstance(it) && (tag == null || tag.matches(it.tag)) } as T?
|
find(recursive) { type.isInstance(it) && (tag == null || tag.matches(it.tag)) } as T?
|
||||||
|
|
||||||
inline operator fun <reified T : Any> get(tag: PluginTag? = null, recursive: Boolean = true): T? =
|
inline operator fun <reified T : Any> get(tag: PluginTag? = null, recursive: Boolean = true): T? =
|
||||||
@ -102,7 +102,7 @@ class PluginManager(override val context: Context) : ContextAware, Iterable<Plug
|
|||||||
load(factory(meta, context))
|
load(factory(meta, context))
|
||||||
|
|
||||||
fun <T : Plugin> load(factory: PluginFactory<T>, metaBuilder: MetaBuilder.() -> Unit): T =
|
fun <T : Plugin> load(factory: PluginFactory<T>, metaBuilder: MetaBuilder.() -> Unit): T =
|
||||||
load(factory, buildMeta(metaBuilder))
|
load(factory, Meta(metaBuilder))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a plugin from [PluginManager]
|
* Remove a plugin from [PluginManager]
|
||||||
@ -134,7 +134,7 @@ class PluginManager(override val context: Context) : ContextAware, Iterable<Plug
|
|||||||
factory: PluginFactory<T>,
|
factory: PluginFactory<T>,
|
||||||
recursive: Boolean = true,
|
recursive: Boolean = true,
|
||||||
metaBuilder: MetaBuilder.() -> Unit
|
metaBuilder: MetaBuilder.() -> Unit
|
||||||
): T = fetch(factory, recursive, buildMeta(metaBuilder))
|
): T = fetch(factory, recursive, Meta(metaBuilder))
|
||||||
|
|
||||||
override fun iterator(): Iterator<Plugin> = plugins.iterator()
|
override fun iterator(): Iterator<Plugin> = plugins.iterator()
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ data class PluginTag(
|
|||||||
|
|
||||||
override fun toString(): String = listOf(group, name, version).joinToString(separator = ":")
|
override fun toString(): String = listOf(group, name, version).joinToString(separator = ":")
|
||||||
|
|
||||||
override fun toMeta(): Meta = buildMeta {
|
override fun toMeta(): Meta = Meta {
|
||||||
"name" put name
|
"name" put name
|
||||||
"group" put group
|
"group" put group
|
||||||
"version" put version
|
"version" put version
|
||||||
|
@ -0,0 +1,130 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 Alexander Nozik.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package hep.dataforge.descriptors
|
||||||
|
|
||||||
|
import hep.dataforge.meta.DFExperimental
|
||||||
|
import hep.dataforge.values.ValueType
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
@MustBeDocumented
|
||||||
|
annotation class Attribute(
|
||||||
|
val key: String,
|
||||||
|
val value: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@MustBeDocumented
|
||||||
|
annotation class Attributes(
|
||||||
|
val attrs: Array<Attribute>
|
||||||
|
)
|
||||||
|
|
||||||
|
@MustBeDocumented
|
||||||
|
annotation class ItemDef(
|
||||||
|
val info: String = "",
|
||||||
|
val multiple: Boolean = false,
|
||||||
|
val required: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
@Target(AnnotationTarget.PROPERTY)
|
||||||
|
@MustBeDocumented
|
||||||
|
annotation class ValueDef(
|
||||||
|
val type: Array<ValueType> = [ValueType.STRING],
|
||||||
|
val def: String = "",
|
||||||
|
val allowed: Array<String> = [],
|
||||||
|
val enumeration: KClass<*> = Any::class
|
||||||
|
)
|
||||||
|
|
||||||
|
///**
|
||||||
|
// * Description text for meta property, node or whole object
|
||||||
|
// */
|
||||||
|
//@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
|
||||||
|
//@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
//@MustBeDocumented
|
||||||
|
//annotation class Description(val value: String)
|
||||||
|
//
|
||||||
|
///**
|
||||||
|
// * Annotation for value property which states that lists are expected
|
||||||
|
// */
|
||||||
|
//@Target(AnnotationTarget.PROPERTY)
|
||||||
|
//@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
//@MustBeDocumented
|
||||||
|
//annotation class Multiple
|
||||||
|
//
|
||||||
|
///**
|
||||||
|
// * Descriptor target
|
||||||
|
// * The DataForge path to the resource containing the description. Following targets are supported:
|
||||||
|
// * 1. resource
|
||||||
|
// * 1. file
|
||||||
|
// * 1. class
|
||||||
|
// * 1. method
|
||||||
|
// * 1. property
|
||||||
|
// *
|
||||||
|
// *
|
||||||
|
// * Does not work if [type] is provided
|
||||||
|
// */
|
||||||
|
//@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER)
|
||||||
|
//@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
//@MustBeDocumented
|
||||||
|
//annotation class Descriptor(val value: String)
|
||||||
|
//
|
||||||
|
//
|
||||||
|
///**
|
||||||
|
// * Aggregator class for descriptor nodes
|
||||||
|
// */
|
||||||
|
//@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER)
|
||||||
|
//@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
//@MustBeDocumented
|
||||||
|
//annotation class DescriptorNodes(vararg val nodes: NodeDef)
|
||||||
|
//
|
||||||
|
///**
|
||||||
|
// * Aggregator class for descriptor values
|
||||||
|
// */
|
||||||
|
//@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER)
|
||||||
|
//@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
//@MustBeDocumented
|
||||||
|
//annotation class DescriptorValues(vararg val nodes: ValueDef)
|
||||||
|
//
|
||||||
|
///**
|
||||||
|
// * Alternative name for property descriptor declaration
|
||||||
|
// */
|
||||||
|
//@Target(AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER)
|
||||||
|
//@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
//@MustBeDocumented
|
||||||
|
//annotation class DescriptorName(val name: String)
|
||||||
|
//
|
||||||
|
//@Target(AnnotationTarget.PROPERTY)
|
||||||
|
//@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
//@MustBeDocumented
|
||||||
|
//annotation class DescriptorValue(val def: ValueDef)
|
||||||
|
////TODO enter fields directly?
|
||||||
|
//
|
||||||
|
//@Target(AnnotationTarget.PROPERTY)
|
||||||
|
//@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
//@MustBeDocumented
|
||||||
|
//annotation class ValueProperty(
|
||||||
|
// val name: String = "",
|
||||||
|
// val type: Array<ValueType> = arrayOf(ValueType.STRING),
|
||||||
|
// val multiple: Boolean = false,
|
||||||
|
// val def: String = "",
|
||||||
|
// val enumeration: KClass<*> = Any::class,
|
||||||
|
// val tags: Array<String> = emptyArray()
|
||||||
|
//)
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//@Target(AnnotationTarget.PROPERTY)
|
||||||
|
//@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
//@MustBeDocumented
|
||||||
|
//annotation class NodeProperty(val name: String = "")
|
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
|
val meta: Meta
|
||||||
|
|
||||||
override fun toMeta(): Meta = buildMeta {
|
override fun toMeta(): Meta = Meta {
|
||||||
"type" put (type.simpleName?:"undefined")
|
"type" put (type.simpleName?:"undefined")
|
||||||
if(!meta.isEmpty()) {
|
if(!meta.isEmpty()) {
|
||||||
"meta" put meta
|
"meta" put meta
|
||||||
@ -91,7 +91,7 @@ fun <T : Any, R : Any> Data<T>.map(
|
|||||||
meta: Meta = this.meta,
|
meta: Meta = this.meta,
|
||||||
block: suspend CoroutineScope.(T) -> R
|
block: suspend CoroutineScope.(T) -> R
|
||||||
): Data<R> = DynamicData(outputType, meta, coroutineContext, listOf(this)) {
|
): Data<R> = DynamicData(outputType, meta, coroutineContext, listOf(this)) {
|
||||||
block(await(this))
|
block(await())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -103,7 +103,7 @@ inline fun <T : Any, reified R : Any> Data<T>.map(
|
|||||||
meta: Meta = this.meta,
|
meta: Meta = this.meta,
|
||||||
noinline block: suspend CoroutineScope.(T) -> R
|
noinline block: suspend CoroutineScope.(T) -> R
|
||||||
): Data<R> = DynamicData(R::class, meta, coroutineContext, listOf(this)) {
|
): Data<R> = DynamicData(R::class, meta, coroutineContext, listOf(this)) {
|
||||||
block(await(this))
|
block(await())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -119,7 +119,7 @@ inline fun <T : Any, reified R : Any> Collection<Data<T>>.reduce(
|
|||||||
coroutineContext,
|
coroutineContext,
|
||||||
this
|
this
|
||||||
) {
|
) {
|
||||||
block(map { run { it.await(this) } })
|
block(map { run { it.await() } })
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <K, T : Any, R : Any> Map<K, Data<T>>.reduce(
|
fun <K, T : Any, R : Any> Map<K, Data<T>>.reduce(
|
||||||
@ -133,7 +133,7 @@ fun <K, T : Any, R : Any> Map<K, Data<T>>.reduce(
|
|||||||
coroutineContext,
|
coroutineContext,
|
||||||
this.values
|
this.values
|
||||||
) {
|
) {
|
||||||
block(mapValues { it.value.await(this) })
|
block(mapValues { it.value.await() })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -153,7 +153,7 @@ inline fun <K, T : Any, reified R : Any> Map<K, Data<T>>.reduce(
|
|||||||
coroutineContext,
|
coroutineContext,
|
||||||
this.values
|
this.values
|
||||||
) {
|
) {
|
||||||
block(mapValues { it.value.await(this) })
|
block(mapValues { it.value.await() })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
package hep.dataforge.data
|
package hep.dataforge.data
|
||||||
|
|
||||||
import hep.dataforge.meta.*
|
import hep.dataforge.meta.*
|
||||||
|
import hep.dataforge.meta.scheme.Scheme
|
||||||
|
import hep.dataforge.meta.scheme.SchemeSpec
|
||||||
|
import hep.dataforge.meta.scheme.string
|
||||||
import hep.dataforge.names.toName
|
import hep.dataforge.names.toName
|
||||||
|
|
||||||
|
|
||||||
class DataFilter(override val config: Config) : Specific {
|
class DataFilter : Scheme() {
|
||||||
/**
|
/**
|
||||||
* A source node for the filter
|
* A source node for the filter
|
||||||
*/
|
*/
|
||||||
@ -22,9 +25,7 @@ class DataFilter(override val config: Config) : Specific {
|
|||||||
|
|
||||||
fun isEmpty(): Boolean = config.isEmpty()
|
fun isEmpty(): Boolean = config.isEmpty()
|
||||||
|
|
||||||
companion object : Specification<DataFilter> {
|
companion object : SchemeSpec<DataFilter>(::DataFilter)
|
||||||
override fun wrap(config: Config): DataFilter = DataFilter(config)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -54,4 +55,4 @@ fun <T : Any> DataNode<T>.filter(filter: Meta): DataNode<T> = filter(DataFilter.
|
|||||||
* Filter data using [DataFilter] builder
|
* Filter data using [DataFilter] builder
|
||||||
*/
|
*/
|
||||||
fun <T : Any> DataNode<T>.filter(filterBuilder: DataFilter.() -> Unit): DataNode<T> =
|
fun <T : Any> DataNode<T>.filter(filterBuilder: DataFilter.() -> Unit): DataNode<T> =
|
||||||
filter(DataFilter.build(filterBuilder))
|
filter(DataFilter.invoke(filterBuilder))
|
@ -4,6 +4,7 @@ import hep.dataforge.meta.*
|
|||||||
import hep.dataforge.names.*
|
import hep.dataforge.names.*
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.collections.component1
|
import kotlin.collections.component1
|
||||||
import kotlin.collections.component2
|
import kotlin.collections.component2
|
||||||
@ -13,16 +14,22 @@ import kotlin.reflect.KClass
|
|||||||
sealed class DataItem<out T : Any> : MetaRepr {
|
sealed class DataItem<out T : Any> : MetaRepr {
|
||||||
abstract val type: KClass<out T>
|
abstract val type: KClass<out T>
|
||||||
|
|
||||||
class Node<out T : Any>(val value: DataNode<T>) : DataItem<T>() {
|
abstract val meta: Meta
|
||||||
override val type: KClass<out T> get() = value.type
|
|
||||||
|
|
||||||
override fun toMeta(): Meta = value.toMeta()
|
class Node<out T : Any>(val node: DataNode<T>) : DataItem<T>() {
|
||||||
|
override val type: KClass<out T> get() = node.type
|
||||||
|
|
||||||
|
override fun toMeta(): Meta = node.toMeta()
|
||||||
|
|
||||||
|
override val meta: Meta get() = node.meta
|
||||||
}
|
}
|
||||||
|
|
||||||
class Leaf<out T : Any>(val value: Data<T>) : DataItem<T>() {
|
class Leaf<out T : Any>(val data: Data<T>) : DataItem<T>() {
|
||||||
override val type: KClass<out T> get() = value.type
|
override val type: KClass<out T> get() = data.type
|
||||||
|
|
||||||
override fun toMeta(): Meta = value.toMeta()
|
override fun toMeta(): Meta = data.toMeta()
|
||||||
|
|
||||||
|
override val meta: Meta get() = data.meta
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,7 +45,9 @@ interface DataNode<out T : Any> : MetaRepr {
|
|||||||
|
|
||||||
val items: Map<NameToken, DataItem<T>>
|
val items: Map<NameToken, DataItem<T>>
|
||||||
|
|
||||||
override fun toMeta(): Meta = buildMeta {
|
val meta: Meta
|
||||||
|
|
||||||
|
override fun toMeta(): Meta = Meta {
|
||||||
"type" put (type.simpleName ?: "undefined")
|
"type" put (type.simpleName ?: "undefined")
|
||||||
"items" put {
|
"items" put {
|
||||||
this@DataNode.items.forEach {
|
this@DataNode.items.forEach {
|
||||||
@ -47,6 +56,19 @@ interface DataNode<out T : Any> : MetaRepr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start computation for all goals in data node and return a job for the whole node
|
||||||
|
*/
|
||||||
|
@Suppress("DeferredResultUnused")
|
||||||
|
fun CoroutineScope.startAll(): Job = launch {
|
||||||
|
items.values.forEach {
|
||||||
|
when (it) {
|
||||||
|
is DataItem.Node<*> -> it.node.run { startAll() }
|
||||||
|
is DataItem.Leaf<*> -> it.data.run { startAsync() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TYPE = "dataNode"
|
const val TYPE = "dataNode"
|
||||||
|
|
||||||
@ -60,28 +82,10 @@ interface DataNode<out T : Any> : MetaRepr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val <T : Any> DataItem<T>?.node: DataNode<T>? get() = (this as? DataItem.Node<T>)?.value
|
suspend fun <T: Any> DataNode<T>.join(): Unit = coroutineScope { startAll().join() }
|
||||||
val <T : Any> DataItem<T>?.data: Data<T>? get() = (this as? DataItem.Leaf<T>)?.value
|
|
||||||
|
|
||||||
/**
|
val <T : Any> DataItem<T>?.node: DataNode<T>? get() = (this as? DataItem.Node<T>)?.node
|
||||||
* Start computation for all goals in data node
|
val <T : Any> DataItem<T>?.data: Data<T>? get() = (this as? DataItem.Leaf<T>)?.data
|
||||||
*/
|
|
||||||
fun DataNode<*>.startAll(scope: CoroutineScope): Unit = items.values.forEach {
|
|
||||||
when (it) {
|
|
||||||
is DataItem.Node<*> -> it.value.startAll(scope)
|
|
||||||
is DataItem.Leaf<*> -> it.value.start(scope)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun DataNode<*>.joinAll(scope: CoroutineScope): Job = scope.launch {
|
|
||||||
startAll(scope)
|
|
||||||
items.forEach {
|
|
||||||
when (val value = it.value) {
|
|
||||||
is DataItem.Node -> value.value.joinAll(this).join()
|
|
||||||
is DataItem.Leaf -> value.value.await(scope)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun <T : Any> DataNode<T>.get(name: Name): DataItem<T>? = when (name.length) {
|
operator fun <T : Any> DataNode<T>.get(name: Name): DataItem<T>? = when (name.length) {
|
||||||
0 -> error("Empty name")
|
0 -> error("Empty name")
|
||||||
@ -98,7 +102,7 @@ fun <T : Any> DataNode<T>.asSequence(): Sequence<Pair<Name, DataItem<T>>> = sequ
|
|||||||
items.forEach { (head, item) ->
|
items.forEach { (head, item) ->
|
||||||
yield(head.asName() to item)
|
yield(head.asName() to item)
|
||||||
if (item is DataItem.Node) {
|
if (item is DataItem.Node) {
|
||||||
val subSequence = item.value.asSequence()
|
val subSequence = item.node.asSequence()
|
||||||
.map { (name, data) -> (head.asName() + name) to data }
|
.map { (name, data) -> (head.asName() + name) to data }
|
||||||
yieldAll(subSequence)
|
yieldAll(subSequence)
|
||||||
}
|
}
|
||||||
@ -111,9 +115,9 @@ fun <T : Any> DataNode<T>.asSequence(): Sequence<Pair<Name, DataItem<T>>> = sequ
|
|||||||
fun <T : Any> DataNode<T>.dataSequence(): Sequence<Pair<Name, Data<T>>> = sequence {
|
fun <T : Any> DataNode<T>.dataSequence(): Sequence<Pair<Name, Data<T>>> = sequence {
|
||||||
items.forEach { (head, item) ->
|
items.forEach { (head, item) ->
|
||||||
when (item) {
|
when (item) {
|
||||||
is DataItem.Leaf -> yield(head.asName() to item.value)
|
is DataItem.Leaf -> yield(head.asName() to item.data)
|
||||||
is DataItem.Node -> {
|
is DataItem.Node -> {
|
||||||
val subSequence = item.value.dataSequence()
|
val subSequence = item.node.dataSequence()
|
||||||
.map { (name, data) -> (head.asName() + name) to data }
|
.map { (name, data) -> (head.asName() + name) to data }
|
||||||
yieldAll(subSequence)
|
yieldAll(subSequence)
|
||||||
}
|
}
|
||||||
@ -125,12 +129,9 @@ operator fun <T : Any> DataNode<T>.iterator(): Iterator<Pair<Name, DataItem<T>>>
|
|||||||
|
|
||||||
class DataTree<out T : Any> internal constructor(
|
class DataTree<out T : Any> internal constructor(
|
||||||
override val type: KClass<out T>,
|
override val type: KClass<out T>,
|
||||||
override val items: Map<NameToken, DataItem<T>>
|
override val items: Map<NameToken, DataItem<T>>,
|
||||||
) : DataNode<T> {
|
override val meta: Meta
|
||||||
override fun toString(): String {
|
) : DataNode<T>
|
||||||
return super.toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class DataTreeBuilderItem<out T : Any> {
|
private sealed class DataTreeBuilderItem<out T : Any> {
|
||||||
class Node<T : Any>(val tree: DataTreeBuilder<T>) : DataTreeBuilderItem<T>()
|
class Node<T : Any>(val tree: DataTreeBuilder<T>) : DataTreeBuilderItem<T>()
|
||||||
@ -144,6 +145,8 @@ private sealed class DataTreeBuilderItem<out T : Any> {
|
|||||||
class DataTreeBuilder<T : Any>(val type: KClass<out T>) {
|
class DataTreeBuilder<T : Any>(val type: KClass<out T>) {
|
||||||
private val map = HashMap<NameToken, DataTreeBuilderItem<T>>()
|
private val map = HashMap<NameToken, DataTreeBuilderItem<T>>()
|
||||||
|
|
||||||
|
private var meta = MetaBuilder()
|
||||||
|
|
||||||
operator fun set(token: NameToken, node: DataTreeBuilder<out T>) {
|
operator fun set(token: NameToken, node: DataTreeBuilder<out T>) {
|
||||||
if (map.containsKey(token)) error("Tree entry with name $token is not empty")
|
if (map.containsKey(token)) error("Tree entry with name $token is not empty")
|
||||||
map[token] = DataTreeBuilderItem.Node(node)
|
map[token] = DataTreeBuilderItem.Node(node)
|
||||||
@ -189,8 +192,8 @@ class DataTreeBuilder<T : Any>(val type: KClass<out T>) {
|
|||||||
operator fun set(name: Name, node: DataNode<T>) = set(name, node.builder())
|
operator fun set(name: Name, node: DataNode<T>) = set(name, node.builder())
|
||||||
|
|
||||||
operator fun set(name: Name, item: DataItem<T>) = when (item) {
|
operator fun set(name: Name, item: DataItem<T>) = when (item) {
|
||||||
is DataItem.Node<T> -> set(name, item.value.builder())
|
is DataItem.Node<T> -> set(name, item.node.builder())
|
||||||
is DataItem.Leaf<T> -> set(name, item.value)
|
is DataItem.Leaf<T> -> set(name, item.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -211,11 +214,21 @@ class DataTreeBuilder<T : Any>(val type: KClass<out T>) {
|
|||||||
infix fun String.put(block: DataTreeBuilder<T>.() -> Unit) = set(toName(), DataTreeBuilder(type).apply(block))
|
infix fun String.put(block: DataTreeBuilder<T>.() -> Unit) = set(toName(), DataTreeBuilder(type).apply(block))
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update data with given node data and meta with node meta.
|
||||||
|
*/
|
||||||
fun update(node: DataNode<T>) {
|
fun update(node: DataNode<T>) {
|
||||||
node.dataSequence().forEach {
|
node.dataSequence().forEach {
|
||||||
//TODO check if the place is occupied
|
//TODO check if the place is occupied
|
||||||
this[it.first] = it.second
|
this[it.first] = it.second
|
||||||
}
|
}
|
||||||
|
meta.update(node.meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun meta(block: MetaBuilder.() -> Unit) = meta.apply(block)
|
||||||
|
|
||||||
|
fun meta(meta: Meta) {
|
||||||
|
this.meta = meta.builder()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun build(): DataTree<T> {
|
fun build(): DataTree<T> {
|
||||||
@ -225,7 +238,7 @@ class DataTreeBuilder<T : Any>(val type: KClass<out T>) {
|
|||||||
is DataTreeBuilderItem.Node -> DataItem.Node(value.tree.build())
|
is DataTreeBuilderItem.Node -> DataItem.Node(value.tree.build())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return DataTree(type, resMap)
|
return DataTree(type, resMap, meta.seal())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,11 +255,11 @@ fun <T : Any> DataTreeBuilder<T>.static(name: Name, data: T, meta: Meta = EmptyM
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun <T : Any> DataTreeBuilder<T>.static(name: Name, data: T, block: MetaBuilder.() -> Unit = {}) {
|
fun <T : Any> DataTreeBuilder<T>.static(name: Name, data: T, block: MetaBuilder.() -> Unit = {}) {
|
||||||
this[name] = Data.static(data, buildMeta(block))
|
this[name] = Data.static(data, Meta(block))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T : Any> DataTreeBuilder<T>.static(name: String, data: T, block: MetaBuilder.() -> Unit = {}) {
|
fun <T : Any> DataTreeBuilder<T>.static(name: String, data: T, block: MetaBuilder.() -> Unit = {}) {
|
||||||
this[name.toName()] = Data.static(data, buildMeta(block))
|
this[name.toName()] = Data.static(data, Meta(block))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T : Any> DataTreeBuilder<T>.node(name: Name, node: DataNode<T>) {
|
fun <T : Any> DataTreeBuilder<T>.node(name: Name, node: DataNode<T>) {
|
||||||
|
@ -15,9 +15,7 @@ interface Goal<out T> {
|
|||||||
* Get ongoing computation or start a new one.
|
* Get ongoing computation or start a new one.
|
||||||
* Does not guarantee thread safety. In case of multi-thread access, could create orphan computations.
|
* Does not guarantee thread safety. In case of multi-thread access, could create orphan computations.
|
||||||
*/
|
*/
|
||||||
fun startAsync(scope: CoroutineScope): Deferred<T>
|
fun CoroutineScope.startAsync(): Deferred<T>
|
||||||
|
|
||||||
suspend fun CoroutineScope.await(): T = startAsync(this).await()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset the computation
|
* Reset the computation
|
||||||
@ -29,17 +27,15 @@ interface Goal<out T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Goal<*>.start(scope: CoroutineScope): Job = startAsync(scope)
|
suspend fun <T> Goal<T>.await(): T = coroutineScope { startAsync().await() }
|
||||||
|
|
||||||
val Goal<*>.isComplete get() = result?.isCompleted ?: false
|
val Goal<*>.isComplete get() = result?.isCompleted ?: false
|
||||||
|
|
||||||
suspend fun <T> Goal<T>.await(scope: CoroutineScope): T = scope.await()
|
|
||||||
|
|
||||||
open class StaticGoal<T>(val value: T) : Goal<T> {
|
open class StaticGoal<T>(val value: T) : Goal<T> {
|
||||||
override val dependencies: Collection<Goal<*>> get() = emptyList()
|
override val dependencies: Collection<Goal<*>> get() = emptyList()
|
||||||
override val result: Deferred<T> = CompletableDeferred(value)
|
override val result: Deferred<T> = CompletableDeferred(value)
|
||||||
|
|
||||||
override fun startAsync(scope: CoroutineScope): Deferred<T> = result
|
override fun CoroutineScope.startAsync(): Deferred<T> = result
|
||||||
|
|
||||||
override fun reset() {
|
override fun reset() {
|
||||||
//doNothing
|
//doNothing
|
||||||
@ -59,11 +55,12 @@ open class DynamicGoal<T>(
|
|||||||
* Get ongoing computation or start a new one.
|
* Get ongoing computation or start a new one.
|
||||||
* Does not guarantee thread safety. In case of multi-thread access, could create orphan computations.
|
* Does not guarantee thread safety. In case of multi-thread access, could create orphan computations.
|
||||||
*/
|
*/
|
||||||
override fun startAsync(scope: CoroutineScope): Deferred<T> {
|
override fun CoroutineScope.startAsync(): Deferred<T> {
|
||||||
val startedDependencies = this.dependencies.map { goal ->
|
val startedDependencies = this@DynamicGoal.dependencies.map { goal ->
|
||||||
goal.startAsync(scope)
|
goal.run { startAsync() }
|
||||||
}
|
}
|
||||||
return result ?: scope.async(coroutineContext + CoroutineMonitor() + Dependencies(startedDependencies)) {
|
return result
|
||||||
|
?: async(this@DynamicGoal.coroutineContext + CoroutineMonitor() + Dependencies(startedDependencies)) {
|
||||||
startedDependencies.forEach { deferred ->
|
startedDependencies.forEach { deferred ->
|
||||||
deferred.invokeOnCompletion { error ->
|
deferred.invokeOnCompletion { error ->
|
||||||
if (error != null) cancel(CancellationException("Dependency $deferred failed with error: ${error.message}"))
|
if (error != null) cancel(CancellationException("Dependency $deferred failed with error: ${error.message}"))
|
||||||
@ -89,7 +86,7 @@ fun <T, R> Goal<T>.map(
|
|||||||
coroutineContext: CoroutineContext = EmptyCoroutineContext,
|
coroutineContext: CoroutineContext = EmptyCoroutineContext,
|
||||||
block: suspend CoroutineScope.(T) -> R
|
block: suspend CoroutineScope.(T) -> R
|
||||||
): Goal<R> = DynamicGoal(coroutineContext, listOf(this)) {
|
): Goal<R> = DynamicGoal(coroutineContext, listOf(this)) {
|
||||||
block(await(this))
|
block(await())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -99,7 +96,7 @@ fun <T, R> Collection<Goal<T>>.reduce(
|
|||||||
coroutineContext: CoroutineContext = EmptyCoroutineContext,
|
coroutineContext: CoroutineContext = EmptyCoroutineContext,
|
||||||
block: suspend CoroutineScope.(Collection<T>) -> R
|
block: suspend CoroutineScope.(Collection<T>) -> R
|
||||||
): Goal<R> = DynamicGoal(coroutineContext, this) {
|
): Goal<R> = DynamicGoal(coroutineContext, this) {
|
||||||
block(map { run { it.await(this) } })
|
block(map { run { it.await() } })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -112,6 +109,6 @@ fun <K, T, R> Map<K, Goal<T>>.reduce(
|
|||||||
coroutineContext: CoroutineContext = EmptyCoroutineContext,
|
coroutineContext: CoroutineContext = EmptyCoroutineContext,
|
||||||
block: suspend CoroutineScope.(Map<K, T>) -> R
|
block: suspend CoroutineScope.(Map<K, T>) -> R
|
||||||
): Goal<R> = DynamicGoal(coroutineContext, this.values) {
|
): Goal<R> = DynamicGoal(coroutineContext, this.values) {
|
||||||
block(mapValues { it.value.await(this) })
|
block(mapValues { it.value.await() })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
package hep.dataforge.data
|
package hep.dataforge.data
|
||||||
|
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.*
|
||||||
import hep.dataforge.meta.MetaBuilder
|
|
||||||
import hep.dataforge.meta.builder
|
|
||||||
import hep.dataforge.meta.seal
|
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
@ -20,6 +17,7 @@ data class ActionEnv(
|
|||||||
/**
|
/**
|
||||||
* Action environment
|
* Action environment
|
||||||
*/
|
*/
|
||||||
|
@DFBuilder
|
||||||
class MapActionBuilder<T, R>(var name: Name, var meta: MetaBuilder, val actionMeta: Meta) {
|
class MapActionBuilder<T, R>(var name: Name, var meta: MetaBuilder, val actionMeta: Meta) {
|
||||||
lateinit var result: suspend ActionEnv.(T) -> R
|
lateinit var result: suspend ActionEnv.(T) -> R
|
||||||
|
|
||||||
|
@ -28,8 +28,8 @@ expect fun <R : Any> DataNode<*>.canCast(type: KClass<out R>): Boolean
|
|||||||
expect fun <R : Any> Data<*>.canCast(type: KClass<out R>): Boolean
|
expect fun <R : Any> Data<*>.canCast(type: KClass<out R>): Boolean
|
||||||
|
|
||||||
fun <R : Any> DataItem<*>.canCast(type: KClass<out R>): Boolean = when (this) {
|
fun <R : Any> DataItem<*>.canCast(type: KClass<out R>): Boolean = when (this) {
|
||||||
is DataItem.Node -> value.canCast(type)
|
is DataItem.Node -> node.canCast(type)
|
||||||
is DataItem.Leaf -> value.canCast(type)
|
is DataItem.Leaf -> data.canCast(type)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -41,7 +41,7 @@ fun <R : Any> Data<*>.cast(type: KClass<out R>): Data<R> {
|
|||||||
override val meta: Meta get() = this@cast.meta
|
override val meta: Meta get() = this@cast.meta
|
||||||
override val dependencies: Collection<Goal<*>> get() = this@cast.dependencies
|
override val dependencies: Collection<Goal<*>> get() = this@cast.dependencies
|
||||||
override val result: Deferred<R>? get() = this@cast.result as Deferred<R>
|
override val result: Deferred<R>? get() = this@cast.result as Deferred<R>
|
||||||
override fun startAsync(scope: CoroutineScope): Deferred<R> = this@cast.startAsync(scope) as Deferred<R>
|
override fun CoroutineScope.startAsync(): Deferred<R> = this@cast.run { startAsync() as Deferred<R> }
|
||||||
override fun reset() = this@cast.reset()
|
override fun reset() = this@cast.reset()
|
||||||
override val type: KClass<out R> = type
|
override val type: KClass<out R> = type
|
||||||
}
|
}
|
||||||
@ -52,6 +52,7 @@ inline fun <reified R : Any> Data<*>.cast(): Data<R> = cast(R::class)
|
|||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun <R : Any> DataNode<*>.cast(type: KClass<out R>): DataNode<R> {
|
fun <R : Any> DataNode<*>.cast(type: KClass<out R>): DataNode<R> {
|
||||||
return object : DataNode<R> {
|
return object : DataNode<R> {
|
||||||
|
override val meta: Meta get() = this@cast.meta
|
||||||
override val type: KClass<out R> = type
|
override val type: KClass<out R> = type
|
||||||
override val items: Map<NameToken, DataItem<R>> get() = this@cast.items as Map<NameToken, DataItem<R>>
|
override val items: Map<NameToken, DataItem<R>> get() = this@cast.items as Map<NameToken, DataItem<R>>
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package hep.dataforge.data
|
package hep.dataforge.data
|
||||||
|
|
||||||
|
import hep.dataforge.meta.Meta
|
||||||
import hep.dataforge.names.NameToken
|
import hep.dataforge.names.NameToken
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
@ -8,16 +9,17 @@ import kotlin.reflect.KClass
|
|||||||
* A zero-copy data node wrapper that returns only children with appropriate type.
|
* A zero-copy data node wrapper that returns only children with appropriate type.
|
||||||
*/
|
*/
|
||||||
class TypeFilteredDataNode<out T : Any>(val origin: DataNode<*>, override val type: KClass<out T>) : DataNode<T> {
|
class TypeFilteredDataNode<out T : Any>(val origin: DataNode<*>, override val type: KClass<out T>) : DataNode<T> {
|
||||||
|
override val meta: Meta get() = origin.meta
|
||||||
override val items: Map<NameToken, DataItem<T>> by lazy {
|
override val items: Map<NameToken, DataItem<T>> by lazy {
|
||||||
origin.items.mapNotNull { (key, item) ->
|
origin.items.mapNotNull { (key, item) ->
|
||||||
when (item) {
|
when (item) {
|
||||||
is DataItem.Leaf -> {
|
is DataItem.Leaf -> {
|
||||||
(item.value.filterIsInstance(type))?.let {
|
(item.data.filterIsInstance(type))?.let {
|
||||||
key to DataItem.Leaf(it)
|
key to DataItem.Leaf(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is DataItem.Node -> {
|
is DataItem.Node -> {
|
||||||
key to DataItem.Node(item.value.filterIsInstance(type))
|
key to DataItem.Node(item.node.filterIsInstance(type))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.associate { it }
|
}.associate { it }
|
||||||
|
@ -30,12 +30,10 @@ fun <R : Any> Data<*>.filterIsInstance(type: KClass<out R>): Data<R>? =
|
|||||||
* but could contain empty nodes
|
* but could contain empty nodes
|
||||||
*/
|
*/
|
||||||
fun <R : Any> DataNode<*>.filterIsInstance(type: KClass<out R>): DataNode<R> {
|
fun <R : Any> DataNode<*>.filterIsInstance(type: KClass<out R>): DataNode<R> {
|
||||||
return if (canCast(type)) {
|
return when {
|
||||||
cast(type)
|
canCast(type) -> cast(type)
|
||||||
} else if (this is TypeFilteredDataNode) {
|
this is TypeFilteredDataNode -> origin.filterIsInstance(type)
|
||||||
origin.filterIsInstance(type)
|
else -> TypeFilteredDataNode(this, type)
|
||||||
} else {
|
|
||||||
TypeFilteredDataNode(this, type)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,8 +42,8 @@ fun <R : Any> DataNode<*>.filterIsInstance(type: KClass<out R>): DataNode<R> {
|
|||||||
*/
|
*/
|
||||||
fun <R : Any> DataItem<*>?.filterIsInstance(type: KClass<out R>): DataItem<R>? = when (this) {
|
fun <R : Any> DataItem<*>?.filterIsInstance(type: KClass<out R>): DataItem<R>? = when (this) {
|
||||||
null -> null
|
null -> null
|
||||||
is DataItem.Node -> DataItem.Node(this.value.filterIsInstance(type))
|
is DataItem.Node -> DataItem.Node(this.node.filterIsInstance(type))
|
||||||
is DataItem.Leaf -> this.value.filterIsInstance(type)?.let { DataItem.Leaf(it) }
|
is DataItem.Leaf -> this.data.filterIsInstance(type)?.let { DataItem.Leaf(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified R : Any> DataItem<*>?.filterIsInstance(): DataItem<R>? = this@filterIsInstance.filterIsInstance(R::class)
|
inline fun <reified R : Any> DataItem<*>?.filterIsInstance(): DataItem<R>? = this@filterIsInstance.filterIsInstance(R::class)
|
@ -1,30 +1,24 @@
|
|||||||
|
import scientifik.DependencySourceSet.TEST
|
||||||
|
import scientifik.serialization
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("scientifik.mpp")
|
id("scientifik.mpp")
|
||||||
}
|
}
|
||||||
|
|
||||||
description = "IO module"
|
description = "IO module"
|
||||||
|
|
||||||
scientifik{
|
serialization(sourceSet = TEST){
|
||||||
withSerialization()
|
cbor()
|
||||||
withIO()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val ioVersion by rootProject.extra("0.2.0-npm-dev-4")
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain {
|
commonMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
api(project(":dataforge-context"))
|
api(project(":dataforge-context"))
|
||||||
}
|
api("org.jetbrains.kotlinx:kotlinx-io:$ioVersion")
|
||||||
}
|
|
||||||
jvmMain{
|
|
||||||
dependencies {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
jsMain{
|
|
||||||
dependencies{
|
|
||||||
api(npm("text-encoding"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
|
import scientifik.serialization
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("scientifik.jvm")
|
id("scientifik.jvm")
|
||||||
}
|
}
|
||||||
|
|
||||||
description = "YAML meta IO"
|
description = "YAML meta IO"
|
||||||
|
|
||||||
|
serialization{
|
||||||
|
yaml()
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(project(":dataforge-io"))
|
api(project(":dataforge-io"))
|
||||||
api("org.yaml:snakeyaml:1.25")
|
api("org.yaml:snakeyaml:1.26")
|
||||||
testImplementation(kotlin("test"))
|
|
||||||
testImplementation(kotlin("test-junit"))
|
|
||||||
}
|
}
|
||||||
|
59
dataforge-io/dataforge-io-yaml/src/main/kotlin/hep/dataforge/io/yaml/FrontMatterEnvelopeFormat.kt
59
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.DFExperimental
|
||||||
import hep.dataforge.meta.EmptyMeta
|
import hep.dataforge.meta.EmptyMeta
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
import kotlinx.io.core.*
|
import kotlinx.io.*
|
||||||
|
import kotlinx.io.text.readUtf8Line
|
||||||
|
import kotlinx.io.text.writeRawString
|
||||||
|
import kotlinx.io.text.writeUtf8String
|
||||||
import kotlinx.serialization.toUtf8Bytes
|
import kotlinx.serialization.toUtf8Bytes
|
||||||
|
|
||||||
@DFExperimental
|
@DFExperimental
|
||||||
@ -18,52 +21,61 @@ class FrontMatterEnvelopeFormat(
|
|||||||
var line: String = ""
|
var line: String = ""
|
||||||
var offset = 0u
|
var offset = 0u
|
||||||
do {
|
do {
|
||||||
line = readUTF8Line() ?: error("Input does not contain front matter separator")
|
line = readUtf8Line() //?: error("Input does not contain front matter separator")
|
||||||
offset += line.toUtf8Bytes().size.toUInt()
|
offset += line.toUtf8Bytes().size.toUInt()
|
||||||
} while (!line.startsWith(SEPARATOR))
|
} while (!line.startsWith(SEPARATOR))
|
||||||
|
|
||||||
val readMetaFormat =
|
val readMetaFormat =
|
||||||
metaTypeRegex.matchEntire(line)?.groupValues?.first()
|
metaTypeRegex.matchEntire(line)?.groupValues?.first()
|
||||||
?.let { io.metaFormat(it) } ?: YamlMetaFormat.default
|
?.let { io.metaFormat(it) } ?: YamlMetaFormat
|
||||||
|
|
||||||
val metaBlock = buildPacket {
|
val meta = buildBytes {
|
||||||
do {
|
do {
|
||||||
line = readUTF8Line() ?: error("Input does not contain closing front matter separator")
|
line = readUtf8Line()
|
||||||
appendln(line)
|
writeUtf8String(line + "\r\n")
|
||||||
offset += line.toUtf8Bytes().size.toUInt()
|
offset += line.toUtf8Bytes().size.toUInt()
|
||||||
} while (!line.startsWith(SEPARATOR))
|
} while (!line.startsWith(SEPARATOR))
|
||||||
|
}.read {
|
||||||
|
readMetaFormat.run {
|
||||||
|
readMeta()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val meta = readMetaFormat.fromBytes(metaBlock)
|
|
||||||
return PartialEnvelope(meta, offset, null)
|
return PartialEnvelope(meta, offset, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun Input.readObject(): Envelope {
|
override fun Input.readObject(): Envelope {
|
||||||
var line: String = ""
|
var line: String = ""
|
||||||
do {
|
do {
|
||||||
line = readUTF8Line() ?: error("Input does not contain front matter separator")
|
line = readUtf8Line() //?: error("Input does not contain front matter separator")
|
||||||
} while (!line.startsWith(SEPARATOR))
|
} while (!line.startsWith(SEPARATOR))
|
||||||
|
|
||||||
val readMetaFormat =
|
val readMetaFormat =
|
||||||
metaTypeRegex.matchEntire(line)?.groupValues?.first()
|
metaTypeRegex.matchEntire(line)?.groupValues?.first()
|
||||||
?.let { io.metaFormat(it) } ?: YamlMetaFormat.default
|
?.let { io.metaFormat(it) } ?: YamlMetaFormat
|
||||||
|
|
||||||
val metaBlock = buildPacket {
|
val meta = buildBytes {
|
||||||
do {
|
do {
|
||||||
appendln(readUTF8Line() ?: error("Input does not contain closing front matter separator"))
|
writeUtf8String(readUtf8Line() + "\r\n")
|
||||||
} while (!line.startsWith(SEPARATOR))
|
} while (!line.startsWith(SEPARATOR))
|
||||||
|
}.read {
|
||||||
|
readMetaFormat.run {
|
||||||
|
readMeta()
|
||||||
}
|
}
|
||||||
val meta = readMetaFormat.fromBytes(metaBlock)
|
}
|
||||||
val bytes = readBytes()
|
val bytes = readRemaining()
|
||||||
val data = bytes.asBinary()
|
val data = bytes.asBinary()
|
||||||
return SimpleEnvelope(meta, data)
|
return SimpleEnvelope(meta, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) {
|
override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) {
|
||||||
val metaFormat = metaFormatFactory(formatMeta, io.context)
|
val metaFormat = metaFormatFactory(formatMeta, io.context)
|
||||||
writeText("$SEPARATOR\r\n")
|
writeRawString("$SEPARATOR\r\n")
|
||||||
metaFormat.run { writeObject(envelope.meta) }
|
metaFormat.run { writeObject(envelope.meta) }
|
||||||
writeText("$SEPARATOR\r\n")
|
writeRawString("$SEPARATOR\r\n")
|
||||||
envelope.data?.read { copyTo(this@writeEnvelope) }
|
//Printing data
|
||||||
|
envelope.data?.let { data ->
|
||||||
|
writeBinary(data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object : EnvelopeFormatFactory {
|
companion object : EnvelopeFormatFactory {
|
||||||
@ -76,13 +88,24 @@ class FrontMatterEnvelopeFormat(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun peekFormat(io: IOPlugin, input: Input): EnvelopeFormat? {
|
override fun peekFormat(io: IOPlugin, input: Input): EnvelopeFormat? {
|
||||||
val line = input.readUTF8Line(3, 30)
|
val line = input.readUtf8Line()
|
||||||
return if (line != null && line.startsWith("---")) {
|
return if (line.startsWith("---")) {
|
||||||
invoke()
|
invoke()
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val default by lazy { invoke() }
|
||||||
|
|
||||||
|
override fun Input.readPartial(): PartialEnvelope =
|
||||||
|
default.run { readPartial() }
|
||||||
|
|
||||||
|
override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) =
|
||||||
|
default.run { writeEnvelope(envelope, metaFormatFactory, formatMeta) }
|
||||||
|
|
||||||
|
override fun Input.readObject(): Envelope =
|
||||||
|
default.run { readObject() }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,25 +1,23 @@
|
|||||||
package hep.dataforge.io.yaml
|
package hep.dataforge.io.yaml
|
||||||
|
|
||||||
import hep.dataforge.context.Context
|
import hep.dataforge.context.Context
|
||||||
import hep.dataforge.descriptors.NodeDescriptor
|
|
||||||
import hep.dataforge.io.MetaFormat
|
import hep.dataforge.io.MetaFormat
|
||||||
import hep.dataforge.io.MetaFormatFactory
|
import hep.dataforge.io.MetaFormatFactory
|
||||||
import hep.dataforge.meta.DFExperimental
|
import hep.dataforge.meta.DFExperimental
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
|
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||||
import hep.dataforge.meta.toMap
|
import hep.dataforge.meta.toMap
|
||||||
import hep.dataforge.meta.toMeta
|
import hep.dataforge.meta.toMeta
|
||||||
import hep.dataforge.names.Name
|
import kotlinx.io.Input
|
||||||
import hep.dataforge.names.plus
|
import kotlinx.io.Output
|
||||||
import kotlinx.io.core.Input
|
import kotlinx.io.readUByte
|
||||||
import kotlinx.io.core.Output
|
import kotlinx.io.text.writeUtf8String
|
||||||
import kotlinx.io.core.readUByte
|
|
||||||
import kotlinx.io.core.writeText
|
|
||||||
import org.yaml.snakeyaml.Yaml
|
import org.yaml.snakeyaml.Yaml
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
private class InputAsStream(val input: Input) : InputStream() {
|
private class InputAsStream(val input: Input) : InputStream() {
|
||||||
override fun read(): Int {
|
override fun read(): Int {
|
||||||
if (input.endOfInput) return -1
|
if (input.eof()) return -1
|
||||||
return input.readUByte().toInt()
|
return input.readUByte().toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,7 +34,7 @@ class YamlMetaFormat(val meta: Meta) : MetaFormat {
|
|||||||
|
|
||||||
override fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor?) {
|
override fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor?) {
|
||||||
val string = yaml.dump(meta.toMap(descriptor))
|
val string = yaml.dump(meta.toMap(descriptor))
|
||||||
writeText(string)
|
writeUtf8String(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun Input.readMeta(descriptor: NodeDescriptor?): Meta {
|
override fun Input.readMeta(descriptor: NodeDescriptor?): Meta {
|
||||||
@ -45,12 +43,18 @@ class YamlMetaFormat(val meta: Meta) : MetaFormat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object : MetaFormatFactory {
|
companion object : MetaFormatFactory {
|
||||||
val default = YamlMetaFormat()
|
|
||||||
|
|
||||||
override fun invoke(meta: Meta, context: Context): MetaFormat = YamlMetaFormat(meta)
|
override fun invoke(meta: Meta, context: Context): MetaFormat = YamlMetaFormat(meta)
|
||||||
|
|
||||||
override val name: Name = super.name + "yaml"
|
override val shortName = "yaml"
|
||||||
|
|
||||||
override val key: Short = 0x594d //YM
|
override val key: Short = 0x594d //YM
|
||||||
|
|
||||||
|
private val default = YamlMetaFormat()
|
||||||
|
|
||||||
|
override fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor?) =
|
||||||
|
default.run { writeMeta(meta, descriptor) }
|
||||||
|
|
||||||
|
override fun Input.readMeta(descriptor: NodeDescriptor?): Meta =
|
||||||
|
default.run { readMeta(descriptor) }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,17 +3,16 @@ package hep.dataforge.io.yaml
|
|||||||
import hep.dataforge.io.parse
|
import hep.dataforge.io.parse
|
||||||
import hep.dataforge.io.toString
|
import hep.dataforge.io.toString
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
import hep.dataforge.meta.buildMeta
|
|
||||||
import hep.dataforge.meta.get
|
import hep.dataforge.meta.get
|
||||||
import hep.dataforge.meta.seal
|
import hep.dataforge.meta.seal
|
||||||
import org.junit.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
|
||||||
class YamlMetaFormatTest {
|
class YamlMetaFormatTest {
|
||||||
@Test
|
@Test
|
||||||
fun testYamlMetaFormat() {
|
fun testYamlMetaFormat() {
|
||||||
val meta = buildMeta {
|
val meta = Meta {
|
||||||
"a" put 22
|
"a" put 22
|
||||||
"node" put {
|
"node" put {
|
||||||
"b" put "DDD"
|
"b" put "DDD"
|
||||||
|
@ -1,87 +0,0 @@
|
|||||||
package hep.dataforge.io
|
|
||||||
|
|
||||||
import kotlinx.io.core.*
|
|
||||||
import kotlin.math.min
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A source of binary data
|
|
||||||
*/
|
|
||||||
interface Binary {
|
|
||||||
/**
|
|
||||||
* The size of binary in bytes
|
|
||||||
*/
|
|
||||||
val size: ULong
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read continuous [Input] from this binary stating from the beginning.
|
|
||||||
* The input is automatically closed on scope close.
|
|
||||||
* Some implementation may forbid this to be called twice. In this case second call will throw an exception.
|
|
||||||
*/
|
|
||||||
fun <R> read(block: Input.() -> R): R
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A [Binary] with addition random access functionality. It by default allows multiple [read] operations.
|
|
||||||
*/
|
|
||||||
@ExperimentalUnsignedTypes
|
|
||||||
interface RandomAccessBinary : Binary {
|
|
||||||
/**
|
|
||||||
* Read at most [size] of bytes starting at [from] offset from the beginning of the binary.
|
|
||||||
* This method could be called multiple times simultaneously.
|
|
||||||
*
|
|
||||||
* If size
|
|
||||||
*/
|
|
||||||
fun <R> read(from: UInt, size: UInt = UInt.MAX_VALUE, block: Input.() -> R): R
|
|
||||||
|
|
||||||
override fun <R> read(block: Input.() -> R): R = read(0.toUInt(), UInt.MAX_VALUE, block)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Binary.toBytes(): ByteArray = read {
|
|
||||||
this.readBytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExperimentalUnsignedTypes
|
|
||||||
fun RandomAccessBinary.readPacket(from: UInt, size: UInt): ByteReadPacket = read(from, size) {
|
|
||||||
buildPacket { copyTo(this) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExperimentalUnsignedTypes
|
|
||||||
object EmptyBinary : RandomAccessBinary {
|
|
||||||
|
|
||||||
override val size: ULong = 0.toULong()
|
|
||||||
|
|
||||||
override fun <R> read(from: UInt, size: UInt, block: Input.() -> R): R {
|
|
||||||
error("The binary is empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExperimentalUnsignedTypes
|
|
||||||
inline class ArrayBinary(val array: ByteArray) : RandomAccessBinary {
|
|
||||||
override val size: ULong get() = array.size.toULong()
|
|
||||||
|
|
||||||
override fun <R> read(from: UInt, size: UInt, block: Input.() -> R): R {
|
|
||||||
val theSize = min(size, array.size.toUInt() - from)
|
|
||||||
return buildPacket {
|
|
||||||
writeFully(array, from.toInt(), theSize.toInt())
|
|
||||||
}.block()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ByteArray.asBinary() = ArrayBinary(this)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read given binary as object using given format
|
|
||||||
*/
|
|
||||||
fun <T : Any> Binary.readWith(format: IOFormat<T>): T = format.run {
|
|
||||||
read {
|
|
||||||
readObject()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T : Any> IOFormat<T>.writeBinary(obj: T): Binary {
|
|
||||||
val packet = buildPacket {
|
|
||||||
writeObject(obj)
|
|
||||||
}
|
|
||||||
return ArrayBinary(packet.readBytes())
|
|
||||||
}
|
|
@ -1,18 +1,18 @@
|
|||||||
package hep.dataforge.io
|
package hep.dataforge.io
|
||||||
|
|
||||||
import hep.dataforge.context.Context
|
import hep.dataforge.context.Context
|
||||||
import hep.dataforge.descriptors.NodeDescriptor
|
import hep.dataforge.meta.Meta
|
||||||
import hep.dataforge.meta.*
|
import hep.dataforge.meta.MetaBuilder
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.meta.MetaItem
|
||||||
import hep.dataforge.names.plus
|
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||||
|
import hep.dataforge.meta.setItem
|
||||||
import hep.dataforge.values.*
|
import hep.dataforge.values.*
|
||||||
import kotlinx.io.core.Input
|
import kotlinx.io.*
|
||||||
import kotlinx.io.core.Output
|
import kotlinx.io.text.readUtf8String
|
||||||
import kotlinx.io.core.readText
|
import kotlinx.io.text.writeUtf8String
|
||||||
import kotlinx.io.core.writeText
|
|
||||||
|
|
||||||
object BinaryMetaFormat : MetaFormat, MetaFormatFactory {
|
object BinaryMetaFormat : MetaFormat, MetaFormatFactory {
|
||||||
override val name: Name = super.name + "bin"
|
override val shortName: String = "bin"
|
||||||
override val key: Short = 0x4249//BI
|
override val key: Short = 0x4249//BI
|
||||||
|
|
||||||
override fun invoke(meta: Meta, context: Context): MetaFormat = this
|
override fun invoke(meta: Meta, context: Context): MetaFormat = this
|
||||||
@ -25,7 +25,7 @@ object BinaryMetaFormat : MetaFormat, MetaFormatFactory {
|
|||||||
|
|
||||||
private fun Output.writeString(str: String) {
|
private fun Output.writeString(str: String) {
|
||||||
writeInt(str.length)
|
writeInt(str.length)
|
||||||
writeText(str)
|
writeUtf8String(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Output.writeValue(value: Value) {
|
fun Output.writeValue(value: Value) {
|
||||||
@ -93,7 +93,7 @@ object BinaryMetaFormat : MetaFormat, MetaFormatFactory {
|
|||||||
|
|
||||||
private fun Input.readString(): String {
|
private fun Input.readString(): String {
|
||||||
val length = readInt()
|
val length = readInt()
|
||||||
return readText(max = length)
|
return readUtf8String(length)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
@ -115,7 +115,7 @@ object BinaryMetaFormat : MetaFormat, MetaFormatFactory {
|
|||||||
}
|
}
|
||||||
'M' -> {
|
'M' -> {
|
||||||
val length = readInt()
|
val length = readInt()
|
||||||
val meta = buildMeta {
|
val meta = Meta {
|
||||||
(1..length).forEach { _ ->
|
(1..length).forEach { _ ->
|
||||||
val name = readString()
|
val name = readString()
|
||||||
val item = readMetaItem()
|
val item = readMetaItem()
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
package hep.dataforge.io
|
package hep.dataforge.io
|
||||||
|
|
||||||
import hep.dataforge.meta.*
|
import hep.dataforge.meta.Laminate
|
||||||
|
import hep.dataforge.meta.Meta
|
||||||
|
import hep.dataforge.meta.get
|
||||||
|
import hep.dataforge.meta.string
|
||||||
import hep.dataforge.names.asName
|
import hep.dataforge.names.asName
|
||||||
import hep.dataforge.names.plus
|
import hep.dataforge.names.plus
|
||||||
import kotlinx.io.core.Output
|
import kotlinx.io.Binary
|
||||||
import kotlinx.io.core.buildPacket
|
|
||||||
import kotlinx.io.core.readBytes
|
|
||||||
|
|
||||||
interface Envelope {
|
interface Envelope {
|
||||||
val meta: Meta
|
val meta: Meta
|
||||||
@ -21,12 +22,13 @@ interface Envelope {
|
|||||||
val ENVELOPE_DATA_TYPE_KEY = ENVELOPE_NODE_KEY + "dataType"
|
val ENVELOPE_DATA_TYPE_KEY = ENVELOPE_NODE_KEY + "dataType"
|
||||||
val ENVELOPE_DATA_ID_KEY = ENVELOPE_NODE_KEY + "dataID"
|
val ENVELOPE_DATA_ID_KEY = ENVELOPE_NODE_KEY + "dataID"
|
||||||
val ENVELOPE_DESCRIPTION_KEY = ENVELOPE_NODE_KEY + "description"
|
val ENVELOPE_DESCRIPTION_KEY = ENVELOPE_NODE_KEY + "description"
|
||||||
|
val ENVELOPE_NAME_KEY = ENVELOPE_NODE_KEY + "name"
|
||||||
//const val ENVELOPE_TIME_KEY = "@envelope.time"
|
//const val ENVELOPE_TIME_KEY = "@envelope.time"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a static envelope using provided builder
|
* Build a static envelope using provided builder
|
||||||
*/
|
*/
|
||||||
operator fun invoke(block: EnvelopeBuilder.() -> Unit) = EnvelopeBuilder().apply(block).build()
|
inline operator fun invoke(block: EnvelopeBuilder.() -> Unit) = EnvelopeBuilder().apply(block).build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,33 +84,3 @@ fun Envelope.withMetaLayers(vararg layers: Meta): Envelope {
|
|||||||
else -> ProxyEnvelope(this, *layers)
|
else -> ProxyEnvelope(this, *layers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class EnvelopeBuilder {
|
|
||||||
private val metaBuilder = MetaBuilder()
|
|
||||||
var data: Binary? = null
|
|
||||||
|
|
||||||
fun meta(block: MetaBuilder.() -> Unit) {
|
|
||||||
metaBuilder.apply(block)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun meta(meta: Meta) {
|
|
||||||
metaBuilder.update(meta)
|
|
||||||
}
|
|
||||||
|
|
||||||
var type by metaBuilder.string(key = Envelope.ENVELOPE_TYPE_KEY)
|
|
||||||
var dataType by metaBuilder.string(key = Envelope.ENVELOPE_DATA_TYPE_KEY)
|
|
||||||
var dataID by metaBuilder.string(key = Envelope.ENVELOPE_DATA_ID_KEY)
|
|
||||||
var description by metaBuilder.string(key = Envelope.ENVELOPE_DESCRIPTION_KEY)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct a binary and transform it into byte-array based buffer
|
|
||||||
*/
|
|
||||||
fun data(block: Output.() -> Unit) {
|
|
||||||
val bytes = buildPacket {
|
|
||||||
block()
|
|
||||||
}
|
|
||||||
data = ArrayBinary(bytes.readBytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun build() = SimpleEnvelope(metaBuilder.seal(), data)
|
|
||||||
}
|
|
@ -0,0 +1,52 @@
|
|||||||
|
package hep.dataforge.io
|
||||||
|
|
||||||
|
import hep.dataforge.meta.*
|
||||||
|
import kotlinx.io.ArrayBinary
|
||||||
|
import kotlinx.io.Binary
|
||||||
|
import kotlinx.io.ExperimentalIoApi
|
||||||
|
import kotlinx.io.Output
|
||||||
|
|
||||||
|
class EnvelopeBuilder {
|
||||||
|
private val metaBuilder = MetaBuilder()
|
||||||
|
var data: Binary? = null
|
||||||
|
|
||||||
|
fun meta(block: MetaBuilder.() -> Unit) {
|
||||||
|
metaBuilder.apply(block)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun meta(meta: Meta) {
|
||||||
|
metaBuilder.update(meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The general purpose of the envelope
|
||||||
|
*/
|
||||||
|
var type by metaBuilder.string(key = Envelope.ENVELOPE_TYPE_KEY)
|
||||||
|
var dataType by metaBuilder.string(key = Envelope.ENVELOPE_DATA_TYPE_KEY)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data unique identifier to bypass identity checks
|
||||||
|
*/
|
||||||
|
var dataID by metaBuilder.string(key = Envelope.ENVELOPE_DATA_ID_KEY)
|
||||||
|
var description by metaBuilder.string(key = Envelope.ENVELOPE_DESCRIPTION_KEY)
|
||||||
|
var name by metaBuilder.string(key = Envelope.ENVELOPE_NAME_KEY)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a data binary from given builder
|
||||||
|
*/
|
||||||
|
@ExperimentalIoApi
|
||||||
|
fun data(block: Output.() -> Unit) {
|
||||||
|
data = ArrayBinary.write(builder = block)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build() = SimpleEnvelope(metaBuilder.seal(), data)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//@ExperimentalContracts
|
||||||
|
//suspend fun EnvelopeBuilder.buildData(block: suspend Output.() -> Unit): Binary{
|
||||||
|
// contract {
|
||||||
|
// callsInPlace(block, InvocationKind.EXACTLY_ONCE)
|
||||||
|
// }
|
||||||
|
// val scope = CoroutineScope(coroutineContext)
|
||||||
|
//}
|
@ -7,8 +7,8 @@ import hep.dataforge.meta.Meta
|
|||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.names.asName
|
import hep.dataforge.names.asName
|
||||||
import hep.dataforge.provider.Type
|
import hep.dataforge.provider.Type
|
||||||
import kotlinx.io.core.Input
|
import kotlinx.io.Input
|
||||||
import kotlinx.io.core.Output
|
import kotlinx.io.Output
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -23,15 +23,19 @@ interface EnvelopeFormat : IOFormat<Envelope> {
|
|||||||
|
|
||||||
fun Input.readPartial(): PartialEnvelope
|
fun Input.readPartial(): PartialEnvelope
|
||||||
|
|
||||||
fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta = EmptyMeta)
|
fun Output.writeEnvelope(
|
||||||
|
envelope: Envelope,
|
||||||
|
metaFormatFactory: MetaFormatFactory = defaultMetaFormat,
|
||||||
|
formatMeta: Meta = EmptyMeta
|
||||||
|
)
|
||||||
|
|
||||||
override fun Input.readObject(): Envelope
|
override fun Input.readObject(): Envelope
|
||||||
|
|
||||||
override fun Output.writeObject(obj: Envelope): Unit = writeEnvelope(obj, defaultMetaFormat)
|
override fun Output.writeObject(obj: Envelope): Unit = writeEnvelope(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Type(ENVELOPE_FORMAT_TYPE)
|
@Type(ENVELOPE_FORMAT_TYPE)
|
||||||
interface EnvelopeFormatFactory : IOFormatFactory<Envelope> {
|
interface EnvelopeFormatFactory : IOFormatFactory<Envelope>, EnvelopeFormat {
|
||||||
override val name: Name get() = "envelope".asName()
|
override val name: Name get() = "envelope".asName()
|
||||||
override val type: KClass<out Envelope> get() = Envelope::class
|
override val type: KClass<out Envelope> get() = Envelope::class
|
||||||
|
|
||||||
|
@ -0,0 +1,125 @@
|
|||||||
|
package hep.dataforge.io
|
||||||
|
|
||||||
|
import hep.dataforge.context.Global
|
||||||
|
import hep.dataforge.io.EnvelopeParts.FORMAT_META_KEY
|
||||||
|
import hep.dataforge.io.EnvelopeParts.FORMAT_NAME_KEY
|
||||||
|
import hep.dataforge.io.EnvelopeParts.INDEX_KEY
|
||||||
|
import hep.dataforge.io.EnvelopeParts.MULTIPART_DATA_SEPARATOR
|
||||||
|
import hep.dataforge.io.EnvelopeParts.MULTIPART_DATA_TYPE
|
||||||
|
import hep.dataforge.io.EnvelopeParts.SIZE_KEY
|
||||||
|
import hep.dataforge.meta.*
|
||||||
|
import hep.dataforge.names.asName
|
||||||
|
import hep.dataforge.names.plus
|
||||||
|
import hep.dataforge.names.toName
|
||||||
|
import kotlinx.io.text.readRawString
|
||||||
|
import kotlinx.io.text.writeRawString
|
||||||
|
|
||||||
|
object EnvelopeParts {
|
||||||
|
val MULTIPART_KEY = "multipart".asName()
|
||||||
|
val SIZE_KEY = Envelope.ENVELOPE_NODE_KEY + MULTIPART_KEY + "size"
|
||||||
|
val INDEX_KEY = Envelope.ENVELOPE_NODE_KEY + MULTIPART_KEY + "index"
|
||||||
|
val FORMAT_NAME_KEY = Envelope.ENVELOPE_NODE_KEY + MULTIPART_KEY + "format"
|
||||||
|
val FORMAT_META_KEY = Envelope.ENVELOPE_NODE_KEY + MULTIPART_KEY + "meta"
|
||||||
|
|
||||||
|
const val MULTIPART_DATA_SEPARATOR = "\r\n#~PART~#\r\n"
|
||||||
|
|
||||||
|
const val MULTIPART_DATA_TYPE = "envelope.multipart"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append multiple serialized envelopes to the data block. Previous data is erased if it was present
|
||||||
|
*/
|
||||||
|
@DFExperimental
|
||||||
|
fun EnvelopeBuilder.multipart(
|
||||||
|
envelopes: Collection<Envelope>,
|
||||||
|
format: EnvelopeFormatFactory,
|
||||||
|
formatMeta: Meta = EmptyMeta
|
||||||
|
) {
|
||||||
|
dataType = MULTIPART_DATA_TYPE
|
||||||
|
meta {
|
||||||
|
SIZE_KEY put envelopes.size
|
||||||
|
FORMAT_NAME_KEY put format.name.toString()
|
||||||
|
if (!formatMeta.isEmpty()) {
|
||||||
|
FORMAT_META_KEY put formatMeta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data {
|
||||||
|
format(formatMeta).run {
|
||||||
|
envelopes.forEach {
|
||||||
|
writeRawString(MULTIPART_DATA_SEPARATOR)
|
||||||
|
writeEnvelope(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a multipart partition in the envelope adding additional name-index mapping in meta
|
||||||
|
*/
|
||||||
|
@DFExperimental
|
||||||
|
fun EnvelopeBuilder.multipart(
|
||||||
|
envelopes: Map<String, Envelope>,
|
||||||
|
format: EnvelopeFormatFactory,
|
||||||
|
formatMeta: Meta = EmptyMeta
|
||||||
|
) {
|
||||||
|
dataType = MULTIPART_DATA_TYPE
|
||||||
|
meta {
|
||||||
|
SIZE_KEY put envelopes.size
|
||||||
|
FORMAT_NAME_KEY put format.name.toString()
|
||||||
|
if (!formatMeta.isEmpty()) {
|
||||||
|
FORMAT_META_KEY put formatMeta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data {
|
||||||
|
format.run {
|
||||||
|
var counter = 0
|
||||||
|
envelopes.forEach { (key, envelope) ->
|
||||||
|
writeRawString(MULTIPART_DATA_SEPARATOR)
|
||||||
|
writeEnvelope(envelope)
|
||||||
|
meta {
|
||||||
|
append(INDEX_KEY, Meta {
|
||||||
|
"key" put key
|
||||||
|
"index" put counter
|
||||||
|
})
|
||||||
|
}
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@DFExperimental
|
||||||
|
fun EnvelopeBuilder.multipart(
|
||||||
|
formatFactory: EnvelopeFormatFactory,
|
||||||
|
formatMeta: Meta = EmptyMeta,
|
||||||
|
builder: suspend SequenceScope<Envelope>.() -> Unit
|
||||||
|
) = multipart(sequence(builder).toList(), formatFactory, formatMeta)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If given envelope supports multipart data, return a sequence of those parts (could be empty). Otherwise return null.
|
||||||
|
*/
|
||||||
|
@DFExperimental
|
||||||
|
fun Envelope.parts(io: IOPlugin = Global.plugins.fetch(IOPlugin)): Sequence<Envelope>? {
|
||||||
|
return when (dataType) {
|
||||||
|
MULTIPART_DATA_TYPE -> {
|
||||||
|
val size = meta[SIZE_KEY].int ?: error("Unsized parts not supported yet")
|
||||||
|
val formatName = meta[FORMAT_NAME_KEY].string?.toName()
|
||||||
|
?: error("Inferring parts format is not supported at the moment")
|
||||||
|
val formatMeta = meta[FORMAT_META_KEY].node ?: EmptyMeta
|
||||||
|
val format = io.envelopeFormat(formatName, formatMeta)
|
||||||
|
?: error("Format $formatName is not resolved by $io")
|
||||||
|
return format.run {
|
||||||
|
data?.read {
|
||||||
|
sequence {
|
||||||
|
repeat(size) {
|
||||||
|
val separator = readRawString(MULTIPART_DATA_SEPARATOR.length)
|
||||||
|
if(separator!= MULTIPART_DATA_SEPARATOR) error("Separator is expected, but $separator found")
|
||||||
|
yield(readObject())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} ?: emptySequence()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
@ -10,13 +10,9 @@ import hep.dataforge.names.Name
|
|||||||
import hep.dataforge.names.asName
|
import hep.dataforge.names.asName
|
||||||
import hep.dataforge.provider.Type
|
import hep.dataforge.provider.Type
|
||||||
import hep.dataforge.values.Value
|
import hep.dataforge.values.Value
|
||||||
import kotlinx.io.core.*
|
import kotlinx.io.*
|
||||||
|
import kotlinx.io.buffer.Buffer
|
||||||
import kotlinx.io.pool.ObjectPool
|
import kotlinx.io.pool.ObjectPool
|
||||||
import kotlinx.serialization.ImplicitReflectionSerializer
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.cbor.Cbor
|
|
||||||
import kotlinx.serialization.serializer
|
|
||||||
import kotlin.math.min
|
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -50,7 +46,7 @@ class ListIOFormat<T : Any>(val format: IOFormat<T>) : IOFormat<List<T>> {
|
|||||||
|
|
||||||
val <T : Any> IOFormat<T>.list get() = ListIOFormat(this)
|
val <T : Any> IOFormat<T>.list get() = ListIOFormat(this)
|
||||||
|
|
||||||
fun ObjectPool<IoBuffer>.fill(block: IoBuffer.() -> Unit): IoBuffer {
|
fun ObjectPool<Buffer>.fill(block: Buffer.() -> Unit): Buffer {
|
||||||
val buffer = borrow()
|
val buffer = borrow()
|
||||||
return try {
|
return try {
|
||||||
buffer.apply(block)
|
buffer.apply(block)
|
||||||
@ -72,41 +68,11 @@ interface IOFormatFactory<T : Any> : Factory<IOFormat<T>>, Named {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated("To be removed in io-2")
|
fun <T : Any> IOFormat<T>.writeBytes(obj: T): Bytes = buildBytes { writeObject(obj) }
|
||||||
inline fun buildPacketWithoutPool(headerSizeHint: Int = 0, block: BytePacketBuilder.() -> Unit): ByteReadPacket {
|
|
||||||
val builder = BytePacketBuilder(headerSizeHint, IoBuffer.NoPool)
|
|
||||||
block(builder)
|
|
||||||
return builder.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T : Any> IOFormat<T>.writePacket(obj: T): ByteReadPacket = buildPacket { writeObject(obj) }
|
|
||||||
fun <T : Any> IOFormat<T>.writeBytes(obj: T): ByteArray = buildPacket { writeObject(obj) }.readBytes()
|
|
||||||
fun <T : Any> IOFormat<T>.readBytes(array: ByteArray): T {
|
|
||||||
//= ByteReadPacket(array).readThis()
|
|
||||||
val byteArrayInput: Input = object : AbstractInput(
|
|
||||||
IoBuffer.Pool.borrow(),
|
|
||||||
remaining = array.size.toLong(),
|
|
||||||
pool = IoBuffer.Pool
|
|
||||||
) {
|
|
||||||
var written = 0
|
|
||||||
override fun closeSource() {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun fill(): IoBuffer? {
|
fun <T : Any> IOFormat<T>.writeByteArray(obj: T): ByteArray = buildBytes { writeObject(obj) }.toByteArray()
|
||||||
if (array.size - written <= 0) return null
|
fun <T : Any> IOFormat<T>.readByteArray(array: ByteArray): T = array.asBinary().read { readObject() }
|
||||||
|
|
||||||
return IoBuffer.Pool.fill {
|
|
||||||
reserveEndGap(IoBuffer.ReservedSize)
|
|
||||||
val toWrite = min(capacity, array.size - written)
|
|
||||||
writeFully(array, written, toWrite)
|
|
||||||
written += toWrite
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return byteArrayInput.readObject()
|
|
||||||
}
|
|
||||||
|
|
||||||
object DoubleIOFormat : IOFormat<Double>, IOFormatFactory<Double> {
|
object DoubleIOFormat : IOFormat<Double>, IOFormatFactory<Double> {
|
||||||
override fun invoke(meta: Meta, context: Context): IOFormat<Double> = this
|
override fun invoke(meta: Meta, context: Context): IOFormat<Double> = this
|
||||||
@ -140,25 +106,10 @@ object ValueIOFormat : IOFormat<Value>, IOFormatFactory<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Experimental
|
* Read given binary as object using given format
|
||||||
*/
|
*/
|
||||||
@ImplicitReflectionSerializer
|
fun <T : Any> Binary.readWith(format: IOFormat<T>): T = format.run {
|
||||||
class SerializerIOFormat<T : Any>(
|
read {
|
||||||
type: KClass<T>,
|
readObject()
|
||||||
val serializer: KSerializer<T> = type.serializer()
|
|
||||||
) : IOFormat<T> {
|
|
||||||
|
|
||||||
//override val name: Name = type.simpleName?.toName() ?: EmptyName
|
|
||||||
|
|
||||||
|
|
||||||
override fun Output.writeObject(obj: T) {
|
|
||||||
val bytes = Cbor.plain.dump(serializer, obj)
|
|
||||||
writeFully(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun Input.readObject(): T {
|
|
||||||
//FIXME reads the whole input
|
|
||||||
val bytes = readBytes()
|
|
||||||
return Cbor.plain.load(serializer, bytes)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -20,12 +20,15 @@ class IOPlugin(meta: Meta) : AbstractPlugin(meta) {
|
|||||||
metaFormatFactories.find { it.key == key }?.invoke(meta)
|
metaFormatFactories.find { it.key == key }?.invoke(meta)
|
||||||
|
|
||||||
fun metaFormat(name: String, meta: Meta = EmptyMeta): MetaFormat? =
|
fun metaFormat(name: String, meta: Meta = EmptyMeta): MetaFormat? =
|
||||||
metaFormatFactories.find { it.name.toString() == name }?.invoke(meta)
|
metaFormatFactories.find { it.shortName == name }?.invoke(meta)
|
||||||
|
|
||||||
val envelopeFormatFactories by lazy {
|
val envelopeFormatFactories by lazy {
|
||||||
context.content<EnvelopeFormatFactory>(ENVELOPE_FORMAT_TYPE).values
|
context.content<EnvelopeFormatFactory>(ENVELOPE_FORMAT_TYPE).values
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun envelopeFormat(name: Name, meta: Meta = EmptyMeta) =
|
||||||
|
envelopeFormatFactories.find { it.name == name }?.invoke(meta, context)
|
||||||
|
|
||||||
override fun provideTop(target: String): Map<Name, Any> {
|
override fun provideTop(target: String): Map<Name, Any> {
|
||||||
return when (target) {
|
return when (target) {
|
||||||
META_FORMAT_TYPE -> defaultMetaFormats.toMap()
|
META_FORMAT_TYPE -> defaultMetaFormats.toMap()
|
||||||
@ -49,7 +52,7 @@ class IOPlugin(meta: Meta) : AbstractPlugin(meta) {
|
|||||||
|
|
||||||
companion object : PluginFactory<IOPlugin> {
|
companion object : PluginFactory<IOPlugin> {
|
||||||
val defaultMetaFormats: List<MetaFormatFactory> = listOf(JsonMetaFormat, BinaryMetaFormat)
|
val defaultMetaFormats: List<MetaFormatFactory> = listOf(JsonMetaFormat, BinaryMetaFormat)
|
||||||
val defaultEnvelopeFormats = listOf(TaggedEnvelopeFormat)
|
val defaultEnvelopeFormats = listOf(TaggedEnvelopeFormat, TaglessEnvelopeFormat)
|
||||||
|
|
||||||
override val tag: PluginTag = PluginTag("io", group = PluginTag.DATAFORGE_GROUP)
|
override val tag: PluginTag = PluginTag("io", group = PluginTag.DATAFORGE_GROUP)
|
||||||
|
|
||||||
|
@ -2,171 +2,50 @@
|
|||||||
|
|
||||||
package hep.dataforge.io
|
package hep.dataforge.io
|
||||||
|
|
||||||
|
|
||||||
import hep.dataforge.context.Context
|
import hep.dataforge.context.Context
|
||||||
import hep.dataforge.descriptors.ItemDescriptor
|
|
||||||
import hep.dataforge.descriptors.NodeDescriptor
|
|
||||||
import hep.dataforge.descriptors.ValueDescriptor
|
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
import hep.dataforge.meta.MetaBase
|
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||||
import hep.dataforge.meta.MetaItem
|
import hep.dataforge.meta.node
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.meta.toJson
|
||||||
import hep.dataforge.names.NameToken
|
import hep.dataforge.meta.toMetaItem
|
||||||
import hep.dataforge.names.plus
|
import kotlinx.io.Input
|
||||||
import hep.dataforge.names.toName
|
import kotlinx.io.Output
|
||||||
import hep.dataforge.values.*
|
import kotlinx.io.text.readUtf8String
|
||||||
import kotlinx.io.core.Input
|
import kotlinx.io.text.writeUtf8String
|
||||||
import kotlinx.io.core.Output
|
import kotlinx.serialization.UnstableDefault
|
||||||
import kotlinx.io.core.readText
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.io.core.writeText
|
import kotlinx.serialization.json.JsonObjectSerializer
|
||||||
import kotlinx.serialization.json.*
|
|
||||||
import kotlin.collections.component1
|
|
||||||
import kotlin.collections.component2
|
|
||||||
import kotlin.collections.set
|
|
||||||
|
|
||||||
|
@OptIn(UnstableDefault::class)
|
||||||
class JsonMetaFormat(private val json: Json = Json.indented) : MetaFormat {
|
class JsonMetaFormat(private val json: Json = DEFAULT_JSON) : MetaFormat {
|
||||||
|
|
||||||
override fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor?) {
|
override fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor?) {
|
||||||
val jsonObject = meta.toJson(descriptor)
|
val jsonObject = meta.toJson(descriptor)
|
||||||
writeText(json.stringify(JsonObjectSerializer, jsonObject))
|
writeUtf8String(json.stringify(JsonObjectSerializer, jsonObject))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun Input.readMeta(descriptor: NodeDescriptor?): Meta {
|
override fun Input.readMeta(descriptor: NodeDescriptor?): Meta {
|
||||||
val str = readText()
|
val str = readUtf8String()
|
||||||
val jsonElement = json.parseJson(str)
|
val jsonElement = json.parseJson(str)
|
||||||
return jsonElement.toMeta()
|
val item = jsonElement.toMetaItem(descriptor)
|
||||||
|
return item.node ?: Meta.EMPTY
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object : MetaFormatFactory {
|
companion object : MetaFormatFactory {
|
||||||
val default = JsonMetaFormat()
|
val DEFAULT_JSON = Json { prettyPrint = true }
|
||||||
|
|
||||||
override fun invoke(meta: Meta, context: Context): MetaFormat = default
|
override fun invoke(meta: Meta, context: Context): MetaFormat = default
|
||||||
|
|
||||||
override val name: Name = super.name + "json"
|
override val shortName = "json"
|
||||||
override val key: Short = 0x4a53//"JS"
|
override val key: Short = 0x4a53//"JS"
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
private val default = JsonMetaFormat()
|
||||||
* @param descriptor reserved for custom serialization in future
|
|
||||||
*/
|
|
||||||
fun Value.toJson(descriptor: ValueDescriptor? = null): JsonElement {
|
|
||||||
return if (isList()) {
|
|
||||||
JsonArray(list.map { it.toJson() })
|
|
||||||
} else {
|
|
||||||
when (type) {
|
|
||||||
ValueType.NUMBER -> JsonPrimitive(number)
|
|
||||||
ValueType.STRING -> JsonPrimitive(string)
|
|
||||||
ValueType.BOOLEAN -> JsonPrimitive(boolean)
|
|
||||||
ValueType.NULL -> JsonNull
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Use these methods to customize JSON key mapping
|
override fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor?) =
|
||||||
private fun NameToken.toJsonKey(descriptor: ItemDescriptor?) = toString()
|
default.run { writeMeta(meta, descriptor) }
|
||||||
|
|
||||||
private fun NodeDescriptor?.getDescriptor(key: String) = this?.items?.get(key)
|
override fun Input.readMeta(descriptor: NodeDescriptor?): Meta =
|
||||||
|
default.run { readMeta(descriptor) }
|
||||||
fun Meta.toJson(descriptor: NodeDescriptor? = null): JsonObject {
|
|
||||||
|
|
||||||
//TODO search for same name siblings and arrange them into arrays
|
|
||||||
val map = this.items.entries.associate { (name, item) ->
|
|
||||||
val itemDescriptor = descriptor?.items?.get(name.body)
|
|
||||||
val key = name.toJsonKey(itemDescriptor)
|
|
||||||
val value = when (item) {
|
|
||||||
is MetaItem.ValueItem -> {
|
|
||||||
item.value.toJson(itemDescriptor as? ValueDescriptor)
|
|
||||||
}
|
|
||||||
is MetaItem.NodeItem -> {
|
|
||||||
item.node.toJson(itemDescriptor as? NodeDescriptor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
key to value
|
|
||||||
}
|
|
||||||
return JsonObject(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun JsonElement.toMeta(descriptor: NodeDescriptor? = null): Meta {
|
|
||||||
return when (val item = toMetaItem(descriptor)) {
|
|
||||||
is MetaItem.NodeItem<*> -> item.node
|
|
||||||
is MetaItem.ValueItem ->item.value.toMeta()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun JsonPrimitive.toValue(descriptor: ValueDescriptor?): Value {
|
|
||||||
return when (this) {
|
|
||||||
JsonNull -> Null
|
|
||||||
else -> this.content.parseValue() // Optimize number and boolean parsing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun JsonElement.toMetaItem(descriptor: ItemDescriptor? = null): MetaItem<JsonMeta> = when (this) {
|
|
||||||
is JsonPrimitive -> {
|
|
||||||
val value = this.toValue(descriptor as? ValueDescriptor)
|
|
||||||
MetaItem.ValueItem(value)
|
|
||||||
}
|
|
||||||
is JsonObject -> {
|
|
||||||
val meta = JsonMeta(this, descriptor as? NodeDescriptor)
|
|
||||||
MetaItem.NodeItem(meta)
|
|
||||||
}
|
|
||||||
is JsonArray -> {
|
|
||||||
if (this.all { it is JsonPrimitive }) {
|
|
||||||
val value = if (isEmpty()) {
|
|
||||||
Null
|
|
||||||
} else {
|
|
||||||
ListValue(
|
|
||||||
map<JsonElement, Value> {
|
|
||||||
//We already checked that all values are primitives
|
|
||||||
(it as JsonPrimitive).toValue(descriptor as? ValueDescriptor)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
MetaItem.ValueItem(value)
|
|
||||||
} else {
|
|
||||||
json {
|
|
||||||
"@value" to this@toMetaItem
|
|
||||||
}.toMetaItem(descriptor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class JsonMeta(val json: JsonObject, val descriptor: NodeDescriptor? = null) : MetaBase() {
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
private operator fun MutableMap<String, MetaItem<JsonMeta>>.set(key: String, value: JsonElement): Unit {
|
|
||||||
val itemDescriptor = descriptor.getDescriptor(key)
|
|
||||||
//use name from descriptor in case descriptor name differs from json key
|
|
||||||
val name = itemDescriptor?.name ?: key
|
|
||||||
return when (value) {
|
|
||||||
is JsonPrimitive -> {
|
|
||||||
this[name] = MetaItem.ValueItem(value.toValue(itemDescriptor as? ValueDescriptor)) as MetaItem<JsonMeta>
|
|
||||||
}
|
|
||||||
is JsonObject -> {
|
|
||||||
this[name] = MetaItem.NodeItem(JsonMeta(value, itemDescriptor as? NodeDescriptor))
|
|
||||||
}
|
|
||||||
is JsonArray -> {
|
|
||||||
when {
|
|
||||||
value.all { it is JsonPrimitive } -> {
|
|
||||||
val listValue = ListValue(
|
|
||||||
value.map {
|
|
||||||
//We already checked that all values are primitives
|
|
||||||
(it as JsonPrimitive).toValue(itemDescriptor as? ValueDescriptor)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
this[name] = MetaItem.ValueItem(listValue) as MetaItem<JsonMeta>
|
|
||||||
}
|
|
||||||
else -> value.forEachIndexed { index, jsonElement ->
|
|
||||||
this["$name[$index]"] = jsonElement.toMetaItem(itemDescriptor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override val items: Map<NameToken, MetaItem<JsonMeta>> by lazy {
|
|
||||||
val map = HashMap<String, MetaItem<JsonMeta>>()
|
|
||||||
json.forEach { (key, value) -> map[key] = value }
|
|
||||||
map.mapKeys { it.key.toName().first()!! }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,13 +1,14 @@
|
|||||||
package hep.dataforge.io
|
package hep.dataforge.io
|
||||||
|
|
||||||
import hep.dataforge.context.Context
|
import hep.dataforge.context.Context
|
||||||
import hep.dataforge.descriptors.NodeDescriptor
|
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||||
import hep.dataforge.io.MetaFormatFactory.Companion.META_FORMAT_TYPE
|
import hep.dataforge.io.MetaFormatFactory.Companion.META_FORMAT_TYPE
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.names.asName
|
import hep.dataforge.names.asName
|
||||||
|
import hep.dataforge.names.plus
|
||||||
import hep.dataforge.provider.Type
|
import hep.dataforge.provider.Type
|
||||||
import kotlinx.io.core.*
|
import kotlinx.io.*
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -27,12 +28,14 @@ interface MetaFormat : IOFormat<Meta> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Type(META_FORMAT_TYPE)
|
@Type(META_FORMAT_TYPE)
|
||||||
interface MetaFormatFactory : IOFormatFactory<Meta> {
|
interface MetaFormatFactory : IOFormatFactory<Meta>, MetaFormat {
|
||||||
override val name: Name get() = "meta".asName()
|
val shortName: String
|
||||||
|
|
||||||
|
override val name: Name get() = "meta".asName() + shortName
|
||||||
|
|
||||||
override val type: KClass<out Meta> get() = Meta::class
|
override val type: KClass<out Meta> get() = Meta::class
|
||||||
|
|
||||||
val key: Short
|
val key: Short get() = name.hashCode().toShort()
|
||||||
|
|
||||||
override operator fun invoke(meta: Meta, context: Context): MetaFormat
|
override operator fun invoke(meta: Meta, context: Context): MetaFormat
|
||||||
|
|
||||||
@ -41,24 +44,16 @@ interface MetaFormatFactory : IOFormatFactory<Meta> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Meta.toString(format: MetaFormat): String = buildPacket {
|
fun Meta.toString(format: MetaFormat): String = buildBytes {
|
||||||
format.run { writeObject(this@toString) }
|
format.run { writeObject(this@toString) }
|
||||||
}.readText()
|
}.toByteArray().decodeToString()
|
||||||
|
|
||||||
fun Meta.toString(formatFactory: MetaFormatFactory): String = toString(formatFactory())
|
fun Meta.toString(formatFactory: MetaFormatFactory): String = toString(formatFactory())
|
||||||
|
|
||||||
fun Meta.toBytes(format: MetaFormat = JsonMetaFormat.default): ByteReadPacket = buildPacket {
|
|
||||||
format.run { writeObject(this@toBytes) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun MetaFormat.parse(str: String): Meta {
|
fun MetaFormat.parse(str: String): Meta {
|
||||||
return buildPacket { writeText(str) }.readObject()
|
return str.encodeToByteArray().read { readObject() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun MetaFormatFactory.parse(str: String): Meta = invoke().parse(str)
|
fun MetaFormatFactory.parse(str: String, formatMeta: Meta): Meta = invoke(formatMeta).parse(str)
|
||||||
|
|
||||||
fun MetaFormat.fromBytes(packet: ByteReadPacket): Meta {
|
|
||||||
return packet.readObject()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,43 +7,51 @@ import hep.dataforge.meta.string
|
|||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.names.plus
|
import hep.dataforge.names.plus
|
||||||
import hep.dataforge.names.toName
|
import hep.dataforge.names.toName
|
||||||
import kotlinx.io.charsets.Charsets
|
import kotlinx.io.*
|
||||||
import kotlinx.io.core.*
|
import kotlinx.io.text.readRawString
|
||||||
|
import kotlinx.io.text.writeRawString
|
||||||
|
|
||||||
@ExperimentalUnsignedTypes
|
@ExperimentalIoApi
|
||||||
class TaggedEnvelopeFormat(
|
class TaggedEnvelopeFormat(
|
||||||
val io: IOPlugin,
|
val io: IOPlugin,
|
||||||
val version: VERSION = TaggedEnvelopeFormat.VERSION.DF02
|
val version: VERSION = VERSION.DF02
|
||||||
) : EnvelopeFormat {
|
) : EnvelopeFormat {
|
||||||
|
|
||||||
// private val metaFormat = io.metaFormat(metaFormatKey)
|
// private val metaFormat = io.metaFormat(metaFormatKey)
|
||||||
// ?: error("Meta format with key $metaFormatKey could not be resolved in $io")
|
// ?: error("Meta format with key $metaFormatKey could not be resolved in $io")
|
||||||
|
|
||||||
|
|
||||||
private fun Tag.toBytes(): ByteReadPacket = buildPacket(24) {
|
private fun Tag.toBytes() = buildBytes(24) {
|
||||||
writeText(START_SEQUENCE)
|
writeRawString(START_SEQUENCE)
|
||||||
writeText(version.name)
|
writeRawString(version.name)
|
||||||
writeShort(metaFormatKey)
|
writeShort(metaFormatKey)
|
||||||
writeUInt(metaSize)
|
writeUInt(metaSize)
|
||||||
when (version) {
|
when (version) {
|
||||||
TaggedEnvelopeFormat.VERSION.DF02 -> {
|
VERSION.DF02 -> {
|
||||||
writeUInt(dataSize.toUInt())
|
writeUInt(dataSize.toUInt())
|
||||||
}
|
}
|
||||||
TaggedEnvelopeFormat.VERSION.DF03 -> {
|
VERSION.DF03 -> {
|
||||||
writeULong(dataSize)
|
writeULong(dataSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
writeText(END_SEQUENCE)
|
writeRawString(END_SEQUENCE)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) {
|
override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) {
|
||||||
val metaFormat = metaFormatFactory.invoke(formatMeta, io.context)
|
val metaFormat = metaFormatFactory.invoke(formatMeta, io.context)
|
||||||
val metaBytes = metaFormat.writeBytes(envelope.meta)
|
val metaBytes = metaFormat.writeBytes(envelope.meta)
|
||||||
val tag = Tag(metaFormatFactory.key, metaBytes.size.toUInt() + 2u, envelope.data?.size ?: 0.toULong())
|
val actualSize: ULong = if (envelope.data == null) {
|
||||||
writePacket(tag.toBytes())
|
0
|
||||||
writeFully(metaBytes)
|
} else {
|
||||||
writeText("\r\n")
|
envelope.data?.size ?: Binary.INFINITE
|
||||||
envelope.data?.read { copyTo(this@writeEnvelope) }
|
}.toULong()
|
||||||
|
val tag = Tag(metaFormatFactory.key, metaBytes.size.toUInt() + 2u, actualSize)
|
||||||
|
writeBinary(tag.toBytes())
|
||||||
|
writeBinary(metaBytes)
|
||||||
|
writeRawString("\r\n")
|
||||||
|
envelope.data?.let {
|
||||||
|
writeBinary(it)
|
||||||
|
}
|
||||||
flush()
|
flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,11 +67,15 @@ class TaggedEnvelopeFormat(
|
|||||||
val metaFormat = io.metaFormat(tag.metaFormatKey)
|
val metaFormat = io.metaFormat(tag.metaFormatKey)
|
||||||
?: error("Meta format with key ${tag.metaFormatKey} not found")
|
?: error("Meta format with key ${tag.metaFormatKey} not found")
|
||||||
|
|
||||||
val metaPacket = ByteReadPacket(readBytes(tag.metaSize.toInt()))
|
val meta: Meta = limit(tag.metaSize.toInt()).run {
|
||||||
val dataBytes = readBytes(tag.dataSize.toInt())
|
metaFormat.run {
|
||||||
|
readObject()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val meta = metaFormat.run { metaPacket.readObject() }
|
val data = ByteArray(tag.dataSize.toInt()).also { readArray(it) }.asBinary()
|
||||||
return SimpleEnvelope(meta, ArrayBinary(dataBytes))
|
|
||||||
|
return SimpleEnvelope(meta, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun Input.readPartial(): PartialEnvelope {
|
override fun Input.readPartial(): PartialEnvelope {
|
||||||
@ -72,8 +84,11 @@ class TaggedEnvelopeFormat(
|
|||||||
val metaFormat = io.metaFormat(tag.metaFormatKey)
|
val metaFormat = io.metaFormat(tag.metaFormatKey)
|
||||||
?: error("Meta format with key ${tag.metaFormatKey} not found")
|
?: error("Meta format with key ${tag.metaFormatKey} not found")
|
||||||
|
|
||||||
val metaPacket = ByteReadPacket(readBytes(tag.metaSize.toInt()))
|
val meta: Meta = limit(tag.metaSize.toInt()).run {
|
||||||
val meta = metaFormat.run { metaPacket.readObject() }
|
metaFormat.run {
|
||||||
|
readObject()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return PartialEnvelope(meta, version.tagSize + tag.metaSize, tag.dataSize)
|
return PartialEnvelope(meta, version.tagSize + tag.metaSize, tag.dataSize)
|
||||||
}
|
}
|
||||||
@ -99,16 +114,16 @@ class TaggedEnvelopeFormat(
|
|||||||
val io = context.io
|
val io = context.io
|
||||||
|
|
||||||
val metaFormatName = meta["name"].string?.toName() ?: JsonMetaFormat.name
|
val metaFormatName = meta["name"].string?.toName() ?: JsonMetaFormat.name
|
||||||
val metaFormatFactory = io.metaFormatFactories.find { it.name == metaFormatName }
|
//Check if appropriate factory exists
|
||||||
?: error("Meta format could not be resolved")
|
io.metaFormatFactories.find { it.name == metaFormatName } ?: error("Meta format could not be resolved")
|
||||||
|
|
||||||
return TaggedEnvelopeFormat(io)
|
return TaggedEnvelopeFormat(io)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Input.readTag(version: VERSION): Tag {
|
private fun Input.readTag(version: VERSION): Tag {
|
||||||
val start = readTextExactBytes(2, charset = Charsets.ISO_8859_1)
|
val start = readRawString(2)
|
||||||
if (start != START_SEQUENCE) error("The input is not an envelope")
|
if (start != START_SEQUENCE) error("The input is not an envelope")
|
||||||
val versionString = readTextExactBytes(4, charset = Charsets.ISO_8859_1)
|
val versionString = readRawString(4)
|
||||||
if (version.name != versionString) error("Wrong version of DataForge: expected $version but found $versionString")
|
if (version.name != versionString) error("Wrong version of DataForge: expected $version but found $versionString")
|
||||||
val metaFormatKey = readShort()
|
val metaFormatKey = readShort()
|
||||||
val metaLength = readUInt()
|
val metaLength = readUInt()
|
||||||
@ -116,14 +131,14 @@ class TaggedEnvelopeFormat(
|
|||||||
VERSION.DF02 -> readUInt().toULong()
|
VERSION.DF02 -> readUInt().toULong()
|
||||||
VERSION.DF03 -> readULong()
|
VERSION.DF03 -> readULong()
|
||||||
}
|
}
|
||||||
val end = readTextExactBytes(4, charset = Charsets.ISO_8859_1)
|
val end = readRawString(4)
|
||||||
if (end != END_SEQUENCE) error("The input is not an envelope")
|
if (end != END_SEQUENCE) error("The input is not an envelope")
|
||||||
return Tag(metaFormatKey, metaLength, dataLength)
|
return Tag(metaFormatKey, metaLength, dataLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun peekFormat(io: IOPlugin, input: Input): EnvelopeFormat? {
|
override fun peekFormat(io: IOPlugin, input: Input): EnvelopeFormat? {
|
||||||
return try {
|
return try {
|
||||||
val header = input.readTextExactBytes(6)
|
val header = input.readRawString(6)
|
||||||
when (header.substring(2..5)) {
|
when (header.substring(2..5)) {
|
||||||
VERSION.DF02.name -> TaggedEnvelopeFormat(io, VERSION.DF02)
|
VERSION.DF02.name -> TaggedEnvelopeFormat(io, VERSION.DF02)
|
||||||
VERSION.DF03.name -> TaggedEnvelopeFormat(io, VERSION.DF03)
|
VERSION.DF03.name -> TaggedEnvelopeFormat(io, VERSION.DF03)
|
||||||
@ -134,7 +149,16 @@ class TaggedEnvelopeFormat(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val default by lazy { invoke() }
|
private val default by lazy { invoke() }
|
||||||
|
|
||||||
|
override fun Input.readPartial(): PartialEnvelope =
|
||||||
|
default.run { readPartial() }
|
||||||
|
|
||||||
|
override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) =
|
||||||
|
default.run { writeEnvelope(envelope, metaFormatFactory, formatMeta) }
|
||||||
|
|
||||||
|
override fun Input.readObject(): Envelope =
|
||||||
|
default.run { readObject() }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -3,9 +3,13 @@ package hep.dataforge.io
|
|||||||
import hep.dataforge.context.Context
|
import hep.dataforge.context.Context
|
||||||
import hep.dataforge.meta.*
|
import hep.dataforge.meta.*
|
||||||
import hep.dataforge.names.asName
|
import hep.dataforge.names.asName
|
||||||
import kotlinx.io.core.*
|
import kotlinx.io.*
|
||||||
import kotlinx.serialization.toUtf8Bytes
|
import kotlinx.io.text.readRawString
|
||||||
|
import kotlinx.io.text.readUtf8Line
|
||||||
|
import kotlinx.io.text.writeRawString
|
||||||
|
import kotlinx.io.text.writeUtf8String
|
||||||
|
|
||||||
|
@ExperimentalIoApi
|
||||||
class TaglessEnvelopeFormat(
|
class TaglessEnvelopeFormat(
|
||||||
val io: IOPlugin,
|
val io: IOPlugin,
|
||||||
meta: Meta = EmptyMeta
|
meta: Meta = EmptyMeta
|
||||||
@ -15,40 +19,46 @@ class TaglessEnvelopeFormat(
|
|||||||
private val dataStart = meta[DATA_START_PROPERTY].string ?: DEFAULT_DATA_START
|
private val dataStart = meta[DATA_START_PROPERTY].string ?: DEFAULT_DATA_START
|
||||||
|
|
||||||
private fun Output.writeProperty(key: String, value: Any) {
|
private fun Output.writeProperty(key: String, value: Any) {
|
||||||
writeText("#? $key: $value;\r\n")
|
writeUtf8String("#? $key: $value;\r\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) {
|
override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) {
|
||||||
val metaFormat = metaFormatFactory(formatMeta, io.context)
|
val metaFormat = metaFormatFactory(formatMeta, io.context)
|
||||||
|
|
||||||
//printing header
|
//printing header
|
||||||
writeText(TAGLESS_ENVELOPE_HEADER + "\r\n")
|
writeRawString(TAGLESS_ENVELOPE_HEADER + "\r\n")
|
||||||
|
|
||||||
//printing all properties
|
//printing all properties
|
||||||
writeProperty(META_TYPE_PROPERTY, metaFormatFactory.type)
|
writeProperty(META_TYPE_PROPERTY, metaFormatFactory.shortName)
|
||||||
//TODO add optional metaFormat properties
|
//TODO add optional metaFormat properties
|
||||||
writeProperty(DATA_LENGTH_PROPERTY, envelope.data?.size ?: 0)
|
val actualSize: Int = if (envelope.data == null) {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
envelope.data?.size ?: Binary.INFINITE
|
||||||
|
}
|
||||||
|
|
||||||
|
writeProperty(DATA_LENGTH_PROPERTY, actualSize)
|
||||||
|
|
||||||
//Printing meta
|
//Printing meta
|
||||||
if (!envelope.meta.isEmpty()) {
|
if (!envelope.meta.isEmpty()) {
|
||||||
val metaBytes = metaFormat.writeBytes(envelope.meta)
|
val metaBytes = metaFormat.writeBytes(envelope.meta)
|
||||||
writeProperty(META_LENGTH_PROPERTY, metaBytes.size)
|
writeProperty(META_LENGTH_PROPERTY, metaBytes.size + 2)
|
||||||
writeText(metaStart + "\r\n")
|
writeUtf8String(metaStart + "\r\n")
|
||||||
writeFully(metaBytes)
|
writeBinary(metaBytes)
|
||||||
writeText("\r\n")
|
writeRawString("\r\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
//Printing data
|
//Printing data
|
||||||
envelope.data?.let { data ->
|
envelope.data?.let { data ->
|
||||||
writeText(dataStart + "\r\n")
|
writeUtf8String(dataStart + "\r\n")
|
||||||
writeFully(data.toBytes())
|
writeBinary(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun Input.readObject(): Envelope {
|
override fun Input.readObject(): Envelope {
|
||||||
var line: String = ""
|
var line: String
|
||||||
do {
|
do {
|
||||||
line = readUTF8Line() ?: error("Input does not contain tagless envelope header")
|
line = readUtf8Line() // ?: error("Input does not contain tagless envelope header")
|
||||||
} while (!line.startsWith(TAGLESS_ENVELOPE_HEADER))
|
} while (!line.startsWith(TAGLESS_ENVELOPE_HEADER))
|
||||||
val properties = HashMap<String, String>()
|
val properties = HashMap<String, String>()
|
||||||
|
|
||||||
@ -60,19 +70,20 @@ class TaglessEnvelopeFormat(
|
|||||||
val (key, value) = match.destructured
|
val (key, value) = match.destructured
|
||||||
properties[key] = value
|
properties[key] = value
|
||||||
}
|
}
|
||||||
line = readUTF8Line() ?: return SimpleEnvelope(Meta.empty, null)
|
//If can't read line, return envelope without data
|
||||||
|
if (eof()) return SimpleEnvelope(Meta.EMPTY, null)
|
||||||
|
line = readUtf8Line()
|
||||||
}
|
}
|
||||||
|
|
||||||
var meta: Meta = EmptyMeta
|
var meta: Meta = EmptyMeta
|
||||||
|
|
||||||
if (line.startsWith(metaStart)) {
|
if (line.startsWith(metaStart)) {
|
||||||
val metaFormat = properties[META_TYPE_PROPERTY]?.let { io.metaFormat(it) } ?: JsonMetaFormat.default
|
val metaFormat = properties[META_TYPE_PROPERTY]?.let { io.metaFormat(it) } ?: JsonMetaFormat
|
||||||
val metaSize = properties.get(META_LENGTH_PROPERTY)?.toInt()
|
val metaSize = properties[META_LENGTH_PROPERTY]?.toInt()
|
||||||
meta = if (metaSize != null) {
|
meta = if (metaSize != null) {
|
||||||
val metaPacket = buildPacket {
|
limit(metaSize).run {
|
||||||
writeFully(readBytes(metaSize))
|
metaFormat.run { readObject() }
|
||||||
}
|
}
|
||||||
metaFormat.run { metaPacket.readObject() }
|
|
||||||
} else {
|
} else {
|
||||||
metaFormat.run {
|
metaFormat.run {
|
||||||
readObject()
|
readObject()
|
||||||
@ -81,17 +92,22 @@ class TaglessEnvelopeFormat(
|
|||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
line = readUTF8Line() ?: return SimpleEnvelope(meta, null)
|
try {
|
||||||
|
line = readUtf8Line()
|
||||||
|
} catch (ex: EOFException) {
|
||||||
//returning an Envelope without data if end of input is reached
|
//returning an Envelope without data if end of input is reached
|
||||||
|
return SimpleEnvelope(meta, null)
|
||||||
|
}
|
||||||
} while (!line.startsWith(dataStart))
|
} while (!line.startsWith(dataStart))
|
||||||
|
|
||||||
val data: Binary? = if (properties.containsKey(DATA_LENGTH_PROPERTY)) {
|
val data: Binary? = if (properties.containsKey(DATA_LENGTH_PROPERTY)) {
|
||||||
val bytes = ByteArray(properties[DATA_LENGTH_PROPERTY]!!.toInt())
|
val bytes = ByteArray(properties[DATA_LENGTH_PROPERTY]!!.toInt())
|
||||||
readFully(bytes)
|
readArray(bytes)
|
||||||
bytes.asBinary()
|
bytes.asBinary()
|
||||||
} else {
|
} else {
|
||||||
val bytes = readBytes()
|
ArrayBinary.write {
|
||||||
bytes.asBinary()
|
writeInput(this@readObject)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return SimpleEnvelope(meta, data)
|
return SimpleEnvelope(meta, data)
|
||||||
@ -99,10 +115,10 @@ class TaglessEnvelopeFormat(
|
|||||||
|
|
||||||
override fun Input.readPartial(): PartialEnvelope {
|
override fun Input.readPartial(): PartialEnvelope {
|
||||||
var offset = 0u
|
var offset = 0u
|
||||||
var line: String = ""
|
var line: String
|
||||||
do {
|
do {
|
||||||
line = readUTF8Line() ?: error("Input does not contain tagless envelope header")
|
line = readUtf8Line()// ?: error("Input does not contain tagless envelope header")
|
||||||
offset += line.toUtf8Bytes().size.toUInt()
|
offset += line.encodeToByteArray().size.toUInt()
|
||||||
} while (!line.startsWith(TAGLESS_ENVELOPE_HEADER))
|
} while (!line.startsWith(TAGLESS_ENVELOPE_HEADER))
|
||||||
val properties = HashMap<String, String>()
|
val properties = HashMap<String, String>()
|
||||||
|
|
||||||
@ -114,30 +130,32 @@ class TaglessEnvelopeFormat(
|
|||||||
val (key, value) = match.destructured
|
val (key, value) = match.destructured
|
||||||
properties[key] = value
|
properties[key] = value
|
||||||
}
|
}
|
||||||
line = readUTF8Line() ?: return PartialEnvelope(Meta.empty, offset.toUInt(), 0.toULong())
|
try {
|
||||||
offset += line.toUtf8Bytes().size.toUInt()
|
line = readUtf8Line()
|
||||||
|
offset += line.encodeToByteArray().size.toUInt()
|
||||||
|
} catch (ex: EOFException) {
|
||||||
|
return PartialEnvelope(Meta.EMPTY, offset.toUInt(), 0.toULong())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var meta: Meta = EmptyMeta
|
var meta: Meta = EmptyMeta
|
||||||
|
|
||||||
if (line.startsWith(metaStart)) {
|
if (line.startsWith(metaStart)) {
|
||||||
val metaFormat = properties[META_TYPE_PROPERTY]?.let { io.metaFormat(it) } ?: JsonMetaFormat.default
|
val metaFormat = properties[META_TYPE_PROPERTY]?.let { io.metaFormat(it) } ?: JsonMetaFormat
|
||||||
|
val metaSize = properties[META_LENGTH_PROPERTY]?.toInt()
|
||||||
val metaSize = properties.get(META_LENGTH_PROPERTY)?.toInt()
|
|
||||||
meta = if (metaSize != null) {
|
meta = if (metaSize != null) {
|
||||||
val metaPacket = buildPacket {
|
|
||||||
writeFully(readBytes(metaSize))
|
|
||||||
}
|
|
||||||
offset += metaSize.toUInt()
|
offset += metaSize.toUInt()
|
||||||
metaFormat.run { metaPacket.readObject() }
|
limit(metaSize).run {
|
||||||
|
metaFormat.run { readObject() }
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
error("Can't partially read an envelope with undefined meta size")
|
error("Can't partially read an envelope with undefined meta size")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
line = readUTF8Line() ?: return PartialEnvelope(Meta.empty, offset.toUInt(), 0.toULong())
|
line = readUtf8Line() //?: return PartialEnvelope(Meta.EMPTY, offset.toUInt(), 0.toULong())
|
||||||
offset += line.toUtf8Bytes().size.toUInt()
|
offset += line.encodeToByteArray().size.toUInt()
|
||||||
//returning an Envelope without data if end of input is reached
|
//returning an Envelope without data if end of input is reached
|
||||||
} while (!line.startsWith(dataStart))
|
} while (!line.startsWith(dataStart))
|
||||||
|
|
||||||
@ -170,13 +188,21 @@ class TaglessEnvelopeFormat(
|
|||||||
return TaglessEnvelopeFormat(context.io, meta)
|
return TaglessEnvelopeFormat(context.io, meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
val default by lazy { invoke() }
|
private val default by lazy { invoke() }
|
||||||
|
|
||||||
|
override fun Input.readPartial(): PartialEnvelope =
|
||||||
|
default.run { readPartial() }
|
||||||
|
|
||||||
|
override fun Output.writeEnvelope(envelope: Envelope, metaFormatFactory: MetaFormatFactory, formatMeta: Meta) =
|
||||||
|
default.run { writeEnvelope(envelope, metaFormatFactory, formatMeta) }
|
||||||
|
|
||||||
|
override fun Input.readObject(): Envelope =
|
||||||
|
default.run { readObject() }
|
||||||
|
|
||||||
override fun peekFormat(io: IOPlugin, input: Input): EnvelopeFormat? {
|
override fun peekFormat(io: IOPlugin, input: Input): EnvelopeFormat? {
|
||||||
return try {
|
return try {
|
||||||
val buffer = ByteArray(TAGLESS_ENVELOPE_HEADER.length)
|
val string = input.readRawString(TAGLESS_ENVELOPE_HEADER.length)
|
||||||
input.readFully(buffer)
|
return if (string == TAGLESS_ENVELOPE_HEADER) {
|
||||||
return if (buffer.toString() == TAGLESS_ENVELOPE_HEADER) {
|
|
||||||
TaglessEnvelopeFormat(io)
|
TaglessEnvelopeFormat(io)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
|
@ -6,6 +6,7 @@ import hep.dataforge.io.*
|
|||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
import hep.dataforge.meta.get
|
import hep.dataforge.meta.get
|
||||||
import hep.dataforge.meta.int
|
import hep.dataforge.meta.int
|
||||||
|
import hep.dataforge.meta.scheme.int
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
class RemoteFunctionClient(override val context: Context, val responder: Responder) : FunctionServer, ContextAware {
|
class RemoteFunctionClient(override val context: Context, val responder: Responder) : FunctionServer, ContextAware {
|
||||||
|
@ -8,6 +8,7 @@ import hep.dataforge.io.Responder
|
|||||||
import hep.dataforge.io.type
|
import hep.dataforge.io.type
|
||||||
import hep.dataforge.meta.get
|
import hep.dataforge.meta.get
|
||||||
import hep.dataforge.meta.int
|
import hep.dataforge.meta.int
|
||||||
|
import hep.dataforge.meta.scheme.int
|
||||||
|
|
||||||
class RemoteFunctionServer(
|
class RemoteFunctionServer(
|
||||||
override val context: Context,
|
override val context: Context,
|
||||||
|
@ -1,141 +0,0 @@
|
|||||||
package hep.dataforge.io.serialization
|
|
||||||
|
|
||||||
import hep.dataforge.io.toJson
|
|
||||||
import hep.dataforge.io.toMeta
|
|
||||||
import hep.dataforge.meta.*
|
|
||||||
import hep.dataforge.names.NameToken
|
|
||||||
import hep.dataforge.values.*
|
|
||||||
import kotlinx.serialization.*
|
|
||||||
import kotlinx.serialization.internal.*
|
|
||||||
import kotlinx.serialization.json.JsonInput
|
|
||||||
import kotlinx.serialization.json.JsonObjectSerializer
|
|
||||||
import kotlinx.serialization.json.JsonOutput
|
|
||||||
|
|
||||||
|
|
||||||
@Serializer(Value::class)
|
|
||||||
object ValueSerializer : KSerializer<Value> {
|
|
||||||
private val valueTypeSerializer = EnumSerializer(ValueType::class)
|
|
||||||
private val listSerializer by lazy { ArrayListSerializer(ValueSerializer) }
|
|
||||||
|
|
||||||
override val descriptor: SerialDescriptor = descriptor("hep.dataforge.values.Value") {
|
|
||||||
boolean("isList")
|
|
||||||
enum<ValueType>("valueType")
|
|
||||||
element("value", null)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Decoder.decodeValue(): Value {
|
|
||||||
return when (decode(valueTypeSerializer)) {
|
|
||||||
ValueType.NULL -> Null
|
|
||||||
ValueType.NUMBER -> decodeDouble().asValue() //TODO differentiate?
|
|
||||||
ValueType.BOOLEAN -> decodeBoolean().asValue()
|
|
||||||
ValueType.STRING -> decodeString().asValue()
|
|
||||||
else -> decodeString().parseValue()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override fun deserialize(decoder: Decoder): Value {
|
|
||||||
val isList = decoder.decodeBoolean()
|
|
||||||
return if (isList) {
|
|
||||||
listSerializer.deserialize(decoder).asValue()
|
|
||||||
} else {
|
|
||||||
decoder.decodeValue()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Encoder.encodeValue(value: Value) {
|
|
||||||
encode(valueTypeSerializer, value.type)
|
|
||||||
when (value.type) {
|
|
||||||
ValueType.NULL -> {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
ValueType.NUMBER -> encodeDouble(value.double)
|
|
||||||
ValueType.BOOLEAN -> encodeBoolean(value.boolean)
|
|
||||||
ValueType.STRING -> encodeString(value.string)
|
|
||||||
else -> encodeString(value.string)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, obj: Value) {
|
|
||||||
encoder.encodeBoolean(obj.isList())
|
|
||||||
if (obj.isList()) {
|
|
||||||
listSerializer.serialize(encoder, obj.list)
|
|
||||||
} else {
|
|
||||||
encoder.encodeValue(obj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializer(MetaItem::class)
|
|
||||||
object MetaItemSerializer : KSerializer<MetaItem<*>> {
|
|
||||||
override val descriptor: SerialDescriptor = descriptor("MetaItem") {
|
|
||||||
boolean("isNode")
|
|
||||||
element("value", null)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override fun deserialize(decoder: Decoder): MetaItem<*> {
|
|
||||||
val isNode = decoder.decodeBoolean()
|
|
||||||
return if (isNode) {
|
|
||||||
MetaItem.NodeItem(decoder.decode(MetaSerializer))
|
|
||||||
} else {
|
|
||||||
MetaItem.ValueItem(decoder.decode(ValueSerializer))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, obj: MetaItem<*>) {
|
|
||||||
encoder.encodeBoolean(obj is MetaItem.NodeItem)
|
|
||||||
when (obj) {
|
|
||||||
is MetaItem.NodeItem -> MetaSerializer.serialize(encoder, obj.node)
|
|
||||||
is MetaItem.ValueItem -> ValueSerializer.serialize(encoder, obj.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class DeserializedMeta(override val items: Map<NameToken, MetaItem<*>>) : MetaBase()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serialized for meta
|
|
||||||
*/
|
|
||||||
@Serializer(Meta::class)
|
|
||||||
object MetaSerializer : KSerializer<Meta> {
|
|
||||||
private val mapSerializer = HashMapSerializer(
|
|
||||||
StringSerializer,
|
|
||||||
MetaItemSerializer
|
|
||||||
)
|
|
||||||
|
|
||||||
override val descriptor: SerialDescriptor = NamedMapClassDescriptor(
|
|
||||||
"hep.dataforge.meta.Meta",
|
|
||||||
StringSerializer.descriptor,
|
|
||||||
MetaItemSerializer.descriptor
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun deserialize(decoder: Decoder): Meta {
|
|
||||||
return if (decoder is JsonInput) {
|
|
||||||
JsonObjectSerializer.deserialize(decoder).toMeta()
|
|
||||||
} else {
|
|
||||||
DeserializedMeta(mapSerializer.deserialize(decoder).mapKeys { NameToken(it.key) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, obj: Meta) {
|
|
||||||
if (encoder is JsonOutput) {
|
|
||||||
JsonObjectSerializer.serialize(encoder, obj.toJson())
|
|
||||||
} else {
|
|
||||||
mapSerializer.serialize(encoder, obj.items.mapKeys { it.key.toString() })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializer(Config::class)
|
|
||||||
object ConfigSerializer : KSerializer<Config> {
|
|
||||||
override val descriptor: SerialDescriptor = MetaSerializer.descriptor
|
|
||||||
|
|
||||||
override fun deserialize(decoder: Decoder): Config {
|
|
||||||
return MetaSerializer.deserialize(decoder).toConfig()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, obj: Config) {
|
|
||||||
MetaSerializer.serialize(encoder, obj)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package hep.dataforge.io.serialization
|
|
||||||
|
|
||||||
import hep.dataforge.names.Name
|
|
||||||
import hep.dataforge.names.NameToken
|
|
||||||
import hep.dataforge.names.toName
|
|
||||||
import kotlinx.serialization.*
|
|
||||||
import kotlinx.serialization.internal.StringDescriptor
|
|
||||||
|
|
||||||
@Serializer(Name::class)
|
|
||||||
object NameSerializer : KSerializer<Name> {
|
|
||||||
override val descriptor: SerialDescriptor = StringDescriptor.withName("Name")
|
|
||||||
|
|
||||||
override fun deserialize(decoder: Decoder): Name {
|
|
||||||
return decoder.decodeString().toName()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, obj: Name) {
|
|
||||||
encoder.encodeString(obj.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializer(NameToken::class)
|
|
||||||
object NameTokenSerializer : KSerializer<NameToken> {
|
|
||||||
override val descriptor: SerialDescriptor = StringDescriptor.withName("NameToken")
|
|
||||||
|
|
||||||
override fun deserialize(decoder: Decoder): NameToken {
|
|
||||||
return decoder.decodeString().toName().first()!!
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, obj: NameToken) {
|
|
||||||
encoder.encodeString(obj.toString())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
package hep.dataforge.io.serialization
|
|
||||||
|
|
||||||
import kotlinx.serialization.CompositeDecoder
|
|
||||||
import kotlinx.serialization.Decoder
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.SerialDescriptor
|
|
||||||
import kotlinx.serialization.internal.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A convenience builder for serial descriptors
|
|
||||||
*/
|
|
||||||
inline class SerialDescriptorBuilder(private val impl: SerialClassDescImpl) {
|
|
||||||
fun element(
|
|
||||||
name: String,
|
|
||||||
descriptor: SerialDescriptor?,
|
|
||||||
isOptional: Boolean = false,
|
|
||||||
vararg annotations: Annotation
|
|
||||||
) {
|
|
||||||
impl.addElement(name, isOptional)
|
|
||||||
descriptor?.let { impl.pushDescriptor(descriptor) }
|
|
||||||
annotations.forEach {
|
|
||||||
impl.pushAnnotation(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun element(
|
|
||||||
name: String,
|
|
||||||
isOptional: Boolean = false,
|
|
||||||
vararg annotations: Annotation,
|
|
||||||
block: SerialDescriptorBuilder.() -> Unit
|
|
||||||
) {
|
|
||||||
impl.addElement(name, isOptional)
|
|
||||||
impl.pushDescriptor(SerialDescriptorBuilder(SerialClassDescImpl(name)).apply(block).build())
|
|
||||||
annotations.forEach {
|
|
||||||
impl.pushAnnotation(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun boolean(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
|
|
||||||
element(name, BooleanDescriptor, isOptional, *annotations)
|
|
||||||
|
|
||||||
fun string(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
|
|
||||||
element(name, StringDescriptor, isOptional, *annotations)
|
|
||||||
|
|
||||||
fun int(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
|
|
||||||
element(name, IntDescriptor, isOptional, *annotations)
|
|
||||||
|
|
||||||
fun double(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
|
|
||||||
element(name, DoubleDescriptor, isOptional, *annotations)
|
|
||||||
|
|
||||||
fun float(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
|
|
||||||
element(name, FloatDescriptor, isOptional, *annotations)
|
|
||||||
|
|
||||||
fun long(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
|
|
||||||
element(name, LongDescriptor, isOptional, *annotations)
|
|
||||||
|
|
||||||
fun doubleArray(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
|
|
||||||
element(name, DoubleArraySerializer.descriptor, isOptional, *annotations)
|
|
||||||
|
|
||||||
inline fun <reified E : Enum<E>> enum(name: String, isOptional: Boolean = false, vararg annotations: Annotation) =
|
|
||||||
element(name, EnumSerializer(E::class).descriptor, isOptional, *annotations)
|
|
||||||
|
|
||||||
fun classAnnotation(a: Annotation) = impl.pushClassAnnotation(a)
|
|
||||||
|
|
||||||
fun build(): SerialDescriptor = impl
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <reified T : Any> KSerializer<T>.descriptor(
|
|
||||||
name: String,
|
|
||||||
block: SerialDescriptorBuilder.() -> Unit
|
|
||||||
): SerialDescriptor =
|
|
||||||
SerialDescriptorBuilder(SerialClassDescImpl(name)).apply(block).build()
|
|
||||||
|
|
||||||
fun Decoder.decodeStructure(
|
|
||||||
desc: SerialDescriptor,
|
|
||||||
vararg typeParams: KSerializer<*> = emptyArray(),
|
|
||||||
block: CompositeDecoder.() -> Unit
|
|
||||||
) {
|
|
||||||
beginStructure(desc, *typeParams).apply(block).endStructure(desc)
|
|
||||||
}
|
|
@ -1,5 +1,7 @@
|
|||||||
package hep.dataforge.io
|
package hep.dataforge.io
|
||||||
|
|
||||||
|
import kotlinx.io.readDouble
|
||||||
|
import kotlinx.io.writeDouble
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
@ -12,16 +14,18 @@ class EnvelopeFormatTest {
|
|||||||
}
|
}
|
||||||
data{
|
data{
|
||||||
writeDouble(22.2)
|
writeDouble(22.2)
|
||||||
|
// repeat(2000){
|
||||||
|
// writeInt(it)
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExperimentalStdlibApi
|
|
||||||
@Test
|
@Test
|
||||||
fun testTaggedFormat(){
|
fun testTaggedFormat(){
|
||||||
TaggedEnvelopeFormat.default.run {
|
TaggedEnvelopeFormat.run {
|
||||||
val bytes = writeBytes(envelope)
|
val byteArray = this.writeByteArray(envelope)
|
||||||
println(bytes.decodeToString())
|
//println(byteArray.decodeToString())
|
||||||
val res = readBytes(bytes)
|
val res = readByteArray(byteArray)
|
||||||
assertEquals(envelope.meta,res.meta)
|
assertEquals(envelope.meta,res.meta)
|
||||||
val double = res.data?.read {
|
val double = res.data?.read {
|
||||||
readDouble()
|
readDouble()
|
||||||
@ -32,10 +36,10 @@ class EnvelopeFormatTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testTaglessFormat(){
|
fun testTaglessFormat(){
|
||||||
TaglessEnvelopeFormat.default.run {
|
TaglessEnvelopeFormat.run {
|
||||||
val bytes = writeBytes(envelope)
|
val byteArray = writeByteArray(envelope)
|
||||||
println(bytes.decodeToString())
|
//println(byteArray.decodeToString())
|
||||||
val res = readBytes(bytes)
|
val res = readByteArray(byteArray)
|
||||||
assertEquals(envelope.meta,res.meta)
|
assertEquals(envelope.meta,res.meta)
|
||||||
val double = res.data?.read {
|
val double = res.data?.read {
|
||||||
readDouble()
|
readDouble()
|
||||||
|
@ -1,16 +1,26 @@
|
|||||||
package hep.dataforge.io
|
package hep.dataforge.io
|
||||||
|
|
||||||
import hep.dataforge.meta.*
|
import hep.dataforge.meta.*
|
||||||
|
import kotlinx.io.Bytes
|
||||||
|
import kotlinx.io.buildBytes
|
||||||
import kotlinx.serialization.json.JsonPrimitive
|
import kotlinx.serialization.json.JsonPrimitive
|
||||||
import kotlinx.serialization.json.json
|
import kotlinx.serialization.json.json
|
||||||
import kotlinx.serialization.json.jsonArray
|
import kotlinx.serialization.json.jsonArray
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
fun Meta.toBytes(format: MetaFormat = JsonMetaFormat): Bytes = buildBytes {
|
||||||
|
format.run { writeObject(this@toBytes) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun MetaFormat.fromBytes(packet: Bytes): Meta {
|
||||||
|
return packet.read { readObject() }
|
||||||
|
}
|
||||||
|
|
||||||
class MetaFormatTest {
|
class MetaFormatTest {
|
||||||
@Test
|
@Test
|
||||||
fun testBinaryMetaFormat() {
|
fun testBinaryMetaFormat() {
|
||||||
val meta = buildMeta {
|
val meta = Meta {
|
||||||
"a" put 22
|
"a" put 22
|
||||||
"node" put {
|
"node" put {
|
||||||
"b" put "DDD"
|
"b" put "DDD"
|
||||||
@ -25,7 +35,7 @@ class MetaFormatTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testJsonMetaFormat() {
|
fun testJsonMetaFormat() {
|
||||||
val meta = buildMeta {
|
val meta = Meta {
|
||||||
"a" put 22
|
"a" put 22
|
||||||
"node" put {
|
"node" put {
|
||||||
"b" put "DDD"
|
"b" put "DDD"
|
||||||
|
@ -1,21 +1,17 @@
|
|||||||
package hep.dataforge.io
|
package hep.dataforge.io
|
||||||
|
|
||||||
import hep.dataforge.io.serialization.MetaItemSerializer
|
import hep.dataforge.meta.Meta
|
||||||
import hep.dataforge.io.serialization.MetaSerializer
|
import hep.dataforge.meta.MetaItem
|
||||||
import hep.dataforge.io.serialization.NameSerializer
|
import hep.dataforge.meta.MetaSerializer
|
||||||
import hep.dataforge.meta.buildMeta
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.names.toName
|
import hep.dataforge.names.toName
|
||||||
import kotlinx.io.charsets.Charsets
|
|
||||||
import kotlinx.io.core.String
|
|
||||||
import kotlinx.serialization.cbor.Cbor
|
import kotlinx.serialization.cbor.Cbor
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class MetaSerializerTest {
|
class MetaSerializerTest {
|
||||||
@Test
|
val meta = Meta {
|
||||||
fun testMetaSerialization() {
|
|
||||||
val meta = buildMeta {
|
|
||||||
"a" put 22
|
"a" put 22
|
||||||
"node" put {
|
"node" put {
|
||||||
"b" put "DDD"
|
"b" put "DDD"
|
||||||
@ -24,6 +20,8 @@ class MetaSerializerTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMetaSerialization() {
|
||||||
val string = Json.indented.stringify(MetaSerializer, meta)
|
val string = Json.indented.stringify(MetaSerializer, meta)
|
||||||
val restored = Json.plain.parse(MetaSerializer, string)
|
val restored = Json.plain.parse(MetaSerializer, string)
|
||||||
assertEquals(restored, meta)
|
assertEquals(restored, meta)
|
||||||
@ -31,17 +29,8 @@ class MetaSerializerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testCborSerialization() {
|
fun testCborSerialization() {
|
||||||
val meta = buildMeta {
|
|
||||||
"a" put 22
|
|
||||||
"node" put {
|
|
||||||
"b" put "DDD"
|
|
||||||
"c" put 11.1
|
|
||||||
"array" put doubleArrayOf(1.0, 2.0, 3.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val bytes = Cbor.dump(MetaSerializer, meta)
|
val bytes = Cbor.dump(MetaSerializer, meta)
|
||||||
println(String(bytes, charset = Charsets.ISO_8859_1))
|
println(bytes.contentToString())
|
||||||
val restored = Cbor.load(MetaSerializer, bytes)
|
val restored = Cbor.load(MetaSerializer, bytes)
|
||||||
assertEquals(restored, meta)
|
assertEquals(restored, meta)
|
||||||
}
|
}
|
||||||
@ -49,13 +38,13 @@ class MetaSerializerTest {
|
|||||||
@Test
|
@Test
|
||||||
fun testNameSerialization() {
|
fun testNameSerialization() {
|
||||||
val name = "a.b.c".toName()
|
val name = "a.b.c".toName()
|
||||||
val string = Json.indented.stringify(NameSerializer, name)
|
val string = Json.indented.stringify(Name.serializer(), name)
|
||||||
val restored = Json.plain.parse(NameSerializer, string)
|
val restored = Json.plain.parse(Name.serializer(), string)
|
||||||
assertEquals(restored, name)
|
assertEquals(restored, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testMetaItemDescriptor() {
|
fun testMetaItemDescriptor() {
|
||||||
val descriptor = MetaItemSerializer.descriptor.getElementDescriptor(0)
|
val descriptor = MetaItem.serializer(MetaSerializer).descriptor.getElementDescriptor(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
package hep.dataforge.io
|
||||||
|
|
||||||
|
import hep.dataforge.meta.DFExperimental
|
||||||
|
import hep.dataforge.meta.get
|
||||||
|
import hep.dataforge.meta.int
|
||||||
|
import hep.dataforge.meta.scheme.int
|
||||||
|
import kotlinx.io.text.writeRawString
|
||||||
|
import kotlinx.io.text.writeUtf8String
|
||||||
|
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
@DFExperimental
|
||||||
|
class MultipartTest {
|
||||||
|
val envelopes = (0..5).map {
|
||||||
|
Envelope {
|
||||||
|
meta {
|
||||||
|
"value" put it
|
||||||
|
}
|
||||||
|
data {
|
||||||
|
writeUtf8String("Hello World $it")
|
||||||
|
repeat(300) {
|
||||||
|
writeRawString("$it ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val partsEnvelope = Envelope {
|
||||||
|
multipart(envelopes, TaggedEnvelopeFormat)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testParts() {
|
||||||
|
TaggedEnvelopeFormat.run {
|
||||||
|
val singleEnvelopeData = writeBytes(envelopes[0])
|
||||||
|
val singleEnvelopeSize = singleEnvelopeData.size
|
||||||
|
val bytes = writeBytes(partsEnvelope)
|
||||||
|
assertTrue(5*singleEnvelopeSize < bytes.size)
|
||||||
|
val reconstructed = bytes.readWith(this)
|
||||||
|
val parts = reconstructed.parts()?.toList() ?: emptyList()
|
||||||
|
assertEquals(2, parts[2].meta["value"].int)
|
||||||
|
println(reconstructed.data!!.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,31 +0,0 @@
|
|||||||
package hep.dataforge.io
|
|
||||||
|
|
||||||
import kotlinx.io.core.Input
|
|
||||||
import kotlinx.io.core.buildPacket
|
|
||||||
import java.nio.channels.FileChannel
|
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.Path
|
|
||||||
import java.nio.file.StandardOpenOption
|
|
||||||
import kotlin.math.min
|
|
||||||
|
|
||||||
@ExperimentalUnsignedTypes
|
|
||||||
class FileBinary(val path: Path, private val offset: UInt = 0u, size: ULong? = null) : RandomAccessBinary {
|
|
||||||
|
|
||||||
override val size: ULong = size ?: (Files.size(path).toULong() - offset).toULong()
|
|
||||||
|
|
||||||
init {
|
|
||||||
if( size != null && Files.size(path) < offset.toLong() + size.toLong()){
|
|
||||||
error("Can't read binary from file. File is to short.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun <R> read(from: UInt, size: UInt, block: Input.() -> R): R {
|
|
||||||
FileChannel.open(path, StandardOpenOption.READ).use {
|
|
||||||
val theSize: UInt = min(size, Files.size(path).toUInt() - offset)
|
|
||||||
val buffer = it.map(FileChannel.MapMode.READ_ONLY, (from + offset).toLong(), theSize.toLong())
|
|
||||||
return buildPacket { writeFully(buffer) }.block()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Path.asBinary(offset: UInt = 0u, size: ULong? = null): FileBinary = FileBinary(this, offset, size)
|
|
@ -1,52 +1,22 @@
|
|||||||
package hep.dataforge.io
|
package hep.dataforge.io
|
||||||
|
|
||||||
import hep.dataforge.meta.EmptyMeta
|
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
import kotlinx.io.nio.asInput
|
import kotlinx.io.Binary
|
||||||
import kotlinx.io.nio.asOutput
|
import kotlinx.io.ExperimentalIoApi
|
||||||
import java.nio.file.Files
|
import kotlinx.io.FileBinary
|
||||||
|
import kotlinx.io.read
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.nio.file.StandardOpenOption
|
|
||||||
|
|
||||||
|
@ExperimentalIoApi
|
||||||
class FileEnvelope internal constructor(val path: Path, val format: EnvelopeFormat) : Envelope {
|
class FileEnvelope internal constructor(val path: Path, val format: EnvelopeFormat) : Envelope {
|
||||||
//TODO do not like this constructor. Hope to replace it later
|
//TODO do not like this constructor. Hope to replace it later
|
||||||
|
|
||||||
private val partialEnvelope: PartialEnvelope
|
private val partialEnvelope: PartialEnvelope = path.read {
|
||||||
|
format.run { readPartial() }
|
||||||
init {
|
|
||||||
val input = Files.newByteChannel(path, StandardOpenOption.READ).asInput()
|
|
||||||
partialEnvelope = format.run { input.use { it.readPartial()} }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override val meta: Meta get() = partialEnvelope.meta
|
override val meta: Meta get() = partialEnvelope.meta
|
||||||
|
|
||||||
override val data: Binary? = FileBinary(path, partialEnvelope.dataOffset, partialEnvelope.dataSize)
|
override val data: Binary? = FileBinary(path, partialEnvelope.dataOffset.toInt(), partialEnvelope.dataSize?.toInt())
|
||||||
}
|
|
||||||
|
|
||||||
fun IOPlugin.readEnvelopeFile(
|
|
||||||
path: Path,
|
|
||||||
formatFactory: EnvelopeFormatFactory = TaggedEnvelopeFormat,
|
|
||||||
formatMeta: Meta = EmptyMeta
|
|
||||||
): FileEnvelope {
|
|
||||||
val format = formatFactory(formatMeta, context)
|
|
||||||
return FileEnvelope(path, format)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun IOPlugin.writeEnvelopeFile(
|
|
||||||
path: Path,
|
|
||||||
envelope: Envelope,
|
|
||||||
formatFactory: EnvelopeFormatFactory = TaggedEnvelopeFormat,
|
|
||||||
formatMeta: Meta = EmptyMeta
|
|
||||||
) {
|
|
||||||
val output = Files.newByteChannel(
|
|
||||||
path,
|
|
||||||
StandardOpenOption.WRITE,
|
|
||||||
StandardOpenOption.CREATE,
|
|
||||||
StandardOpenOption.TRUNCATE_EXISTING
|
|
||||||
).asOutput()
|
|
||||||
|
|
||||||
with(formatFactory(formatMeta, context)) {
|
|
||||||
output.writeObject(envelope)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 hep.dataforge.meta.Meta
|
||||||
import kotlinx.coroutines.asCoroutineDispatcher
|
import kotlinx.coroutines.asCoroutineDispatcher
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.io.streams.writePacket
|
|
||||||
import java.net.Socket
|
import java.net.Socket
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import kotlin.time.ExperimentalTime
|
import kotlin.time.ExperimentalTime
|
||||||
@ -39,7 +38,7 @@ class EnvelopeClient(
|
|||||||
suspend fun close() {
|
suspend fun close() {
|
||||||
try {
|
try {
|
||||||
respond(
|
respond(
|
||||||
Envelope.invoke {
|
Envelope {
|
||||||
type = EnvelopeServer.SHUTDOWN_ENVELOPE_TYPE
|
type = EnvelopeServer.SHUTDOWN_ENVELOPE_TYPE
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -52,14 +51,14 @@ class EnvelopeClient(
|
|||||||
override suspend fun respond(request: Envelope): Envelope = withContext(dispatcher) {
|
override suspend fun respond(request: Envelope): Envelope = withContext(dispatcher) {
|
||||||
//val address = InetSocketAddress(host,port)
|
//val address = InetSocketAddress(host,port)
|
||||||
val socket = Socket(host, port)
|
val socket = Socket(host, port)
|
||||||
val input = socket.getInputStream().asInput()
|
val inputStream = socket.getInputStream()
|
||||||
val output = socket.getOutputStream()
|
val outputStream = socket.getOutputStream()
|
||||||
format.run {
|
format.run {
|
||||||
output.writePacket {
|
outputStream.write {
|
||||||
writeObject(request)
|
writeObject(request)
|
||||||
}
|
}
|
||||||
logger.debug { "Sent request with type ${request.type} to ${socket.remoteSocketAddress}" }
|
logger.debug { "Sent request with type ${request.type} to ${socket.remoteSocketAddress}" }
|
||||||
val res = input.readObject()
|
val res = inputStream.readBlocking { readObject() }
|
||||||
logger.debug { "Received response with type ${res.type} from ${socket.remoteSocketAddress}" }
|
logger.debug { "Received response with type ${res.type} from ${socket.remoteSocketAddress}" }
|
||||||
return@withContext res
|
return@withContext res
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ import hep.dataforge.io.type
|
|||||||
import hep.dataforge.meta.EmptyMeta
|
import hep.dataforge.meta.EmptyMeta
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.io.streams.writePacket
|
|
||||||
import java.net.ServerSocket
|
import java.net.ServerSocket
|
||||||
import java.net.Socket
|
import java.net.Socket
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
@ -71,14 +70,17 @@ class EnvelopeServer(
|
|||||||
|
|
||||||
private fun readSocket(socket: Socket) {
|
private fun readSocket(socket: Socket) {
|
||||||
thread {
|
thread {
|
||||||
val input = socket.getInputStream().asInput()
|
val inputStream = socket.getInputStream()
|
||||||
val outputStream = socket.getOutputStream()
|
val outputStream = socket.getOutputStream()
|
||||||
format.run {
|
format.run {
|
||||||
while (socket.isConnected) {
|
while (socket.isConnected) {
|
||||||
val request = input.readObject()
|
val request = inputStream.readBlocking { readObject() }
|
||||||
logger.debug { "Accepted request with type ${request.type} from ${socket.remoteSocketAddress}" }
|
logger.debug { "Accepted request with type ${request.type} from ${socket.remoteSocketAddress}" }
|
||||||
if (request.type == SHUTDOWN_ENVELOPE_TYPE) {
|
if (request.type == SHUTDOWN_ENVELOPE_TYPE) {
|
||||||
//Echo shutdown command
|
//Echo shutdown command
|
||||||
|
outputStream.write{
|
||||||
|
writeObject(request)
|
||||||
|
}
|
||||||
logger.info { "Accepted graceful shutdown signal from ${socket.inetAddress}" }
|
logger.info { "Accepted graceful shutdown signal from ${socket.inetAddress}" }
|
||||||
socket.close()
|
socket.close()
|
||||||
return@thread
|
return@thread
|
||||||
@ -86,7 +88,7 @@ class EnvelopeServer(
|
|||||||
}
|
}
|
||||||
runBlocking {
|
runBlocking {
|
||||||
val response = responder.respond(request)
|
val response = responder.respond(request)
|
||||||
outputStream.writePacket {
|
outputStream.write {
|
||||||
writeObject(response)
|
writeObject(response)
|
||||||
}
|
}
|
||||||
logger.debug { "Sent response with type ${response.type} to ${socket.remoteSocketAddress}" }
|
logger.debug { "Sent response with type ${response.type} to ${socket.remoteSocketAddress}" }
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
package hep.dataforge.io.tcp
|
|
||||||
|
|
||||||
import kotlinx.io.core.AbstractInput
|
|
||||||
import kotlinx.io.core.Input
|
|
||||||
import kotlinx.io.core.IoBuffer
|
|
||||||
import kotlinx.io.core.IoBuffer.Companion.NoPool
|
|
||||||
import kotlinx.io.core.writePacket
|
|
||||||
import kotlinx.io.streams.readPacketAtMost
|
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modified version of InputStream to Input converter that supports waiting for input
|
|
||||||
*/
|
|
||||||
internal class InputStreamAsInput(
|
|
||||||
private val stream: InputStream
|
|
||||||
) : AbstractInput(pool = NoPool) {
|
|
||||||
|
|
||||||
|
|
||||||
override fun fill(): IoBuffer? {
|
|
||||||
val packet = stream.readPacketAtMost(4096)
|
|
||||||
return pool.borrow().apply {
|
|
||||||
resetForWrite(4096)
|
|
||||||
writePacket(packet)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun closeSource() {
|
|
||||||
stream.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun InputStream.asInput(): Input =
|
|
||||||
InputStreamAsInput(this)
|
|
@ -0,0 +1,62 @@
|
|||||||
|
package hep.dataforge.io.tcp
|
||||||
|
|
||||||
|
import kotlinx.io.Input
|
||||||
|
import kotlinx.io.Output
|
||||||
|
import kotlinx.io.asBinary
|
||||||
|
import kotlinx.io.buffer.Buffer
|
||||||
|
import kotlinx.io.buffer.get
|
||||||
|
import kotlinx.io.buffer.set
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
|
||||||
|
private class InputStreamInput(val source: InputStream, val waitForInput: Boolean = false) : Input() {
|
||||||
|
override fun closeSource() {
|
||||||
|
source.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fill(buffer: Buffer): Int {
|
||||||
|
if (waitForInput) {
|
||||||
|
while (source.available() == 0) {
|
||||||
|
//block until input is available
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var bufferPos = 0
|
||||||
|
do {
|
||||||
|
val byte = source.read()
|
||||||
|
buffer[bufferPos] = byte.toByte()
|
||||||
|
bufferPos++
|
||||||
|
} while (byte > 0 && bufferPos < buffer.size && source.available() > 0)
|
||||||
|
return bufferPos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class OutputStreamOutput(val out: OutputStream) : Output() {
|
||||||
|
override fun flush(source: Buffer, length: Int) {
|
||||||
|
for (i in 0..length) {
|
||||||
|
out.write(source[i].toInt())
|
||||||
|
}
|
||||||
|
out.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun closeSource() {
|
||||||
|
out.flush()
|
||||||
|
out.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun <R> InputStream.read(size: Int, block: Input.() -> R): R {
|
||||||
|
val buffer = ByteArray(size)
|
||||||
|
read(buffer)
|
||||||
|
return buffer.asBinary().read(block)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <R> InputStream.read(block: Input.() -> R): R =
|
||||||
|
InputStreamInput(this, false).block()
|
||||||
|
|
||||||
|
fun <R> InputStream.readBlocking(block: Input.() -> R): R =
|
||||||
|
InputStreamInput(this, true).block()
|
||||||
|
|
||||||
|
fun OutputStream.write(block: Output.() -> Unit) {
|
||||||
|
OutputStreamOutput(this).block()
|
||||||
|
}
|
@ -1,6 +1,9 @@
|
|||||||
package hep.dataforge.io
|
package hep.dataforge.io
|
||||||
|
|
||||||
import hep.dataforge.context.Global
|
import hep.dataforge.context.Global
|
||||||
|
import kotlinx.io.asBinary
|
||||||
|
import kotlinx.io.toByteArray
|
||||||
|
import kotlinx.io.writeDouble
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
@ -21,7 +24,7 @@ class FileBinaryTest {
|
|||||||
@Test
|
@Test
|
||||||
fun testSize() {
|
fun testSize() {
|
||||||
val binary = envelope.data
|
val binary = envelope.data
|
||||||
assertEquals(binary?.size?.toInt(), binary?.toBytes()?.size)
|
assertEquals(binary?.size?.toInt(), binary?.toByteArray()?.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -34,12 +37,12 @@ class FileBinaryTest {
|
|||||||
"b" put 22.2
|
"b" put 22.2
|
||||||
}
|
}
|
||||||
dataType = "hep.dataforge.satellite"
|
dataType = "hep.dataforge.satellite"
|
||||||
dataID = "cellDepositTest" // добавил только что
|
dataID = "cellDepositTest"
|
||||||
data = dataFile.asBinary()
|
data = dataFile.asBinary()
|
||||||
}
|
}
|
||||||
val binary = envelopeFromFile.data!!
|
val binary = envelopeFromFile.data!!
|
||||||
println(binary.toBytes().size)
|
println(binary.toByteArray().size)
|
||||||
assertEquals(binary.size.toInt(), binary.toBytes().size)
|
assertEquals(binary.size.toInt(), binary.toByteArray().size)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,8 +52,7 @@ class FileBinaryTest {
|
|||||||
val tmpPath = Files.createTempFile("dataforge_test", ".df")
|
val tmpPath = Files.createTempFile("dataforge_test", ".df")
|
||||||
Global.io.writeEnvelopeFile(tmpPath, envelope)
|
Global.io.writeEnvelopeFile(tmpPath, envelope)
|
||||||
|
|
||||||
val binary = Global.io.readEnvelopeFile(tmpPath).data!!
|
val binary = Global.io.readEnvelopeFile(tmpPath)?.data!!
|
||||||
assertEquals(binary.size.toInt(), binary.toBytes().size)
|
assertEquals(binary.size.toInt(), binary.toByteArray().size)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package hep.dataforge.io
|
package hep.dataforge.io
|
||||||
|
|
||||||
import hep.dataforge.context.Global
|
import hep.dataforge.context.Global
|
||||||
|
import kotlinx.io.writeDouble
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
@ -22,10 +23,23 @@ class FileEnvelopeTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testFileWriteRead() {
|
fun testFileWriteRead() {
|
||||||
|
Global.io.run {
|
||||||
val tmpPath = Files.createTempFile("dataforge_test", ".df")
|
val tmpPath = Files.createTempFile("dataforge_test", ".df")
|
||||||
Global.io.writeEnvelopeFile(tmpPath,envelope)
|
writeEnvelopeFile(tmpPath, envelope)
|
||||||
println(tmpPath.toUri())
|
println(tmpPath.toUri())
|
||||||
val restored: Envelope = Global.io.readEnvelopeFile(tmpPath)
|
val restored: Envelope = readEnvelopeFile(tmpPath)!!
|
||||||
assertTrue { envelope.contentEquals(restored) }
|
assertTrue { envelope.contentEquals(restored) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testFileWriteReadTagless() {
|
||||||
|
Global.io.run {
|
||||||
|
val tmpPath = Files.createTempFile("dataforge_test_tagless", ".df")
|
||||||
|
writeEnvelopeFile(tmpPath, envelope, envelopeFormat = TaglessEnvelopeFormat)
|
||||||
|
println(tmpPath.toUri())
|
||||||
|
val restored: Envelope = readEnvelopeFile(tmpPath)!!
|
||||||
|
assertTrue { envelope.contentEquals(restored) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,19 +4,20 @@ import hep.dataforge.context.Global
|
|||||||
import hep.dataforge.io.Envelope
|
import hep.dataforge.io.Envelope
|
||||||
import hep.dataforge.io.Responder
|
import hep.dataforge.io.Responder
|
||||||
import hep.dataforge.io.TaggedEnvelopeFormat
|
import hep.dataforge.io.TaggedEnvelopeFormat
|
||||||
import hep.dataforge.io.writeBytes
|
import hep.dataforge.io.writeByteArray
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.io.writeDouble
|
||||||
import org.junit.AfterClass
|
import org.junit.AfterClass
|
||||||
import org.junit.BeforeClass
|
import org.junit.BeforeClass
|
||||||
import org.junit.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.time.ExperimentalTime
|
import kotlin.time.ExperimentalTime
|
||||||
|
|
||||||
@ExperimentalStdlibApi
|
@ExperimentalStdlibApi
|
||||||
object EchoResponder : Responder {
|
object EchoResponder : Responder {
|
||||||
override suspend fun respond(request: Envelope): Envelope {
|
override suspend fun respond(request: Envelope): Envelope {
|
||||||
val string = TaggedEnvelopeFormat().run { writeBytes(request).decodeToString() }
|
val string = TaggedEnvelopeFormat().run { writeByteArray(request).decodeToString() }
|
||||||
println("ECHO:")
|
println("ECHO:")
|
||||||
println(string)
|
println(string)
|
||||||
return request
|
return request
|
||||||
@ -43,7 +44,7 @@ class EnvelopeServerTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test(timeout = 1000)
|
||||||
fun doEchoTest() {
|
fun doEchoTest() {
|
||||||
val request = Envelope.invoke {
|
val request = Envelope.invoke {
|
||||||
type = "test.echo"
|
type = "test.echo"
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
|
import scientifik.serialization
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("scientifik.mpp")
|
id("scientifik.mpp")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
serialization()
|
||||||
|
|
||||||
description = "Meta definition and basic operations on meta"
|
description = "Meta definition and basic operations on meta"
|
@ -1,29 +0,0 @@
|
|||||||
package hep.dataforge.descriptors
|
|
||||||
|
|
||||||
import hep.dataforge.descriptors.Described.Companion.DESCRIPTOR_NODE
|
|
||||||
import hep.dataforge.meta.Meta
|
|
||||||
import hep.dataforge.meta.get
|
|
||||||
import hep.dataforge.meta.node
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An object which provides its descriptor
|
|
||||||
*/
|
|
||||||
interface Described {
|
|
||||||
val descriptor: NodeDescriptor
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val DESCRIPTOR_NODE = "@descriptor"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If meta node supplies explicit descriptor, return it, otherwise try to use descriptor node from meta itself
|
|
||||||
*/
|
|
||||||
val Meta.descriptor: NodeDescriptor?
|
|
||||||
get() {
|
|
||||||
return if (this is Described) {
|
|
||||||
descriptor
|
|
||||||
} else {
|
|
||||||
get(DESCRIPTOR_NODE).node?.let { NodeDescriptor.wrap(it) }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,149 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2018 Alexander Nozik.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package hep.dataforge.descriptors
|
|
||||||
|
|
||||||
import hep.dataforge.values.ValueType
|
|
||||||
import kotlin.reflect.KClass
|
|
||||||
|
|
||||||
@Target(AnnotationTarget.PROPERTY)
|
|
||||||
@MustBeDocumented
|
|
||||||
annotation class ValueDef(
|
|
||||||
val key: String,
|
|
||||||
val type: Array<ValueType> = arrayOf(ValueType.STRING),
|
|
||||||
val multiple: Boolean = false,
|
|
||||||
val def: String = "",
|
|
||||||
val info: String = "",
|
|
||||||
val required: Boolean = true,
|
|
||||||
val allowed: Array<String> = emptyArray(),
|
|
||||||
val enumeration: KClass<*> = Any::class,
|
|
||||||
val tags: Array<String> = emptyArray()
|
|
||||||
)
|
|
||||||
|
|
||||||
@MustBeDocumented
|
|
||||||
annotation class NodeDef(
|
|
||||||
val key: String,
|
|
||||||
val info: String = "",
|
|
||||||
val multiple: Boolean = false,
|
|
||||||
val required: Boolean = false,
|
|
||||||
val tags: Array<String> = emptyArray(),
|
|
||||||
/**
|
|
||||||
* A list of child value descriptors
|
|
||||||
*/
|
|
||||||
val values: Array<ValueDef> = emptyArray(),
|
|
||||||
/**
|
|
||||||
* A target class for this node to describe
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
val type: KClass<*> = Any::class,
|
|
||||||
/**
|
|
||||||
* The DataForge path to the resource containing the description. Following targets are supported:
|
|
||||||
*
|
|
||||||
* 1. resource
|
|
||||||
* 1. file
|
|
||||||
* 1. class
|
|
||||||
* 1. method
|
|
||||||
* 1. property
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* Does not work if [type] is provided
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
val descriptor: String = ""
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Description text for meta property, node or whole object
|
|
||||||
*/
|
|
||||||
@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@MustBeDocumented
|
|
||||||
annotation class Description(val value: String)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Annotation for value property which states that lists are expected
|
|
||||||
*/
|
|
||||||
@Target(AnnotationTarget.PROPERTY)
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@MustBeDocumented
|
|
||||||
annotation class Multiple
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Descriptor target
|
|
||||||
* The DataForge path to the resource containing the description. Following targets are supported:
|
|
||||||
* 1. resource
|
|
||||||
* 1. file
|
|
||||||
* 1. class
|
|
||||||
* 1. method
|
|
||||||
* 1. property
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* Does not work if [type] is provided
|
|
||||||
*/
|
|
||||||
@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER)
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@MustBeDocumented
|
|
||||||
annotation class Descriptor(val value: String)
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Aggregator class for descriptor nodes
|
|
||||||
*/
|
|
||||||
@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER)
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@MustBeDocumented
|
|
||||||
annotation class DescriptorNodes(vararg val nodes: NodeDef)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Aggregator class for descriptor values
|
|
||||||
*/
|
|
||||||
@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER)
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@MustBeDocumented
|
|
||||||
annotation class DescriptorValues(vararg val nodes: ValueDef)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alternative name for property descriptor declaration
|
|
||||||
*/
|
|
||||||
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER)
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@MustBeDocumented
|
|
||||||
annotation class DescriptorName(val name: String)
|
|
||||||
|
|
||||||
@Target(AnnotationTarget.PROPERTY)
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@MustBeDocumented
|
|
||||||
annotation class DescriptorValue(val def: ValueDef)
|
|
||||||
//TODO enter fields directly?
|
|
||||||
|
|
||||||
@Target(AnnotationTarget.PROPERTY)
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@MustBeDocumented
|
|
||||||
annotation class ValueProperty(
|
|
||||||
val name: String = "",
|
|
||||||
val type: Array<ValueType> = arrayOf(ValueType.STRING),
|
|
||||||
val multiple: Boolean = false,
|
|
||||||
val def: String = "",
|
|
||||||
val enumeration: KClass<*> = Any::class,
|
|
||||||
val tags: Array<String> = emptyArray()
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@Target(AnnotationTarget.PROPERTY)
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@MustBeDocumented
|
|
||||||
annotation class NodeProperty(val name: String = "")
|
|
@ -4,6 +4,7 @@ import hep.dataforge.names.Name
|
|||||||
import hep.dataforge.names.NameToken
|
import hep.dataforge.names.NameToken
|
||||||
import hep.dataforge.names.asName
|
import hep.dataforge.names.asName
|
||||||
import hep.dataforge.names.plus
|
import hep.dataforge.names.plus
|
||||||
|
import kotlinx.serialization.*
|
||||||
|
|
||||||
//TODO add validator to configuration
|
//TODO add validator to configuration
|
||||||
|
|
||||||
@ -12,10 +13,16 @@ data class MetaListener(
|
|||||||
val action: (name: Name, oldItem: MetaItem<*>?, newItem: MetaItem<*>?) -> Unit
|
val action: (name: Name, oldItem: MetaItem<*>?, newItem: MetaItem<*>?) -> Unit
|
||||||
)
|
)
|
||||||
|
|
||||||
|
interface ObservableMeta : Meta {
|
||||||
|
fun onChange(owner: Any?, action: (Name, MetaItem<*>?, MetaItem<*>?) -> Unit)
|
||||||
|
fun removeListener(owner: Any?)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mutable meta representing object state
|
* Mutable meta representing object state
|
||||||
*/
|
*/
|
||||||
class Config : AbstractMutableMeta<Config>() {
|
@Serializable
|
||||||
|
class Config : AbstractMutableMeta<Config>(), ObservableMeta {
|
||||||
|
|
||||||
private val listeners = HashSet<MetaListener>()
|
private val listeners = HashSet<MetaListener>()
|
||||||
|
|
||||||
@ -26,14 +33,14 @@ class Config : AbstractMutableMeta<Config>() {
|
|||||||
/**
|
/**
|
||||||
* Add change listener to this meta. Owner is declared to be able to remove listeners later. Listener without owner could not be removed
|
* Add change listener to this meta. Owner is declared to be able to remove listeners later. Listener without owner could not be removed
|
||||||
*/
|
*/
|
||||||
fun onChange(owner: Any?, action: (Name, MetaItem<*>?, MetaItem<*>?) -> Unit) {
|
override fun onChange(owner: Any?, action: (Name, MetaItem<*>?, MetaItem<*>?) -> Unit) {
|
||||||
listeners.add(MetaListener(owner, action))
|
listeners.add(MetaListener(owner, action))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove all listeners belonging to given owner
|
* Remove all listeners belonging to given owner
|
||||||
*/
|
*/
|
||||||
fun removeListener(owner: Any?) {
|
override fun removeListener(owner: Any?) {
|
||||||
listeners.removeAll { it.owner === owner }
|
listeners.removeAll { it.owner === owner }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,33 +64,34 @@ class Config : AbstractMutableMeta<Config>() {
|
|||||||
/**
|
/**
|
||||||
* Attach configuration node instead of creating one
|
* Attach configuration node instead of creating one
|
||||||
*/
|
*/
|
||||||
override fun wrapNode(meta: Meta): Config = meta.toConfig()
|
override fun wrapNode(meta: Meta): Config = meta.asConfig()
|
||||||
|
|
||||||
override fun empty(): Config = Config()
|
override fun empty(): Config = Config()
|
||||||
|
|
||||||
companion object {
|
@Serializer(Config::class)
|
||||||
|
companion object ConfigSerializer : KSerializer<Config> {
|
||||||
|
|
||||||
fun empty(): Config = Config()
|
fun empty(): Config = Config()
|
||||||
|
override val descriptor: SerialDescriptor get() = MetaSerializer.descriptor
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): Config {
|
||||||
|
return MetaSerializer.deserialize(decoder).asConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: Config) {
|
||||||
|
MetaSerializer.serialize(encoder, value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun Config.get(token: NameToken): MetaItem<Config>? = items[token]
|
operator fun Config.get(token: NameToken): MetaItem<Config>? = items[token]
|
||||||
|
|
||||||
fun Meta.toConfig(): Config = this as? Config ?: Config().also { builder ->
|
fun Meta.asConfig(): Config = this as? Config ?: Config().also { builder ->
|
||||||
this.items.mapValues { entry ->
|
this.items.mapValues { entry ->
|
||||||
val item = entry.value
|
val item = entry.value
|
||||||
builder[entry.key.asName()] = when (item) {
|
builder[entry.key.asName()] = when (item) {
|
||||||
is MetaItem.ValueItem -> item.value
|
is MetaItem.ValueItem -> item.value
|
||||||
is MetaItem.NodeItem -> MetaItem.NodeItem(item.node.toConfig())
|
is MetaItem.NodeItem -> MetaItem.NodeItem(item.node.asConfig())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Configurable {
|
|
||||||
val config: Config
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T : Configurable> T.configure(meta: Meta): T = this.apply { config.update(meta) }
|
|
||||||
|
|
||||||
fun <T : Configurable> T.configure(action: MetaBuilder.() -> Unit): T = configure(buildMeta(action))
|
|
||||||
|
|
||||||
open class SimpleConfigurable(override val config: Config) : Configurable
|
|
@ -0,0 +1,132 @@
|
|||||||
|
package hep.dataforge.meta
|
||||||
|
|
||||||
|
import hep.dataforge.meta.descriptors.ItemDescriptor
|
||||||
|
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||||
|
import hep.dataforge.meta.descriptors.ValueDescriptor
|
||||||
|
import hep.dataforge.names.NameToken
|
||||||
|
import hep.dataforge.names.toName
|
||||||
|
import hep.dataforge.values.*
|
||||||
|
import kotlinx.serialization.json.*
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param descriptor reserved for custom serialization in future
|
||||||
|
*/
|
||||||
|
fun Value.toJson(descriptor: ValueDescriptor? = null): JsonElement {
|
||||||
|
return if (isList()) {
|
||||||
|
JsonArray(list.map { it.toJson() })
|
||||||
|
} else {
|
||||||
|
when (type) {
|
||||||
|
ValueType.NUMBER -> JsonPrimitive(number)
|
||||||
|
ValueType.STRING -> JsonPrimitive(string)
|
||||||
|
ValueType.BOOLEAN -> JsonPrimitive(boolean)
|
||||||
|
ValueType.NULL -> JsonNull
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Use these methods to customize JSON key mapping
|
||||||
|
private fun NameToken.toJsonKey(descriptor: ItemDescriptor?) = toString()
|
||||||
|
|
||||||
|
//private fun NodeDescriptor?.getDescriptor(key: String) = this?.items?.get(key)
|
||||||
|
|
||||||
|
fun Meta.toJson(descriptor: NodeDescriptor? = null): JsonObject {
|
||||||
|
|
||||||
|
//TODO search for same name siblings and arrange them into arrays
|
||||||
|
val map = this.items.entries.associate { (name, item) ->
|
||||||
|
val itemDescriptor = descriptor?.items?.get(name.body)
|
||||||
|
val key = name.toJsonKey(itemDescriptor)
|
||||||
|
val value = when (item) {
|
||||||
|
is MetaItem.ValueItem -> {
|
||||||
|
item.value.toJson(itemDescriptor as? ValueDescriptor)
|
||||||
|
}
|
||||||
|
is MetaItem.NodeItem -> {
|
||||||
|
item.node.toJson(itemDescriptor as? NodeDescriptor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
key to value
|
||||||
|
}
|
||||||
|
return JsonObject(map)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun JsonObject.toMeta(descriptor: NodeDescriptor? = null): Meta = JsonMeta(this, descriptor)
|
||||||
|
|
||||||
|
fun JsonPrimitive.toValue(descriptor: ValueDescriptor?): Value {
|
||||||
|
return when (this) {
|
||||||
|
JsonNull -> Null
|
||||||
|
else -> this.content.parseValue() // Optimize number and boolean parsing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun JsonElement.toMetaItem(descriptor: ItemDescriptor? = null): MetaItem<JsonMeta> = when (this) {
|
||||||
|
is JsonPrimitive -> {
|
||||||
|
val value = this.toValue(descriptor as? ValueDescriptor)
|
||||||
|
MetaItem.ValueItem(value)
|
||||||
|
}
|
||||||
|
is JsonObject -> {
|
||||||
|
val meta = JsonMeta(this, descriptor as? NodeDescriptor)
|
||||||
|
MetaItem.NodeItem(meta)
|
||||||
|
}
|
||||||
|
is JsonArray -> {
|
||||||
|
if (this.all { it is JsonPrimitive }) {
|
||||||
|
val value = if (isEmpty()) {
|
||||||
|
Null
|
||||||
|
} else {
|
||||||
|
ListValue(
|
||||||
|
map<JsonElement, Value> {
|
||||||
|
//We already checked that all values are primitives
|
||||||
|
(it as JsonPrimitive).toValue(descriptor as? ValueDescriptor)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
MetaItem.ValueItem(value)
|
||||||
|
} else {
|
||||||
|
json {
|
||||||
|
"@value" to this@toMetaItem
|
||||||
|
}.toMetaItem(descriptor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class JsonMeta(val json: JsonObject, val descriptor: NodeDescriptor? = null) : MetaBase() {
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
private operator fun MutableMap<String, MetaItem<JsonMeta>>.set(key: String, value: JsonElement): Unit {
|
||||||
|
val itemDescriptor = descriptor?.items?.get(key)
|
||||||
|
return when (value) {
|
||||||
|
is JsonPrimitive -> {
|
||||||
|
this[key] = MetaItem.ValueItem(value.toValue(itemDescriptor as? ValueDescriptor)) as MetaItem<JsonMeta>
|
||||||
|
}
|
||||||
|
is JsonObject -> {
|
||||||
|
this[key] = MetaItem.NodeItem(
|
||||||
|
JsonMeta(
|
||||||
|
value,
|
||||||
|
itemDescriptor as? NodeDescriptor
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is JsonArray -> {
|
||||||
|
when {
|
||||||
|
value.all { it is JsonPrimitive } -> {
|
||||||
|
val listValue = ListValue(
|
||||||
|
value.map {
|
||||||
|
//We already checked that all values are primitives
|
||||||
|
(it as JsonPrimitive).toValue(itemDescriptor as? ValueDescriptor)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
this[key] = MetaItem.ValueItem(listValue) as MetaItem<JsonMeta>
|
||||||
|
}
|
||||||
|
else -> value.forEachIndexed { index, jsonElement ->
|
||||||
|
this["$key[$index]"] = jsonElement.toMetaItem(itemDescriptor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val items: Map<NameToken, MetaItem<JsonMeta>> by lazy {
|
||||||
|
val map = HashMap<String, MetaItem<JsonMeta>>()
|
||||||
|
json.forEach { (key, value) -> map[key] = value }
|
||||||
|
map.mapKeys { it.key.toName().first()!! }
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,11 @@
|
|||||||
package hep.dataforge.meta
|
package hep.dataforge.meta
|
||||||
|
|
||||||
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.names.NameToken
|
import hep.dataforge.names.NameToken
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A meta laminate consisting of multiple immutable meta layers. For mutable front layer, use [Styled].
|
* A meta laminate consisting of multiple immutable meta layers. For mutable front layer, use [Scheme].
|
||||||
|
* If [layers] list contains a [Laminate] it is flat-mapped.
|
||||||
*/
|
*/
|
||||||
class Laminate(layers: List<Meta>) : MetaBase() {
|
class Laminate(layers: List<Meta>) : MetaBase() {
|
||||||
|
|
||||||
@ -17,10 +19,11 @@ class Laminate(layers: List<Meta>) : MetaBase() {
|
|||||||
|
|
||||||
constructor(vararg layers: Meta?) : this(layers.filterNotNull())
|
constructor(vararg layers: Meta?) : this(layers.filterNotNull())
|
||||||
|
|
||||||
override val items: Map<NameToken, MetaItem<Meta>>
|
override val items: Map<NameToken, MetaItem<Meta>> by lazy {
|
||||||
get() = layers.map { it.items.keys }.flatten().associateWith { key ->
|
layers.map { it.items.keys }.flatten().associateWith { key ->
|
||||||
layers.asSequence().map { it.items[key] }.filterNotNull().let(replaceRule)
|
layers.asSequence().map { it.items[key] }.filterNotNull().let(replaceRule)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate sealed meta using [mergeRule]
|
* Generate sealed meta using [mergeRule]
|
||||||
@ -61,7 +64,7 @@ class Laminate(layers: List<Meta>) : MetaBase() {
|
|||||||
}
|
}
|
||||||
else -> map {
|
else -> map {
|
||||||
when (it) {
|
when (it) {
|
||||||
is MetaItem.ValueItem -> MetaItem.NodeItem(buildMeta { Meta.VALUE_KEY put it.value })
|
is MetaItem.ValueItem -> MetaItem.NodeItem(Meta { Meta.VALUE_KEY put it.value })
|
||||||
is MetaItem.NodeItem -> it
|
is MetaItem.NodeItem -> it
|
||||||
}
|
}
|
||||||
}.merge()
|
}.merge()
|
||||||
@ -77,6 +80,16 @@ class Laminate(layers: List<Meta>) : MetaBase() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performance optimized version of get method
|
||||||
|
*/
|
||||||
|
fun Laminate.getFirst(name: Name): MetaItem<*>? {
|
||||||
|
layers.forEach { layer ->
|
||||||
|
layer[name]?.let { return it }
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new [Laminate] adding given layer to the top
|
* Create a new [Laminate] adding given layer to the top
|
||||||
*/
|
*/
|
||||||
|
@ -4,9 +4,8 @@ import hep.dataforge.meta.Meta.Companion.VALUE_KEY
|
|||||||
import hep.dataforge.meta.MetaItem.NodeItem
|
import hep.dataforge.meta.MetaItem.NodeItem
|
||||||
import hep.dataforge.meta.MetaItem.ValueItem
|
import hep.dataforge.meta.MetaItem.ValueItem
|
||||||
import hep.dataforge.names.*
|
import hep.dataforge.names.*
|
||||||
import hep.dataforge.values.EnumValue
|
import hep.dataforge.values.*
|
||||||
import hep.dataforge.values.Value
|
import kotlinx.serialization.*
|
||||||
import hep.dataforge.values.boolean
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -14,13 +13,51 @@ import hep.dataforge.values.boolean
|
|||||||
* * a [ValueItem] (leaf)
|
* * a [ValueItem] (leaf)
|
||||||
* * a [NodeItem] (node)
|
* * a [NodeItem] (node)
|
||||||
*/
|
*/
|
||||||
|
@Serializable
|
||||||
sealed class MetaItem<out M : Meta> {
|
sealed class MetaItem<out M : Meta> {
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class ValueItem(val value: Value) : MetaItem<Nothing>() {
|
data class ValueItem(val value: Value) : MetaItem<Nothing>() {
|
||||||
override fun toString(): String = value.toString()
|
override fun toString(): String = value.toString()
|
||||||
|
|
||||||
|
@Serializer(ValueItem::class)
|
||||||
|
companion object : KSerializer<ValueItem> {
|
||||||
|
override val descriptor: SerialDescriptor get() = ValueSerializer.descriptor
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): ValueItem = ValueItem(ValueSerializer.deserialize(decoder))
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: ValueItem) {
|
||||||
|
ValueSerializer.serialize(encoder, value.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class NodeItem<M : Meta>(val node: M) : MetaItem<M>() {
|
@Serializable
|
||||||
|
data class NodeItem<M : Meta>(@Serializable(MetaSerializer::class) val node: M) : MetaItem<M>() {
|
||||||
|
//Fixing serializer for node could cause class cast problems, but it should not since Meta descendants are not serializeable
|
||||||
override fun toString(): String = node.toString()
|
override fun toString(): String = node.toString()
|
||||||
|
|
||||||
|
@Serializer(NodeItem::class)
|
||||||
|
companion object : KSerializer<NodeItem<out Meta>> {
|
||||||
|
override val descriptor: SerialDescriptor get() = MetaSerializer.descriptor
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): NodeItem<*> = NodeItem(MetaSerializer.deserialize(decoder))
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: NodeItem<*>) {
|
||||||
|
MetaSerializer.serialize(encoder, value.node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun of(arg: Any?): MetaItem<*> {
|
||||||
|
return when (arg) {
|
||||||
|
null -> ValueItem(Null)
|
||||||
|
is MetaItem<*> -> arg
|
||||||
|
is Meta -> NodeItem(arg)
|
||||||
|
else -> ValueItem(Value.of(arg))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,7 +82,7 @@ interface Meta : MetaRepr {
|
|||||||
*/
|
*/
|
||||||
val items: Map<NameToken, MetaItem<*>>
|
val items: Map<NameToken, MetaItem<*>>
|
||||||
|
|
||||||
override fun toMeta(): Meta = this
|
override fun toMeta(): Meta = seal()
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean
|
override fun equals(other: Any?): Boolean
|
||||||
|
|
||||||
@ -55,19 +92,26 @@ interface Meta : MetaRepr {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TYPE = "meta"
|
const val TYPE = "meta"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A key for single value node
|
* A key for single value node
|
||||||
*/
|
*/
|
||||||
const val VALUE_KEY = "@value"
|
const val VALUE_KEY = "@value"
|
||||||
|
|
||||||
val empty: EmptyMeta = EmptyMeta
|
val EMPTY: EmptyMeta = EmptyMeta
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get operations*/
|
/* Get operations*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform recursive item search using given [name]. Each [NameToken] is treated as a name in [Meta.items] of a parent node.
|
||||||
|
*
|
||||||
|
* If [name] is empty return current [Meta] as a [NodeItem]
|
||||||
|
*/
|
||||||
operator fun Meta?.get(name: Name): MetaItem<*>? {
|
operator fun Meta?.get(name: Name): MetaItem<*>? {
|
||||||
if (this == null) return null
|
if (this == null) return null
|
||||||
|
if (name.isEmpty()) return NodeItem(this)
|
||||||
return name.first()?.let { token ->
|
return name.first()?.let { token ->
|
||||||
val tail = name.cutFirst()
|
val tail = name.cutFirst()
|
||||||
when (tail.length) {
|
when (tail.length) {
|
||||||
@ -78,6 +122,10 @@ operator fun Meta?.get(name: Name): MetaItem<*>? {
|
|||||||
}
|
}
|
||||||
|
|
||||||
operator fun Meta?.get(token: NameToken): MetaItem<*>? = this?.items?.get(token)
|
operator fun Meta?.get(token: NameToken): MetaItem<*>? = this?.items?.get(token)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse [Name] from [key] using full name notation and pass it to [Meta.get]
|
||||||
|
*/
|
||||||
operator fun Meta?.get(key: String): MetaItem<*>? = get(key.toName())
|
operator fun Meta?.get(key: String): MetaItem<*>? = get(key.toName())
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -113,32 +161,19 @@ operator fun Meta.iterator(): Iterator<Pair<Name, MetaItem<*>>> = sequence().ite
|
|||||||
/**
|
/**
|
||||||
* A meta node that ensures that all of its descendants has at least the same type
|
* A meta node that ensures that all of its descendants has at least the same type
|
||||||
*/
|
*/
|
||||||
interface MetaNode<M : MetaNode<M>> : Meta {
|
interface MetaNode<out M : MetaNode<M>> : Meta {
|
||||||
override val items: Map<NameToken, MetaItem<M>>
|
override val items: Map<NameToken, MetaItem<M>>
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun <M : MetaNode<M>> MetaNode<M>?.get(name: Name): MetaItem<M>? {
|
/**
|
||||||
if (this == null) return null
|
* The same as [Meta.get], but with specific node type
|
||||||
return name.first()?.let { token ->
|
*/
|
||||||
val tail = name.cutFirst()
|
@Suppress("UNCHECKED_CAST")
|
||||||
when (tail.length) {
|
operator fun <M : MetaNode<M>> M?.get(name: Name): MetaItem<M>? = (this as Meta)[name] as MetaItem<M>?
|
||||||
0 -> items[token]
|
|
||||||
else -> items[token]?.node?.get(tail)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun <M : MetaNode<M>> MetaNode<M>?.get(key: String): MetaItem<M>? = if (this == null) {
|
operator fun <M : MetaNode<M>> M?.get(key: String): MetaItem<M>? = this[key.toName()]
|
||||||
null
|
|
||||||
} else {
|
|
||||||
this[key.toName()]
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun <M : MetaNode<M>> MetaNode<M>?.get(key: NameToken): MetaItem<M>? = if (this == null) {
|
operator fun <M : MetaNode<M>> M?.get(key: NameToken): MetaItem<M>? = this[key.asName()]
|
||||||
null
|
|
||||||
} else {
|
|
||||||
this[key.asName()]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Equals, hashcode and to string for any meta
|
* Equals, hashcode and to string for any meta
|
||||||
@ -153,7 +188,7 @@ abstract class MetaBase : Meta {
|
|||||||
|
|
||||||
override fun hashCode(): Int = items.hashCode()
|
override fun hashCode(): Int = items.hashCode()
|
||||||
|
|
||||||
override fun toString(): String = items.toString()
|
override fun toString(): String = toJson().toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -166,8 +201,9 @@ abstract class AbstractMetaNode<M : MetaNode<M>> : MetaNode<M>, MetaBase()
|
|||||||
*
|
*
|
||||||
* If the argument is possibly mutable node, it is copied on creation
|
* If the argument is possibly mutable node, it is copied on creation
|
||||||
*/
|
*/
|
||||||
class SealedMeta internal constructor(override val items: Map<NameToken, MetaItem<SealedMeta>>) :
|
class SealedMeta internal constructor(
|
||||||
AbstractMetaNode<SealedMeta>()
|
override val items: Map<NameToken, MetaItem<SealedMeta>>
|
||||||
|
) : AbstractMetaNode<SealedMeta>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate sealed node from [this]. If it is already sealed return it as is
|
* Generate sealed node from [this]. If it is already sealed return it as is
|
||||||
@ -200,7 +236,7 @@ val MetaItem<*>?.int get() = number?.toInt()
|
|||||||
val MetaItem<*>?.long get() = number?.toLong()
|
val MetaItem<*>?.long get() = number?.toLong()
|
||||||
val MetaItem<*>?.short get() = number?.toShort()
|
val MetaItem<*>?.short get() = number?.toShort()
|
||||||
|
|
||||||
inline fun <reified E : Enum<E>> MetaItem<*>?.enum() = if (this is ValueItem && this.value is EnumValue<*>) {
|
inline fun <reified E : Enum<E>> MetaItem<*>?.enum(): E? = if (this is ValueItem && this.value is EnumValue<*>) {
|
||||||
this.value.value as E
|
this.value.value as E
|
||||||
} else {
|
} else {
|
||||||
string?.let { enumValueOf<E>(it) }
|
string?.let { enumValueOf<E>(it) }
|
||||||
|
@ -15,6 +15,10 @@ class MetaBuilder : AbstractMutableMeta<MetaBuilder>() {
|
|||||||
override fun wrapNode(meta: Meta): MetaBuilder = if (meta is MetaBuilder) meta else meta.builder()
|
override fun wrapNode(meta: Meta): MetaBuilder = if (meta is MetaBuilder) meta else meta.builder()
|
||||||
override fun empty(): MetaBuilder = MetaBuilder()
|
override fun empty(): MetaBuilder = MetaBuilder()
|
||||||
|
|
||||||
|
infix fun String.put(item: MetaItem<*>?) {
|
||||||
|
set(this, item)
|
||||||
|
}
|
||||||
|
|
||||||
infix fun String.put(value: Value?) {
|
infix fun String.put(value: Value?) {
|
||||||
set(this, value)
|
set(this, value)
|
||||||
}
|
}
|
||||||
@ -141,9 +145,11 @@ fun Meta.edit(builder: MetaBuilder.() -> Unit): MetaBuilder = builder().apply(bu
|
|||||||
/**
|
/**
|
||||||
* Build a [MetaBuilder] using given transformation
|
* Build a [MetaBuilder] using given transformation
|
||||||
*/
|
*/
|
||||||
|
@Deprecated("To be replaced with fake constructor", ReplaceWith("Meta"))
|
||||||
fun buildMeta(builder: MetaBuilder.() -> Unit): MetaBuilder = MetaBuilder().apply(builder)
|
fun buildMeta(builder: MetaBuilder.() -> Unit): MetaBuilder = MetaBuilder().apply(builder)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build meta using given source meta as a base
|
* Build a [MetaBuilder] using given transformation
|
||||||
*/
|
*/
|
||||||
fun buildMeta(source: Meta, builder: MetaBuilder.() -> Unit): MetaBuilder = source.builder().apply(builder)
|
@Suppress("FunctionName")
|
||||||
|
fun Meta(builder: MetaBuilder.() -> Unit): MetaBuilder = MetaBuilder().apply(builder)
|
@ -0,0 +1,98 @@
|
|||||||
|
package hep.dataforge.meta
|
||||||
|
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.asName
|
||||||
|
import hep.dataforge.values.Value
|
||||||
|
import kotlin.jvm.JvmName
|
||||||
|
import kotlin.properties.ReadOnlyProperty
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
/* Meta delegates */
|
||||||
|
|
||||||
|
open class MetaDelegate(
|
||||||
|
open val owner: Meta,
|
||||||
|
val key: Name? = null,
|
||||||
|
open val default: MetaItem<*>? = null
|
||||||
|
) : ReadOnlyProperty<Any?, MetaItem<*>?> {
|
||||||
|
override fun getValue(thisRef: Any?, property: KProperty<*>): MetaItem<*>? {
|
||||||
|
return owner[key ?: property.name.asName()] ?: default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LazyMetaDelegate(
|
||||||
|
owner: Meta,
|
||||||
|
key: Name? = null,
|
||||||
|
defaultProvider: () -> MetaItem<*>? = { null }
|
||||||
|
) : MetaDelegate(owner, key) {
|
||||||
|
override val default by lazy(defaultProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
class DelegateWrapper<T, R>(
|
||||||
|
val delegate: ReadOnlyProperty<Any?, T>,
|
||||||
|
val reader: (T) -> R
|
||||||
|
) : ReadOnlyProperty<Any?, R> {
|
||||||
|
override fun getValue(thisRef: Any?, property: KProperty<*>): R {
|
||||||
|
return reader(delegate.getValue(thisRef, property))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T, R> ReadOnlyProperty<Any?, T>.map(reader: (T) -> R): DelegateWrapper<T, R> =
|
||||||
|
DelegateWrapper(this, reader)
|
||||||
|
|
||||||
|
|
||||||
|
fun Meta.item(default: Any? = null, key: Name? = null): MetaDelegate =
|
||||||
|
MetaDelegate(this, key, default?.let { MetaItem.of(it) })
|
||||||
|
|
||||||
|
fun Meta.lazyItem(key: Name? = null, defaultProvider: () -> Any?): LazyMetaDelegate =
|
||||||
|
LazyMetaDelegate(this, key) { defaultProvider()?.let { MetaItem.of(it) } }
|
||||||
|
|
||||||
|
//TODO add caching for sealed nodes
|
||||||
|
|
||||||
|
|
||||||
|
//Read-only delegates for Metas
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A property delegate that uses custom key
|
||||||
|
*/
|
||||||
|
fun Meta.value(default: Value? = null, key: Name? = null) =
|
||||||
|
item(default, key).map { it.value }
|
||||||
|
|
||||||
|
fun Meta.string(default: String? = null, key: Name? = null) =
|
||||||
|
item(default, key).map { it.string }
|
||||||
|
|
||||||
|
fun Meta.boolean(default: Boolean? = null, key: Name? = null) =
|
||||||
|
item(default, key).map { it.boolean }
|
||||||
|
|
||||||
|
fun Meta.number(default: Number? = null, key: Name? = null) =
|
||||||
|
item(default, key).map { it.number }
|
||||||
|
|
||||||
|
fun Meta.node(key: Name? = null) =
|
||||||
|
item(key).map { it.node }
|
||||||
|
|
||||||
|
@JvmName("safeString")
|
||||||
|
fun Meta.string(default: String, key: Name? = null) =
|
||||||
|
item(default, key).map { it.string!! }
|
||||||
|
|
||||||
|
@JvmName("safeBoolean")
|
||||||
|
fun Meta.boolean(default: Boolean, key: Name? = null) =
|
||||||
|
item(default, key).map { it.boolean!! }
|
||||||
|
|
||||||
|
@JvmName("safeNumber")
|
||||||
|
fun Meta.number(default: Number, key: Name? = null) =
|
||||||
|
item(default, key).map { it.number!! }
|
||||||
|
|
||||||
|
@JvmName("lazyString")
|
||||||
|
fun Meta.string(key: Name? = null, default: () -> String) =
|
||||||
|
lazyItem(key, default).map { it.string!! }
|
||||||
|
|
||||||
|
@JvmName("lazyBoolean")
|
||||||
|
fun Meta.boolean(key: Name? = null, default: () -> Boolean) =
|
||||||
|
lazyItem(key, default).map { it.boolean!! }
|
||||||
|
|
||||||
|
@JvmName("lazyNumber")
|
||||||
|
fun Meta.number(key: Name? = null, default: () -> Number) =
|
||||||
|
lazyItem(key, default).map { it.number!! }
|
||||||
|
|
||||||
|
|
||||||
|
inline fun <reified E : Enum<E>> Meta.enum(default: E, key: Name? = null) =
|
||||||
|
item(default, key).map { it.enum<E>()!! }
|
@ -0,0 +1,40 @@
|
|||||||
|
package hep.dataforge.meta
|
||||||
|
|
||||||
|
import hep.dataforge.names.NameToken
|
||||||
|
import kotlinx.serialization.*
|
||||||
|
import kotlinx.serialization.builtins.MapSerializer
|
||||||
|
import kotlinx.serialization.json.JsonInput
|
||||||
|
import kotlinx.serialization.json.JsonObjectSerializer
|
||||||
|
import kotlinx.serialization.json.JsonOutput
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialized for meta
|
||||||
|
*/
|
||||||
|
@Serializer(Meta::class)
|
||||||
|
object MetaSerializer : KSerializer<Meta> {
|
||||||
|
private val mapSerializer = MapSerializer(
|
||||||
|
NameToken.serializer(),
|
||||||
|
MetaItem.serializer(MetaSerializer)
|
||||||
|
)
|
||||||
|
|
||||||
|
override val descriptor: SerialDescriptor get() = mapSerializer.descriptor
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): Meta {
|
||||||
|
return if (decoder is JsonInput) {
|
||||||
|
JsonObjectSerializer.deserialize(decoder).toMeta()
|
||||||
|
} else {
|
||||||
|
object : MetaBase() {
|
||||||
|
override val items: Map<NameToken, MetaItem<*>> = mapSerializer.deserialize(decoder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: Meta) {
|
||||||
|
if (encoder is JsonOutput) {
|
||||||
|
JsonObjectSerializer.serialize(encoder, value.toJson())
|
||||||
|
} else {
|
||||||
|
mapSerializer.serialize(encoder, value.items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,10 @@
|
|||||||
package hep.dataforge.meta
|
package hep.dataforge.meta
|
||||||
|
|
||||||
|
import hep.dataforge.meta.scheme.Configurable
|
||||||
import hep.dataforge.names.*
|
import hep.dataforge.names.*
|
||||||
import hep.dataforge.values.Value
|
import hep.dataforge.values.Value
|
||||||
|
|
||||||
interface MutableMeta<M : MutableMeta<M>> : MetaNode<M> {
|
interface MutableMeta<out M : MutableMeta<M>> : MetaNode<M> {
|
||||||
override val items: Map<NameToken, MetaItem<M>>
|
override val items: Map<NameToken, MetaItem<M>>
|
||||||
operator fun set(name: Name, item: MetaItem<*>?)
|
operator fun set(name: Name, item: MetaItem<*>?)
|
||||||
// fun onChange(owner: Any? = null, action: (Name, MetaItem<*>?, MetaItem<*>?) -> Unit)
|
// fun onChange(owner: Any? = null, action: (Name, MetaItem<*>?, MetaItem<*>?) -> Unit)
|
||||||
@ -54,7 +55,8 @@ abstract class AbstractMutableMeta<M : MutableMeta<M>> : AbstractMetaNode<M>(),
|
|||||||
0 -> error("Can't setValue meta item for empty name")
|
0 -> error("Can't setValue meta item for empty name")
|
||||||
1 -> {
|
1 -> {
|
||||||
val token = name.first()!!
|
val token = name.first()!!
|
||||||
replaceItem(token, get(name), wrapItem(item))
|
@Suppress("UNCHECKED_CAST") val oldItem: MetaItem<M>? = get(name) as? MetaItem<M>
|
||||||
|
replaceItem(token, oldItem, wrapItem(item))
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val token = name.first()!!
|
val token = name.first()!!
|
||||||
@ -71,6 +73,7 @@ abstract class AbstractMutableMeta<M : MutableMeta<M>> : AbstractMetaNode<M>(),
|
|||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun MutableMeta<*>.remove(name: Name) = set(name, null)
|
inline fun MutableMeta<*>.remove(name: Name) = set(name, null)
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun MutableMeta<*>.remove(name: String) = remove(name.toName())
|
inline fun MutableMeta<*>.remove(name: String) = remove(name.toName())
|
||||||
|
|
||||||
@ -103,7 +106,7 @@ operator fun MutableMeta<*>.set(name: Name, value: Any?) {
|
|||||||
null -> remove(name)
|
null -> remove(name)
|
||||||
is MetaItem<*> -> setItem(name, value)
|
is MetaItem<*> -> setItem(name, value)
|
||||||
is Meta -> setNode(name, value)
|
is Meta -> setNode(name, value)
|
||||||
is Specific -> setNode(name, value.config)
|
is Configurable -> setNode(name, value.config)
|
||||||
else -> setValue(name, Value.of(value))
|
else -> setValue(name, Value.of(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -112,6 +115,8 @@ operator fun MutableMeta<*>.set(name: NameToken, value: Any?) = set(name.asName(
|
|||||||
|
|
||||||
operator fun MutableMeta<*>.set(key: String, value: Any?) = set(key.toName(), value)
|
operator fun MutableMeta<*>.set(key: String, value: Any?) = set(key.toName(), value)
|
||||||
|
|
||||||
|
operator fun MutableMeta<*>.set(key: String, index: String, value: Any?) = set(key.toName().withIndex(index), value)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update existing mutable node with another node. The rules are following:
|
* Update existing mutable node with another node. The rules are following:
|
||||||
* * value replaces anything
|
* * value replaces anything
|
||||||
@ -158,7 +163,7 @@ operator fun MutableMeta<*>.set(name: String, metas: Iterable<Meta>): Unit = set
|
|||||||
/**
|
/**
|
||||||
* Append the node with a same-name-sibling, automatically generating numerical index
|
* Append the node with a same-name-sibling, automatically generating numerical index
|
||||||
*/
|
*/
|
||||||
fun MutableMeta<*>.append(name: Name, value: Any?) {
|
fun <M : MutableMeta<M>> M.append(name: Name, value: Any?) {
|
||||||
require(!name.isEmpty()) { "Name could not be empty for append operation" }
|
require(!name.isEmpty()) { "Name could not be empty for append operation" }
|
||||||
val newIndex = name.last()!!.index
|
val newIndex = name.last()!!.index
|
||||||
if (newIndex.isNotEmpty()) {
|
if (newIndex.isNotEmpty()) {
|
||||||
@ -169,4 +174,4 @@ fun MutableMeta<*>.append(name: Name, value: Any?) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun MutableMeta<*>.append(name: String, value: Any?) = append(name.toName(), value)
|
fun <M : MutableMeta<M>> M.append(name: String, value: Any?) = append(name.toName(), value)
|
@ -0,0 +1,118 @@
|
|||||||
|
package hep.dataforge.meta
|
||||||
|
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.asName
|
||||||
|
import hep.dataforge.values.Value
|
||||||
|
import kotlin.jvm.JvmName
|
||||||
|
import kotlin.properties.ReadWriteProperty
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
/* Read-write delegates */
|
||||||
|
|
||||||
|
open class MutableMetaDelegate<M : MutableMeta<M>>(
|
||||||
|
override val owner: M,
|
||||||
|
key: Name? = null,
|
||||||
|
default: MetaItem<*>? = null
|
||||||
|
) : MetaDelegate(owner, key, default), ReadWriteProperty<Any?, MetaItem<*>?> {
|
||||||
|
|
||||||
|
override fun setValue(thisRef: Any?, property: KProperty<*>, value: MetaItem<*>?) {
|
||||||
|
val name = key ?: property.name.asName()
|
||||||
|
owner.setItem(name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LazyMutableMetaDelegate<M : MutableMeta<M>>(
|
||||||
|
owner: M,
|
||||||
|
key: Name? = null,
|
||||||
|
defaultProvider: () -> MetaItem<*>? = { null }
|
||||||
|
) : MutableMetaDelegate<M>(owner, key) {
|
||||||
|
override val default by lazy(defaultProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReadWriteDelegateWrapper<T, R>(
|
||||||
|
val delegate: ReadWriteProperty<Any?, T>,
|
||||||
|
val reader: (T) -> R,
|
||||||
|
val writer: (R) -> T
|
||||||
|
) : ReadWriteProperty<Any?, R> {
|
||||||
|
override fun getValue(thisRef: Any?, property: KProperty<*>): R {
|
||||||
|
return reader(delegate.getValue(thisRef, property))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setValue(thisRef: Any?, property: KProperty<*>, value: R) {
|
||||||
|
delegate.setValue(thisRef, property, writer(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T, R> ReadWriteProperty<Any?, T>.map(reader: (T) -> R, writer: (R) -> T): ReadWriteDelegateWrapper<T, R> =
|
||||||
|
ReadWriteDelegateWrapper(this, reader, writer)
|
||||||
|
|
||||||
|
fun <R> ReadWriteProperty<Any?, MetaItem<*>?>.transform(reader: (MetaItem<*>?) -> R): ReadWriteProperty<Any?, R> =
|
||||||
|
map(reader = reader, writer = { MetaItem.of(it) })
|
||||||
|
|
||||||
|
fun <R> ReadWriteProperty<Any?, Value?>.transform(reader: (Value?) -> R): ReadWriteDelegateWrapper<Value?, R> =
|
||||||
|
map(reader = reader, writer = { Value.of(it) })
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A delegate that throws
|
||||||
|
*/
|
||||||
|
fun <R : Any> ReadWriteProperty<Any?, R?>.notNull(default: () -> R): ReadWriteProperty<Any?, R> {
|
||||||
|
return ReadWriteDelegateWrapper(this,
|
||||||
|
reader = { it ?: default() },
|
||||||
|
writer = { it }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun <M : MutableMeta<M>> M.item(default: Any? = null, key: Name? = null): MutableMetaDelegate<M> =
|
||||||
|
MutableMetaDelegate(this, key, default?.let { MetaItem.of(it) })
|
||||||
|
|
||||||
|
fun <M : MutableMeta<M>> M.lazyItem(key: Name? = null, defaultProvider: () -> Any?): LazyMutableMetaDelegate<M> =
|
||||||
|
LazyMutableMetaDelegate(this, key) { defaultProvider()?.let { MetaItem.of(it) } }
|
||||||
|
|
||||||
|
//Read-write delegates
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A property delegate that uses custom key
|
||||||
|
*/
|
||||||
|
fun <M : MutableMeta<M>> M.value(default: Value? = null, key: Name? = null): ReadWriteProperty<Any?, Value?> =
|
||||||
|
item(default, key).transform { it.value }
|
||||||
|
|
||||||
|
fun <M : MutableMeta<M>> M.string(default: String? = null, key: Name? = null): ReadWriteProperty<Any?, String?> =
|
||||||
|
item(default, key).transform { it.string }
|
||||||
|
|
||||||
|
fun <M : MutableMeta<M>> M.boolean(default: Boolean? = null, key: Name? = null): ReadWriteProperty<Any?, Boolean?> =
|
||||||
|
item(default, key).transform { it.boolean }
|
||||||
|
|
||||||
|
fun <M : MutableMeta<M>> M.number(default: Number? = null, key: Name? = null): ReadWriteProperty<Any?, Number?> =
|
||||||
|
item(default, key).transform { it.number }
|
||||||
|
|
||||||
|
inline fun <reified M : MutableMeta<M>> M.node(key: Name? = null) =
|
||||||
|
item(this, key).transform { it.node as? M }
|
||||||
|
|
||||||
|
@JvmName("safeString")
|
||||||
|
fun <M : MutableMeta<M>> M.string(default: String, key: Name? = null) =
|
||||||
|
item(default, key).transform { it.string!! }
|
||||||
|
|
||||||
|
@JvmName("safeBoolean")
|
||||||
|
fun <M : MutableMeta<M>> M.boolean(default: Boolean, key: Name? = null) =
|
||||||
|
item(default, key).transform { it.boolean!! }
|
||||||
|
|
||||||
|
@JvmName("safeNumber")
|
||||||
|
fun <M : MutableMeta<M>> M.number(default: Number, key: Name? = null) =
|
||||||
|
item(default, key).transform { it.number!! }
|
||||||
|
|
||||||
|
@JvmName("lazyString")
|
||||||
|
fun <M : MutableMeta<M>> M.string(key: Name? = null, default: () -> String) =
|
||||||
|
lazyItem(key, default).transform { it.string!! }
|
||||||
|
|
||||||
|
@JvmName("safeBoolean")
|
||||||
|
fun <M : MutableMeta<M>> M.boolean(key: Name? = null, default: () -> Boolean) =
|
||||||
|
lazyItem(key, default).transform { it.boolean!! }
|
||||||
|
|
||||||
|
@JvmName("safeNumber")
|
||||||
|
fun <M : MutableMeta<M>> M.number(key: Name? = null, default: () -> Number) =
|
||||||
|
lazyItem(key, default).transform { it.number!! }
|
||||||
|
|
||||||
|
|
||||||
|
inline fun <M : MutableMeta<M>, reified E : Enum<E>> M.enum(default: E, key: Name? = null) =
|
||||||
|
item(default, key).transform { it.enum<E>()!! }
|
@ -1,73 +0,0 @@
|
|||||||
package hep.dataforge.meta
|
|
||||||
|
|
||||||
import hep.dataforge.names.Name
|
|
||||||
import kotlin.jvm.JvmName
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Marker interface for classes with specifications
|
|
||||||
*/
|
|
||||||
interface Specific : Configurable
|
|
||||||
|
|
||||||
//TODO separate mutable config from immutable meta to allow free wrapping of meta
|
|
||||||
|
|
||||||
operator fun Specific.get(name: String): MetaItem<*>? = config[name]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows to apply custom configuration in a type safe way to simple untyped configuration.
|
|
||||||
* By convention [Specific] companion should inherit this class
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
interface Specification<T : Specific> {
|
|
||||||
/**
|
|
||||||
* Update given configuration using given type as a builder
|
|
||||||
*/
|
|
||||||
fun update(config: Config, action: T.() -> Unit): T {
|
|
||||||
return wrap(config).apply(action)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun build(action: T.() -> Unit) = update(Config(), action)
|
|
||||||
|
|
||||||
fun empty() = build { }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wrap generic configuration producing instance of desired type
|
|
||||||
*/
|
|
||||||
fun wrap(config: Config): T
|
|
||||||
|
|
||||||
//TODO replace by free wrapper
|
|
||||||
fun wrap(meta: Meta): T = wrap(meta.toConfig())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T : Specific> specification(wrapper: (Config) -> T): Specification<T> =
|
|
||||||
object : Specification<T> {
|
|
||||||
override fun wrap(config: Config): T = wrapper(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply specified configuration to configurable
|
|
||||||
*/
|
|
||||||
fun <T : Configurable, C : Specific, S : Specification<C>> T.configure(spec: S, action: C.() -> Unit) =
|
|
||||||
apply { spec.update(config, action) }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update configuration using given specification
|
|
||||||
*/
|
|
||||||
fun <C : Specific, S : Specification<C>> Specific.update(spec: S, action: C.() -> Unit) =
|
|
||||||
apply { spec.update(config, action) }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a style based on given specification
|
|
||||||
*/
|
|
||||||
fun <C : Specific, S : Specification<C>> S.createStyle(action: C.() -> Unit): Meta =
|
|
||||||
Config().also { update(it, action) }
|
|
||||||
|
|
||||||
|
|
||||||
fun <C : Specific> Specific.spec(
|
|
||||||
spec: Specification<C>,
|
|
||||||
key: Name? = null
|
|
||||||
): MutableMorphDelegate<Config, C> = MutableMorphDelegate(config, key) { spec.wrap(it) }
|
|
||||||
|
|
||||||
fun <T: Specific> MetaItem<*>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(it)}
|
|
||||||
|
|
||||||
@JvmName("configSpec")
|
|
||||||
fun <T: Specific> MetaItem<Config>.spec(spec: Specification<T>): T? = node?.let { spec.wrap(it)}
|
|
@ -1,72 +0,0 @@
|
|||||||
package hep.dataforge.meta
|
|
||||||
|
|
||||||
import hep.dataforge.names.Name
|
|
||||||
import hep.dataforge.names.NameToken
|
|
||||||
import kotlin.properties.ReadWriteProperty
|
|
||||||
import kotlin.reflect.KProperty
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A meta object with read-only meta base and changeable configuration on top of it
|
|
||||||
* @param base - unchangeable base
|
|
||||||
* @param style - the style
|
|
||||||
*/
|
|
||||||
class Styled(val base: Meta, val style: Config = Config().empty()) : AbstractMutableMeta<Styled>() {
|
|
||||||
override fun wrapNode(meta: Meta): Styled = Styled(meta)
|
|
||||||
|
|
||||||
override fun empty(): Styled = Styled(EmptyMeta)
|
|
||||||
|
|
||||||
override val items: Map<NameToken, MetaItem<Styled>>
|
|
||||||
get() = (base.items.keys + style.items.keys).associate { key ->
|
|
||||||
val value = base.items[key]
|
|
||||||
val styleValue = style[key]
|
|
||||||
val item: MetaItem<Styled> = when (value) {
|
|
||||||
null -> when (styleValue) {
|
|
||||||
null -> error("Should be unreachable")
|
|
||||||
is MetaItem.NodeItem -> MetaItem.NodeItem(Styled(style.empty(), styleValue.node))
|
|
||||||
is MetaItem.ValueItem -> styleValue
|
|
||||||
}
|
|
||||||
is MetaItem.ValueItem -> value
|
|
||||||
is MetaItem.NodeItem -> MetaItem.NodeItem(
|
|
||||||
Styled(value.node, styleValue?.node ?: Config.empty())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
key to item
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun set(name: Name, item: MetaItem<*>?) {
|
|
||||||
if (item == null) {
|
|
||||||
style.remove(name)
|
|
||||||
} else {
|
|
||||||
style[name] = item
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onChange(owner: Any?, action: (Name, before: MetaItem<*>?, after: MetaItem<*>?) -> Unit) {
|
|
||||||
//TODO test correct behavior
|
|
||||||
style.onChange(owner) { name, before, after -> action(name, before ?: base[name], after ?: base[name]) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removeListener(owner: Any?) {
|
|
||||||
style.removeListener(owner)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Styled.configure(meta: Meta) = apply { style.update(meta) }
|
|
||||||
|
|
||||||
fun Meta.withStyle(style: Meta = EmptyMeta) = if (this is Styled) {
|
|
||||||
this.apply { this.configure(style) }
|
|
||||||
} else {
|
|
||||||
Styled(this, style.toConfig())
|
|
||||||
}
|
|
||||||
|
|
||||||
class StyledNodeDelegate(val owner: Styled, val key: String?) : ReadWriteProperty<Any?, Meta> {
|
|
||||||
override fun getValue(thisRef: Any?, property: KProperty<*>): Meta {
|
|
||||||
return owner[key ?: property.name]?.node ?: EmptyMeta
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Meta) {
|
|
||||||
owner.style[key ?: property.name] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -6,6 +6,6 @@ package hep.dataforge.meta
|
|||||||
@DslMarker
|
@DslMarker
|
||||||
annotation class DFBuilder
|
annotation class DFBuilder
|
||||||
|
|
||||||
@Experimental(level = Experimental.Level.WARNING)
|
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
|
||||||
@Retention(AnnotationRetention.BINARY)
|
@Retention(AnnotationRetention.BINARY)
|
||||||
annotation class DFExperimental
|
annotation class DFExperimental
|
@ -1,137 +0,0 @@
|
|||||||
package hep.dataforge.meta
|
|
||||||
|
|
||||||
import hep.dataforge.names.Name
|
|
||||||
import hep.dataforge.values.DoubleArrayValue
|
|
||||||
import hep.dataforge.values.Null
|
|
||||||
import hep.dataforge.values.Value
|
|
||||||
import kotlin.jvm.JvmName
|
|
||||||
|
|
||||||
|
|
||||||
//Configurable delegates
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A property delegate that uses custom key
|
|
||||||
*/
|
|
||||||
fun Configurable.value(default: Any = Null, key: Name? = null): MutableValueDelegate<Config> =
|
|
||||||
MutableValueDelegate(config, key, Value.of(default))
|
|
||||||
|
|
||||||
fun <T> Configurable.value(
|
|
||||||
default: T? = null,
|
|
||||||
key: Name? = null,
|
|
||||||
writer: (T) -> Value = { Value.of(it) },
|
|
||||||
reader: (Value?) -> T
|
|
||||||
): ReadWriteDelegateWrapper<Value?, T> =
|
|
||||||
MutableValueDelegate(config, key, default?.let { Value.of(it) }).transform(reader = reader, writer = writer)
|
|
||||||
|
|
||||||
fun Configurable.string(default: String? = null, key: Name? = null): MutableStringDelegate<Config> =
|
|
||||||
MutableStringDelegate(config, key, default)
|
|
||||||
|
|
||||||
fun Configurable.boolean(default: Boolean? = null, key: Name? = null): MutableBooleanDelegate<Config> =
|
|
||||||
MutableBooleanDelegate(config, key, default)
|
|
||||||
|
|
||||||
fun Configurable.number(default: Number? = null, key: Name? = null): MutableNumberDelegate<Config> =
|
|
||||||
MutableNumberDelegate(config, key, default)
|
|
||||||
|
|
||||||
/* Number delegates*/
|
|
||||||
|
|
||||||
fun Configurable.int(default: Int? = null, key: Name? = null) =
|
|
||||||
number(default, key).int
|
|
||||||
|
|
||||||
fun Configurable.double(default: Double? = null, key: Name? = null) =
|
|
||||||
number(default, key).double
|
|
||||||
|
|
||||||
fun Configurable.long(default: Long? = null, key: Name? = null) =
|
|
||||||
number(default, key).long
|
|
||||||
|
|
||||||
fun Configurable.short(default: Short? = null, key: Name? = null) =
|
|
||||||
number(default, key).short
|
|
||||||
|
|
||||||
fun Configurable.float(default: Float? = null, key: Name? = null) =
|
|
||||||
number(default, key).float
|
|
||||||
|
|
||||||
|
|
||||||
@JvmName("safeString")
|
|
||||||
fun Configurable.string(default: String, key: Name? = null) =
|
|
||||||
MutableSafeStringDelegate(config, key) { default }
|
|
||||||
|
|
||||||
@JvmName("safeBoolean")
|
|
||||||
fun Configurable.boolean(default: Boolean, key: Name? = null) =
|
|
||||||
MutableSafeBooleanDelegate(config, key) { default }
|
|
||||||
|
|
||||||
@JvmName("safeNumber")
|
|
||||||
fun Configurable.number(default: Number, key: Name? = null) =
|
|
||||||
MutableSafeNumberDelegate(config, key) { default }
|
|
||||||
|
|
||||||
@JvmName("safeString")
|
|
||||||
fun Configurable.string(key: Name? = null, default: () -> String) =
|
|
||||||
MutableSafeStringDelegate(config, key, default)
|
|
||||||
|
|
||||||
@JvmName("safeBoolean")
|
|
||||||
fun Configurable.boolean(key: Name? = null, default: () -> Boolean) =
|
|
||||||
MutableSafeBooleanDelegate(config, key, default)
|
|
||||||
|
|
||||||
@JvmName("safeNumber")
|
|
||||||
fun Configurable.number(key: Name? = null, default: () -> Number) =
|
|
||||||
MutableSafeNumberDelegate(config, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
/* Safe number delegates*/
|
|
||||||
|
|
||||||
@JvmName("safeInt")
|
|
||||||
fun Configurable.int(default: Int, key: Name? = null) =
|
|
||||||
number(default, key).int
|
|
||||||
|
|
||||||
@JvmName("safeDouble")
|
|
||||||
fun Configurable.double(default: Double, key: Name? = null) =
|
|
||||||
number(default, key).double
|
|
||||||
|
|
||||||
@JvmName("safeLong")
|
|
||||||
fun Configurable.long(default: Long, key: Name? = null) =
|
|
||||||
number(default, key).long
|
|
||||||
|
|
||||||
@JvmName("safeShort")
|
|
||||||
fun Configurable.short(default: Short, key: Name? = null) =
|
|
||||||
number(default, key).short
|
|
||||||
|
|
||||||
@JvmName("safeFloat")
|
|
||||||
fun Configurable.float(default: Float, key: Name? = null) =
|
|
||||||
number(default, key).float
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enum delegate
|
|
||||||
*/
|
|
||||||
inline fun <reified E : Enum<E>> Configurable.enum(default: E, key: Name? = null) =
|
|
||||||
MutableSafeEnumvDelegate(config, key, default) { enumValueOf(it) }
|
|
||||||
|
|
||||||
/* Node delegates */
|
|
||||||
|
|
||||||
fun Configurable.node(key: Name? = null): MutableNodeDelegate<Config> = MutableNodeDelegate(config, key)
|
|
||||||
|
|
||||||
fun <T : Specific> Configurable.spec(spec: Specification<T>, key: Name? = null) =
|
|
||||||
MutableMorphDelegate(config, key) { spec.wrap(it) }
|
|
||||||
|
|
||||||
fun <T : Specific> Configurable.spec(builder: (Config) -> T, key: Name? = null) =
|
|
||||||
MutableMorphDelegate(config, key) { specification(builder).wrap(it) }
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Extra delegates for special cases
|
|
||||||
*/
|
|
||||||
|
|
||||||
fun Configurable.stringList(vararg strings: String, key: Name? = null): ReadWriteDelegateWrapper<Value?, List<String>> =
|
|
||||||
value(strings.asList(), key) { it?.list?.map { value -> value.string } ?: emptyList() }
|
|
||||||
|
|
||||||
fun Configurable.numberList(vararg numbers: Number, key: Name? = null): ReadWriteDelegateWrapper<Value?, List<Number>> =
|
|
||||||
value(numbers.asList(), key) { it?.list?.map { value -> value.number } ?: emptyList() }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A special delegate for double arrays
|
|
||||||
*/
|
|
||||||
fun Configurable.doubleArray(key: Name? = null): ReadWriteDelegateWrapper<Value?, DoubleArray> =
|
|
||||||
value(doubleArrayOf(), key) {
|
|
||||||
(it as? DoubleArrayValue)?.value
|
|
||||||
?: it?.list?.map { value -> value.number.toDouble() }?.toDoubleArray()
|
|
||||||
?: doubleArrayOf()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T : Configurable> Configurable.node(key: Name? = null, converter: (Meta) -> T) =
|
|
||||||
MutableMorphDelegate(config, key, converter)
|
|
@ -0,0 +1,24 @@
|
|||||||
|
package hep.dataforge.meta.descriptors
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object which provides its descriptor
|
||||||
|
*/
|
||||||
|
interface Described {
|
||||||
|
val descriptor: NodeDescriptor?
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val DESCRIPTOR_NODE = "@descriptor"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///**
|
||||||
|
// * If meta node supplies explicit descriptor, return it, otherwise try to use descriptor node from meta itself
|
||||||
|
// */
|
||||||
|
//val MetaRepr.descriptor: NodeDescriptor?
|
||||||
|
// get() {
|
||||||
|
// return if (this is Described) {
|
||||||
|
// descriptor
|
||||||
|
// } else {
|
||||||
|
// toMeta()[DESCRIPTOR_NODE].node?.let { NodeDescriptor.wrap(it) }
|
||||||
|
// }
|
||||||
|
// }
|
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.*
|
||||||
|
import hep.dataforge.meta.scheme.*
|
||||||
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.names.NameToken
|
import hep.dataforge.names.NameToken
|
||||||
import hep.dataforge.names.toName
|
import hep.dataforge.names.asName
|
||||||
|
import hep.dataforge.names.isEmpty
|
||||||
import hep.dataforge.values.False
|
import hep.dataforge.values.False
|
||||||
import hep.dataforge.values.True
|
import hep.dataforge.values.True
|
||||||
import hep.dataforge.values.Value
|
import hep.dataforge.values.Value
|
||||||
import hep.dataforge.values.ValueType
|
import hep.dataforge.values.ValueType
|
||||||
|
|
||||||
sealed class ItemDescriptor(override val config: Config) : Specific {
|
sealed class ItemDescriptor : Scheme() {
|
||||||
|
|
||||||
/**
|
|
||||||
* The name of this item
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
var name: String by string { error("Anonymous descriptors are not allowed") }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if same name siblings with this name are allowed
|
* True if same name siblings with this name are allowed
|
||||||
@ -36,7 +32,7 @@ sealed class ItemDescriptor(override val config: Config) : Specific {
|
|||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
var attributes by node()
|
var attributes by config()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if the item is required
|
* True if the item is required
|
||||||
@ -46,13 +42,33 @@ sealed class ItemDescriptor(override val config: Config) : Specific {
|
|||||||
abstract var required: Boolean
|
abstract var required: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure attributes of the descriptor
|
||||||
|
*/
|
||||||
|
fun ItemDescriptor.attributes(block: Config.() -> Unit) {
|
||||||
|
(attributes ?: Config().also { this.config = it }).apply(block)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if given item suits the descriptor
|
||||||
|
*/
|
||||||
|
fun ItemDescriptor.validateItem(item: MetaItem<*>?): Boolean {
|
||||||
|
return when (this) {
|
||||||
|
is ValueDescriptor -> isAllowedValue(item.value ?: return false)
|
||||||
|
is NodeDescriptor -> items.all { (key, d) ->
|
||||||
|
d.validateItem(item.node[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Descriptor for meta node. Could contain additional information for viewing
|
* Descriptor for meta node. Could contain additional information for viewing
|
||||||
* and editing.
|
* and editing.
|
||||||
*
|
*
|
||||||
* @author Alexander Nozik
|
* @author Alexander Nozik
|
||||||
*/
|
*/
|
||||||
class NodeDescriptor(config: Config) : ItemDescriptor(config) {
|
@DFBuilder
|
||||||
|
class NodeDescriptor : ItemDescriptor() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if the node is required
|
* True if the node is required
|
||||||
@ -66,46 +82,74 @@ class NodeDescriptor(config: Config) : ItemDescriptor(config) {
|
|||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
var default: Config? by node()
|
var default by node()
|
||||||
|
|
||||||
/**
|
|
||||||
* The list of value descriptors
|
|
||||||
*/
|
|
||||||
val values: Map<String, ValueDescriptor>
|
|
||||||
get() = config.getIndexed(VALUE_KEY.toName()).entries.associate { (name, node) ->
|
|
||||||
name to ValueDescriptor.wrap(node.node ?: error("Value descriptor must be a node"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun value(name: String, descriptor: ValueDescriptor) {
|
|
||||||
if (items.keys.contains(name)) error("The key $name already exists in descriptor")
|
|
||||||
val token = NameToken(VALUE_KEY, name)
|
|
||||||
config[token] = descriptor.config
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a value descriptor using block for
|
|
||||||
*/
|
|
||||||
fun value(name: String, block: ValueDescriptor.() -> Unit) {
|
|
||||||
value(name, ValueDescriptor.build { this.name = name }.apply(block))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The map of children node descriptors
|
* The map of children node descriptors
|
||||||
*/
|
*/
|
||||||
val nodes: Map<String, NodeDescriptor>
|
val nodes: Map<String, NodeDescriptor>
|
||||||
get() = config.getIndexed(NODE_KEY.toName()).entries.associate { (name, node) ->
|
get() = config.getIndexed(NODE_KEY.asName()).entries.associate { (name, node) ->
|
||||||
name to wrap(node.node ?: error("Node descriptor must be a node"))
|
name to wrap(node.node ?: error("Node descriptor must be a node"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
fun node(name: String, descriptor: NodeDescriptor) {
|
* Define a child item descriptor for this node
|
||||||
|
*/
|
||||||
|
fun defineItem(name: String, descriptor: ItemDescriptor) {
|
||||||
if (items.keys.contains(name)) error("The key $name already exists in descriptor")
|
if (items.keys.contains(name)) error("The key $name already exists in descriptor")
|
||||||
val token = NameToken(NODE_KEY, name)
|
val token = when (descriptor) {
|
||||||
|
is NodeDescriptor -> NameToken(NODE_KEY, name)
|
||||||
|
is ValueDescriptor -> NameToken(VALUE_KEY, name)
|
||||||
|
}
|
||||||
config[token] = descriptor.config
|
config[token] = descriptor.config
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun node(name: String, block: NodeDescriptor.() -> Unit) {
|
|
||||||
node(name, build { this.name = name }.apply(block))
|
fun defineNode(name: String, block: NodeDescriptor.() -> Unit) {
|
||||||
|
val token = NameToken(NODE_KEY, name)
|
||||||
|
if (config[token] == null) {
|
||||||
|
config[token] = NodeDescriptor(block)
|
||||||
|
} else {
|
||||||
|
NodeDescriptor.update(config[token].node ?: error("Node expected"), block)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildNode(name: Name): NodeDescriptor {
|
||||||
|
return when (name.length) {
|
||||||
|
0 -> this
|
||||||
|
1 -> {
|
||||||
|
val token = NameToken(NODE_KEY, name.toString())
|
||||||
|
val config: Config = config[token].node ?: Config().also { config[token] = it }
|
||||||
|
wrap(config)
|
||||||
|
}
|
||||||
|
else -> buildNode(name.first()?.asName()!!).buildNode(name.cutFirst())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun defineNode(name: Name, block: NodeDescriptor.() -> Unit) {
|
||||||
|
buildNode(name).apply(block)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of value descriptors
|
||||||
|
*/
|
||||||
|
val values: Map<String, ValueDescriptor>
|
||||||
|
get() = config.getIndexed(VALUE_KEY.asName()).entries.associate { (name, node) ->
|
||||||
|
name to ValueDescriptor.wrap(node.node ?: error("Value descriptor must be a node"))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a value descriptor using block for
|
||||||
|
*/
|
||||||
|
fun defineValue(name: String, block: ValueDescriptor.() -> Unit) {
|
||||||
|
defineItem(name, ValueDescriptor(block))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun defineValue(name: Name, block: ValueDescriptor.() -> Unit) {
|
||||||
|
require(name.length >= 1) { "Name length for value descriptor must be non-empty" }
|
||||||
|
buildNode(name.cutLast()).defineValue(name.last().toString(), block)
|
||||||
}
|
}
|
||||||
|
|
||||||
val items: Map<String, ItemDescriptor> get() = nodes + values
|
val items: Map<String, ItemDescriptor> get() = nodes + values
|
||||||
@ -113,18 +157,28 @@ class NodeDescriptor(config: Config) : ItemDescriptor(config) {
|
|||||||
|
|
||||||
//override val descriptor: NodeDescriptor = empty("descriptor")
|
//override val descriptor: NodeDescriptor = empty("descriptor")
|
||||||
|
|
||||||
companion object : Specification<NodeDescriptor> {
|
companion object : SchemeSpec<NodeDescriptor>(::NodeDescriptor) {
|
||||||
|
|
||||||
// const val ITEM_KEY = "item"
|
// const val ITEM_KEY = "item"
|
||||||
const val NODE_KEY = "node"
|
const val NODE_KEY = "node"
|
||||||
const val VALUE_KEY = "value"
|
const val VALUE_KEY = "value"
|
||||||
|
|
||||||
override fun wrap(config: Config): NodeDescriptor = NodeDescriptor(config)
|
//override fun wrap(config: Config): NodeDescriptor = NodeDescriptor(config)
|
||||||
|
|
||||||
//TODO infer descriptor from spec
|
//TODO infer descriptor from spec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a descriptor item associated with given name or null if item for given name not provided
|
||||||
|
*/
|
||||||
|
operator fun ItemDescriptor.get(name: Name): ItemDescriptor? {
|
||||||
|
if (name.isEmpty()) return this
|
||||||
|
return when (this) {
|
||||||
|
is ValueDescriptor -> null // empty name already checked
|
||||||
|
is NodeDescriptor -> items[name.first()!!.toString()]?.get(name.cutFirst())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A descriptor for meta value
|
* A descriptor for meta value
|
||||||
@ -133,7 +187,7 @@ class NodeDescriptor(config: Config) : ItemDescriptor(config) {
|
|||||||
*
|
*
|
||||||
* @author Alexander Nozik
|
* @author Alexander Nozik
|
||||||
*/
|
*/
|
||||||
class ValueDescriptor(config: Config) : ItemDescriptor(config) {
|
class ValueDescriptor : ItemDescriptor() {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -159,8 +213,8 @@ class ValueDescriptor(config: Config) : ItemDescriptor(config) {
|
|||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
var type: List<ValueType> by value {
|
var type: List<ValueType> by item {
|
||||||
it?.list?.map { v -> ValueType.valueOf(v.string) } ?: emptyList()
|
it?.value?.list?.map { v -> ValueType.valueOf(v.string) } ?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun type(vararg t: ValueType) {
|
fun type(vararg t: ValueType) {
|
||||||
@ -201,16 +255,11 @@ class ValueDescriptor(config: Config) : ItemDescriptor(config) {
|
|||||||
this.allowedValues = v.map { Value.of(it) }
|
this.allowedValues = v.map { Value.of(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object : Specification<ValueDescriptor> {
|
companion object : SchemeSpec<ValueDescriptor>(::ValueDescriptor) {
|
||||||
|
// inline fun <reified E : Enum<E>> enum(name: String) = ValueDescriptor {
|
||||||
override fun wrap(config: Config): ValueDescriptor = ValueDescriptor(config)
|
// type(ValueType.STRING)
|
||||||
|
// this.allowedValues = enumValues<E>().map { Value.of(it.name) }
|
||||||
inline fun <reified E : Enum<E>> enum(name: String) =
|
// }
|
||||||
build {
|
|
||||||
this.name = name
|
|
||||||
type(ValueType.STRING)
|
|
||||||
this.allowedValues = enumValues<E>().map { Value.of(it.name) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// /**
|
// /**
|
||||||
// * Build a value descriptor from annotation
|
// * Build a value descriptor from annotation
|
@ -1,16 +1,8 @@
|
|||||||
package hep.dataforge.meta
|
package hep.dataforge.meta
|
||||||
|
|
||||||
import hep.dataforge.descriptors.NodeDescriptor
|
import hep.dataforge.meta.descriptors.NodeDescriptor
|
||||||
import hep.dataforge.values.Value
|
import hep.dataforge.values.Value
|
||||||
|
|
||||||
///**
|
|
||||||
// * Find all elements with given body
|
|
||||||
// */
|
|
||||||
//private fun Meta.byBody(body: String): Map<String, MetaItem<*>> =
|
|
||||||
// items.filter { it.key.body == body }.mapKeys { it.key.index }
|
|
||||||
//
|
|
||||||
//private fun Meta.distinctNames() = items.keys.map { it.body }.distinct()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert meta to map of maps
|
* Convert meta to map of maps
|
||||||
*/
|
*/
|
||||||
@ -28,7 +20,7 @@ fun Meta.toMap(descriptor: NodeDescriptor? = null): Map<String, Any?> {
|
|||||||
* Convert map of maps to meta
|
* Convert map of maps to meta
|
||||||
*/
|
*/
|
||||||
@DFExperimental
|
@DFExperimental
|
||||||
fun Map<String, Any?>.toMeta(descriptor: NodeDescriptor? = null): Meta = buildMeta {
|
fun Map<String, Any?>.toMeta(descriptor: NodeDescriptor? = null): Meta = Meta {
|
||||||
entries.forEach { (key, value) ->
|
entries.forEach { (key, value) ->
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
when (value) {
|
when (value) {
|
||||||
|
@ -1,432 +0,0 @@
|
|||||||
package hep.dataforge.meta
|
|
||||||
|
|
||||||
import hep.dataforge.names.Name
|
|
||||||
import hep.dataforge.names.asName
|
|
||||||
import hep.dataforge.values.Null
|
|
||||||
import hep.dataforge.values.Value
|
|
||||||
import hep.dataforge.values.asValue
|
|
||||||
import kotlin.jvm.JvmName
|
|
||||||
import kotlin.properties.ReadOnlyProperty
|
|
||||||
import kotlin.properties.ReadWriteProperty
|
|
||||||
import kotlin.reflect.KProperty
|
|
||||||
|
|
||||||
/* Meta delegates */
|
|
||||||
|
|
||||||
//TODO add caching for sealed nodes
|
|
||||||
|
|
||||||
class ValueDelegate(val meta: Meta, private val key: String? = null, private val default: Value? = null) :
|
|
||||||
ReadOnlyProperty<Any?, Value?> {
|
|
||||||
override fun getValue(thisRef: Any?, property: KProperty<*>): Value? {
|
|
||||||
return meta[key ?: property.name]?.value ?: default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class StringDelegate(val meta: Meta, private val key: String? = null, private val default: String? = null) :
|
|
||||||
ReadOnlyProperty<Any?, String?> {
|
|
||||||
override fun getValue(thisRef: Any?, property: KProperty<*>): String? {
|
|
||||||
return meta[key ?: property.name]?.string ?: default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BooleanDelegate(
|
|
||||||
val meta: Meta,
|
|
||||||
private val key: String? = null,
|
|
||||||
private val default: Boolean? = null
|
|
||||||
) : ReadOnlyProperty<Any?, Boolean?> {
|
|
||||||
override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean? {
|
|
||||||
return meta[key ?: property.name]?.boolean ?: default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class NumberDelegate(
|
|
||||||
val meta: Meta,
|
|
||||||
private val key: String? = null,
|
|
||||||
private val default: Number? = null
|
|
||||||
) : ReadOnlyProperty<Any?, Number?> {
|
|
||||||
override fun getValue(thisRef: Any?, property: KProperty<*>): Number? {
|
|
||||||
return meta[key ?: property.name]?.number ?: default
|
|
||||||
}
|
|
||||||
|
|
||||||
//delegates for number transformation
|
|
||||||
|
|
||||||
val double get() = DelegateWrapper(this) { it?.toDouble() }
|
|
||||||
val int get() = DelegateWrapper(this) { it?.toInt() }
|
|
||||||
val short get() = DelegateWrapper(this) { it?.toShort() }
|
|
||||||
val long get() = DelegateWrapper(this) { it?.toLong() }
|
|
||||||
}
|
|
||||||
|
|
||||||
class DelegateWrapper<T, R>(val delegate: ReadOnlyProperty<Any?, T>, val reader: (T) -> R) :
|
|
||||||
ReadOnlyProperty<Any?, R> {
|
|
||||||
override fun getValue(thisRef: Any?, property: KProperty<*>): R {
|
|
||||||
return reader(delegate.getValue(thisRef, property))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Delegates with non-null values
|
|
||||||
|
|
||||||
class SafeStringDelegate(
|
|
||||||
val meta: Meta,
|
|
||||||
private val key: String? = null,
|
|
||||||
default: () -> String
|
|
||||||
) : ReadOnlyProperty<Any?, String> {
|
|
||||||
|
|
||||||
private val default: String by lazy(default)
|
|
||||||
|
|
||||||
override fun getValue(thisRef: Any?, property: KProperty<*>): String {
|
|
||||||
return meta[key ?: property.name]?.string ?: default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SafeBooleanDelegate(
|
|
||||||
val meta: Meta,
|
|
||||||
private val key: String? = null,
|
|
||||||
default: () -> Boolean
|
|
||||||
) : ReadOnlyProperty<Any?, Boolean> {
|
|
||||||
|
|
||||||
private val default: Boolean by lazy(default)
|
|
||||||
|
|
||||||
override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean {
|
|
||||||
return meta[key ?: property.name]?.boolean ?: default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SafeNumberDelegate(
|
|
||||||
val meta: Meta,
|
|
||||||
private val key: String? = null,
|
|
||||||
default: () -> Number
|
|
||||||
) : ReadOnlyProperty<Any?, Number> {
|
|
||||||
|
|
||||||
private val default: Number by lazy(default)
|
|
||||||
|
|
||||||
override fun getValue(thisRef: Any?, property: KProperty<*>): Number {
|
|
||||||
return meta[key ?: property.name]?.number ?: default
|
|
||||||
}
|
|
||||||
|
|
||||||
val double get() = DelegateWrapper(this) { it.toDouble() }
|
|
||||||
val int get() = DelegateWrapper(this) { it.toInt() }
|
|
||||||
val short get() = DelegateWrapper(this) { it.toShort() }
|
|
||||||
val long get() = DelegateWrapper(this) { it.toLong() }
|
|
||||||
}
|
|
||||||
|
|
||||||
class SafeEnumDelegate<E : Enum<E>>(
|
|
||||||
val meta: Meta,
|
|
||||||
private val key: String? = null,
|
|
||||||
private val default: E,
|
|
||||||
private val resolver: (String) -> E
|
|
||||||
) : ReadOnlyProperty<Any?, E> {
|
|
||||||
override fun getValue(thisRef: Any?, property: KProperty<*>): E {
|
|
||||||
return (meta[key ?: property.name]?.string)?.let { resolver(it) } ?: default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Child node delegate
|
|
||||||
|
|
||||||
class ChildDelegate<T>(
|
|
||||||
val meta: Meta,
|
|
||||||
private val key: String? = null,
|
|
||||||
private val converter: (Meta) -> T
|
|
||||||
) : ReadOnlyProperty<Any?, T?> {
|
|
||||||
override fun getValue(thisRef: Any?, property: KProperty<*>): T? {
|
|
||||||
return meta[key ?: property.name]?.node?.let { converter(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Read-only delegates for Metas
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A property delegate that uses custom key
|
|
||||||
*/
|
|
||||||
fun Meta.value(default: Value = Null, key: String? = null) = ValueDelegate(this, key, default)
|
|
||||||
|
|
||||||
fun Meta.string(default: String? = null, key: String? = null) = StringDelegate(this, key, default)
|
|
||||||
|
|
||||||
fun Meta.boolean(default: Boolean? = null, key: String? = null) = BooleanDelegate(this, key, default)
|
|
||||||
|
|
||||||
fun Meta.number(default: Number? = null, key: String? = null) = NumberDelegate(this, key, default)
|
|
||||||
|
|
||||||
fun Meta.node(key: String? = null) = ChildDelegate(this, key) { it }
|
|
||||||
|
|
||||||
@JvmName("safeString")
|
|
||||||
fun Meta.string(default: String, key: String? = null) =
|
|
||||||
SafeStringDelegate(this, key) { default }
|
|
||||||
|
|
||||||
@JvmName("safeBoolean")
|
|
||||||
fun Meta.boolean(default: Boolean, key: String? = null) =
|
|
||||||
SafeBooleanDelegate(this, key) { default }
|
|
||||||
|
|
||||||
@JvmName("safeNumber")
|
|
||||||
fun Meta.number(default: Number, key: String? = null) =
|
|
||||||
SafeNumberDelegate(this, key) { default }
|
|
||||||
|
|
||||||
@JvmName("safeString")
|
|
||||||
fun Meta.string(key: String? = null, default: () -> String) =
|
|
||||||
SafeStringDelegate(this, key, default)
|
|
||||||
|
|
||||||
@JvmName("safeBoolean")
|
|
||||||
fun Meta.boolean(key: String? = null, default: () -> Boolean) =
|
|
||||||
SafeBooleanDelegate(this, key, default)
|
|
||||||
|
|
||||||
@JvmName("safeNumber")
|
|
||||||
fun Meta.number(key: String? = null, default: () -> Number) =
|
|
||||||
SafeNumberDelegate(this, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
inline fun <reified E : Enum<E>> Meta.enum(default: E, key: String? = null) =
|
|
||||||
SafeEnumDelegate(this, key, default) { enumValueOf(it) }
|
|
||||||
|
|
||||||
/* Read-write delegates */
|
|
||||||
|
|
||||||
class MutableValueDelegate<M : MutableMeta<M>>(
|
|
||||||
val meta: M,
|
|
||||||
private val key: Name? = null,
|
|
||||||
private val default: Value? = null
|
|
||||||
) : ReadWriteProperty<Any?, Value?> {
|
|
||||||
override fun getValue(thisRef: Any?, property: KProperty<*>): Value? {
|
|
||||||
return meta[key ?: property.name.asName()]?.value ?: default
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Value?) {
|
|
||||||
val name = key ?: property.name.asName()
|
|
||||||
if (value == null) {
|
|
||||||
meta.remove(name)
|
|
||||||
} else {
|
|
||||||
meta.setValue(name, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T> transform(writer: (T) -> Value? = { Value.of(it) }, reader: (Value?) -> T) =
|
|
||||||
ReadWriteDelegateWrapper(this, reader, writer)
|
|
||||||
}
|
|
||||||
|
|
||||||
class MutableStringDelegate<M : MutableMeta<M>>(
|
|
||||||
val meta: M,
|
|
||||||
private val key: Name? = null,
|
|
||||||
private val default: String? = null
|
|
||||||
) : ReadWriteProperty<Any?, String?> {
|
|
||||||
override fun getValue(thisRef: Any?, property: KProperty<*>): String? {
|
|
||||||
return meta[key ?: property.name.asName()]?.string ?: default
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: String?) {
|
|
||||||
val name = key ?: property.name.asName()
|
|
||||||
if (value == null) {
|
|
||||||
meta.remove(name)
|
|
||||||
} else {
|
|
||||||
meta.setValue(name, value.asValue())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MutableBooleanDelegate<M : MutableMeta<M>>(
|
|
||||||
val meta: M,
|
|
||||||
private val key: Name? = null,
|
|
||||||
private val default: Boolean? = null
|
|
||||||
) : ReadWriteProperty<Any?, Boolean?> {
|
|
||||||
override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean? {
|
|
||||||
return meta[key ?: property.name.asName()]?.boolean ?: default
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean?) {
|
|
||||||
val name = key ?: property.name.asName()
|
|
||||||
if (value == null) {
|
|
||||||
meta.remove(name)
|
|
||||||
} else {
|
|
||||||
meta.setValue(name, value.asValue())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MutableNumberDelegate<M : MutableMeta<M>>(
|
|
||||||
val meta: M,
|
|
||||||
private val key: Name? = null,
|
|
||||||
private val default: Number? = null
|
|
||||||
) : ReadWriteProperty<Any?, Number?> {
|
|
||||||
override fun getValue(thisRef: Any?, property: KProperty<*>): Number? {
|
|
||||||
return meta[key ?: property.name.asName()]?.number ?: default
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Number?) {
|
|
||||||
val name = key ?: property.name.asName()
|
|
||||||
if (value == null) {
|
|
||||||
meta.remove(name)
|
|
||||||
} else {
|
|
||||||
meta.setValue(name, value.asValue())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val double get() = ReadWriteDelegateWrapper(this, reader = { it?.toDouble() }, writer = { it })
|
|
||||||
val float get() = ReadWriteDelegateWrapper(this, reader = { it?.toFloat() }, writer = { it })
|
|
||||||
val int get() = ReadWriteDelegateWrapper(this, reader = { it?.toInt() }, writer = { it })
|
|
||||||
val short get() = ReadWriteDelegateWrapper(this, reader = { it?.toShort() }, writer = { it })
|
|
||||||
val long get() = ReadWriteDelegateWrapper(this, reader = { it?.toLong() }, writer = { it })
|
|
||||||
}
|
|
||||||
|
|
||||||
//Delegates with non-null values
|
|
||||||
|
|
||||||
class MutableSafeStringDelegate<M : MutableMeta<M>>(
|
|
||||||
val meta: M,
|
|
||||||
private val key: Name? = null,
|
|
||||||
default: () -> String
|
|
||||||
) : ReadWriteProperty<Any?, String> {
|
|
||||||
|
|
||||||
private val default: String by lazy(default)
|
|
||||||
|
|
||||||
override fun getValue(thisRef: Any?, property: KProperty<*>): String {
|
|
||||||
return meta[key ?: property.name.asName()]?.string ?: default
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
|
|
||||||
meta.setValue(key ?: property.name.asName(), value.asValue())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MutableSafeBooleanDelegate<M : MutableMeta<M>>(
|
|
||||||
val meta: M,
|
|
||||||
private val key: Name? = null,
|
|
||||||
default: () -> Boolean
|
|
||||||
) : ReadWriteProperty<Any?, Boolean> {
|
|
||||||
|
|
||||||
private val default: Boolean by lazy(default)
|
|
||||||
|
|
||||||
override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean {
|
|
||||||
return meta[key ?: property.name.asName()]?.boolean ?: default
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) {
|
|
||||||
meta.setValue(key ?: property.name.asName(), value.asValue())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MutableSafeNumberDelegate<M : MutableMeta<M>>(
|
|
||||||
val meta: M,
|
|
||||||
private val key: Name? = null,
|
|
||||||
default: () -> Number
|
|
||||||
) : ReadWriteProperty<Any?, Number> {
|
|
||||||
|
|
||||||
private val default: Number by lazy(default)
|
|
||||||
|
|
||||||
override fun getValue(thisRef: Any?, property: KProperty<*>): Number {
|
|
||||||
return meta[key ?: property.name.asName()]?.number ?: default
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Number) {
|
|
||||||
meta.setValue(key ?: property.name.asName(), value.asValue())
|
|
||||||
}
|
|
||||||
|
|
||||||
val double get() = ReadWriteDelegateWrapper(this, reader = { it.toDouble() }, writer = { it })
|
|
||||||
val float get() = ReadWriteDelegateWrapper(this, reader = { it.toFloat() }, writer = { it })
|
|
||||||
val int get() = ReadWriteDelegateWrapper(this, reader = { it.toInt() }, writer = { it })
|
|
||||||
val short get() = ReadWriteDelegateWrapper(this, reader = { it.toShort() }, writer = { it })
|
|
||||||
val long get() = ReadWriteDelegateWrapper(this, reader = { it.toLong() }, writer = { it })
|
|
||||||
}
|
|
||||||
|
|
||||||
class MutableSafeEnumvDelegate<M : MutableMeta<M>, E : Enum<E>>(
|
|
||||||
val meta: M,
|
|
||||||
private val key: Name? = null,
|
|
||||||
private val default: E,
|
|
||||||
private val resolver: (String) -> E
|
|
||||||
) : ReadWriteProperty<Any?, E> {
|
|
||||||
override fun getValue(thisRef: Any?, property: KProperty<*>): E {
|
|
||||||
return (meta[key ?: property.name.asName()]?.string)?.let { resolver(it) } ?: default
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: E) {
|
|
||||||
meta.setValue(key ?: property.name.asName(), value.name.asValue())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Child node delegate
|
|
||||||
|
|
||||||
class MutableNodeDelegate<M : MutableMeta<M>>(
|
|
||||||
val meta: M,
|
|
||||||
private val key: Name? = null
|
|
||||||
) : ReadWriteProperty<Any?, M?> {
|
|
||||||
override fun getValue(thisRef: Any?, property: KProperty<*>): M? {
|
|
||||||
return meta[key ?: property.name.asName()]?.node
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: M?) {
|
|
||||||
meta[key ?: property.name.asName()] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MutableMorphDelegate<M : MutableMeta<M>, T : Configurable>(
|
|
||||||
val meta: M,
|
|
||||||
private val key: Name? = null,
|
|
||||||
private val converter: (Meta) -> T
|
|
||||||
) : ReadWriteProperty<Any?, T?> {
|
|
||||||
override fun getValue(thisRef: Any?, property: KProperty<*>): T? {
|
|
||||||
return meta[key ?: property.name.asName()]?.node?.let(converter)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
|
|
||||||
if (value == null) {
|
|
||||||
meta.remove(key ?: property.name.asName())
|
|
||||||
} else {
|
|
||||||
meta[key ?: property.name.asName()] = value.config
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ReadWriteDelegateWrapper<T, R>(
|
|
||||||
val delegate: ReadWriteProperty<Any?, T>,
|
|
||||||
val reader: (T) -> R,
|
|
||||||
val writer: (R) -> T
|
|
||||||
) : ReadWriteProperty<Any?, R> {
|
|
||||||
override fun getValue(thisRef: Any?, property: KProperty<*>): R {
|
|
||||||
return reader(delegate.getValue(thisRef, property))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: R) {
|
|
||||||
delegate.setValue(thisRef, property, writer(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//Read-write delegates
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A property delegate that uses custom key
|
|
||||||
*/
|
|
||||||
fun <M : MutableMeta<M>> M.value(default: Value = Null, key: Name? = null) =
|
|
||||||
MutableValueDelegate(this, key, default)
|
|
||||||
|
|
||||||
fun <M : MutableMeta<M>> M.string(default: String? = null, key: Name? = null) =
|
|
||||||
MutableStringDelegate(this, key, default)
|
|
||||||
|
|
||||||
fun <M : MutableMeta<M>> M.boolean(default: Boolean? = null, key: Name? = null) =
|
|
||||||
MutableBooleanDelegate(this, key, default)
|
|
||||||
|
|
||||||
fun <M : MutableMeta<M>> M.number(default: Number? = null, key: Name? = null) =
|
|
||||||
MutableNumberDelegate(this, key, default)
|
|
||||||
|
|
||||||
fun <M : MutableMeta<M>> M.node(key: Name? = null) =
|
|
||||||
MutableNodeDelegate(this, key)
|
|
||||||
|
|
||||||
@JvmName("safeString")
|
|
||||||
fun <M : MutableMeta<M>> M.string(default: String, key: Name? = null) =
|
|
||||||
MutableSafeStringDelegate(this, key) { default }
|
|
||||||
|
|
||||||
@JvmName("safeBoolean")
|
|
||||||
fun <M : MutableMeta<M>> M.boolean(default: Boolean, key: Name? = null) =
|
|
||||||
MutableSafeBooleanDelegate(this, key) { default }
|
|
||||||
|
|
||||||
@JvmName("safeNumber")
|
|
||||||
fun <M : MutableMeta<M>> M.number(default: Number, key: Name? = null) =
|
|
||||||
MutableSafeNumberDelegate(this, key) { default }
|
|
||||||
|
|
||||||
@JvmName("safeString")
|
|
||||||
fun <M : MutableMeta<M>> M.string(key: Name? = null, default: () -> String) =
|
|
||||||
MutableSafeStringDelegate(this, key, default)
|
|
||||||
|
|
||||||
@JvmName("safeBoolean")
|
|
||||||
fun <M : MutableMeta<M>> M.boolean(key: Name? = null, default: () -> Boolean) =
|
|
||||||
MutableSafeBooleanDelegate(this, key, default)
|
|
||||||
|
|
||||||
@JvmName("safeNumber")
|
|
||||||
fun <M : MutableMeta<M>> M.number(key: Name? = null, default: () -> Number) =
|
|
||||||
MutableSafeNumberDelegate(this, key, default)
|
|
||||||
|
|
||||||
|
|
||||||
inline fun <M : MutableMeta<M>, reified E : Enum<E>> M.enum(default: E, key: Name? = null) =
|
|
||||||
MutableSafeEnumvDelegate(this, key, default) { enumValueOf(it) }
|
|
@ -26,26 +26,13 @@ fun Meta.getIndexed(name: Name): Map<String, MetaItem<*>> {
|
|||||||
@DFExperimental
|
@DFExperimental
|
||||||
fun Meta.getIndexed(name: String): Map<String, MetaItem<*>> = this@getIndexed.getIndexed(name.toName())
|
fun Meta.getIndexed(name: String): Map<String, MetaItem<*>> = this@getIndexed.getIndexed(name.toName())
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all items matching given name.
|
* Get all items matching given name.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
@DFExperimental
|
@DFExperimental
|
||||||
fun <M : MetaNode<M>> M.getIndexed(name: Name): Map<String, MetaItem<M>> {
|
fun <M : MetaNode<M>> M.getIndexed(name: Name): Map<String, MetaItem<M>> =
|
||||||
val root: MetaNode<M>? = when (name.length) {
|
(this as Meta).getIndexed(name) as Map<String, MetaItem<M>>
|
||||||
0 -> error("Can't use empty name for that")
|
|
||||||
1 -> this
|
|
||||||
else -> (this[name.cutLast()] as? MetaItem.NodeItem<M>)?.node
|
|
||||||
}
|
|
||||||
|
|
||||||
val (body, index) = name.last()!!
|
|
||||||
val regex = index.toRegex()
|
|
||||||
|
|
||||||
return root?.items
|
|
||||||
?.filter { it.key.body == body && (index.isEmpty() || regex.matches(it.key.index)) }
|
|
||||||
?.mapKeys { it.key.index }
|
|
||||||
?: emptyMap()
|
|
||||||
}
|
|
||||||
|
|
||||||
@DFExperimental
|
@DFExperimental
|
||||||
fun <M : MetaNode<M>> M.getIndexed(name: String): Map<String, MetaItem<M>> = getIndexed(name.toName())
|
fun <M : MetaNode<M>> M.getIndexed(name: String): Map<String, MetaItem<M>> = getIndexed(name.toName())
|
@ -0,0 +1,68 @@
|
|||||||
|
package hep.dataforge.meta.scheme
|
||||||
|
|
||||||
|
import hep.dataforge.meta.*
|
||||||
|
import hep.dataforge.meta.descriptors.*
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.toName
|
||||||
|
import hep.dataforge.values.Value
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A container that holds a [Config] and a default item provider.
|
||||||
|
* Default item provider could be use for example to reference parent configuration.
|
||||||
|
* It is not possible to know if some property is declared by provider just by looking on [Configurable],
|
||||||
|
* this information should be provided externally.
|
||||||
|
*/
|
||||||
|
interface Configurable : Described {
|
||||||
|
/**
|
||||||
|
* Backing config
|
||||||
|
*/
|
||||||
|
val config: Config
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default meta item provider
|
||||||
|
*/
|
||||||
|
fun getDefaultItem(name: Name): MetaItem<*>? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if property with given [name] could be assigned to [value]
|
||||||
|
*/
|
||||||
|
fun validateItem(name: Name, item: MetaItem<*>?): Boolean {
|
||||||
|
val descriptor = descriptor?.get(name)
|
||||||
|
return descriptor?.validateItem(item) ?: true
|
||||||
|
}
|
||||||
|
|
||||||
|
override val descriptor: NodeDescriptor? get() = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a property with default
|
||||||
|
*/
|
||||||
|
fun getProperty(name: Name): MetaItem<*>? =
|
||||||
|
config[name] ?: getDefaultItem(name) ?: descriptor?.get(name)?.defaultItem()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a configurable property
|
||||||
|
*/
|
||||||
|
fun setProperty(name: Name, item: MetaItem<*>?) {
|
||||||
|
if (validateItem(name, item)) {
|
||||||
|
config[name] = item
|
||||||
|
} else {
|
||||||
|
error("Validation failed for property $name with value $item")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Configurable.getProperty(key: String) = getProperty(key.toName())
|
||||||
|
|
||||||
|
fun Configurable.setProperty(name: Name, value: Value?) = setProperty(name, value?.let { MetaItem.ValueItem(value) })
|
||||||
|
fun Configurable.setProperty(name: Name, meta: Meta?) = setProperty(name, meta?.let { MetaItem.NodeItem(meta) })
|
||||||
|
|
||||||
|
fun Configurable.setProperty(key: String, item: MetaItem<*>?) {
|
||||||
|
setProperty(key.toName(), item)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Configurable.setProperty(key: String, value: Value?) = setProperty(key, value?.let { MetaItem.ValueItem(value) })
|
||||||
|
fun Configurable.setProperty(key: String, meta: Meta?) = setProperty(key, meta?.let { MetaItem.NodeItem(meta) })
|
||||||
|
|
||||||
|
fun <T : Configurable> T.configure(meta: Meta): T = this.apply { config.update(meta) }
|
||||||
|
|
||||||
|
inline fun <T : Configurable> T.configure(action: Config.() -> Unit): T = apply { config.apply(action) }
|
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
|
import hep.dataforge.names.Name
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -8,7 +9,7 @@ import hep.dataforge.names.Name
|
|||||||
interface TransformationRule {
|
interface TransformationRule {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if this transformation
|
* Check if this transformation should be applied to a node with given name and value
|
||||||
*/
|
*/
|
||||||
fun matches(name: Name, item: MetaItem<*>?): Boolean
|
fun matches(name: Name, item: MetaItem<*>?): Boolean
|
||||||
|
|
||||||
@ -29,7 +30,8 @@ interface TransformationRule {
|
|||||||
/**
|
/**
|
||||||
* A transformation which keeps all elements, matching [selector] unchanged.
|
* A transformation which keeps all elements, matching [selector] unchanged.
|
||||||
*/
|
*/
|
||||||
data class KeepTransformationRule(val selector: (Name) -> Boolean) : TransformationRule {
|
data class KeepTransformationRule(val selector: (Name) -> Boolean) :
|
||||||
|
TransformationRule {
|
||||||
override fun matches(name: Name, item: MetaItem<*>?): Boolean {
|
override fun matches(name: Name, item: MetaItem<*>?): Boolean {
|
||||||
return selector(name)
|
return selector(name)
|
||||||
}
|
}
|
||||||
@ -87,7 +89,8 @@ inline class MetaTransformation(val transformations: Collection<TransformationRu
|
|||||||
/**
|
/**
|
||||||
* Produce new meta using only those items that match transformation rules
|
* Produce new meta using only those items that match transformation rules
|
||||||
*/
|
*/
|
||||||
fun transform(source: Meta): Meta = buildMeta {
|
fun transform(source: Meta): Meta =
|
||||||
|
Meta {
|
||||||
transformations.forEach { rule ->
|
transformations.forEach { rule ->
|
||||||
rule.selectItems(source).forEach { name ->
|
rule.selectItems(source).forEach { name ->
|
||||||
rule.transformItem(name, source[name], this)
|
rule.transformItem(name, source[name], this)
|
||||||
@ -98,7 +101,8 @@ inline class MetaTransformation(val transformations: Collection<TransformationRu
|
|||||||
/**
|
/**
|
||||||
* Transform a meta, replacing all elements found in rules with transformed entries
|
* Transform a meta, replacing all elements found in rules with transformed entries
|
||||||
*/
|
*/
|
||||||
fun apply(source: Meta): Meta = buildMeta(source) {
|
fun apply(source: Meta): Meta =
|
||||||
|
source.edit {
|
||||||
transformations.forEach { rule ->
|
transformations.forEach { rule ->
|
||||||
rule.selectItems(source).forEach { name ->
|
rule.selectItems(source).forEach { name ->
|
||||||
remove(name)
|
remove(name)
|
||||||
@ -150,7 +154,8 @@ class MetaTransformationBuilder {
|
|||||||
* Keep nodes by regex
|
* Keep nodes by regex
|
||||||
*/
|
*/
|
||||||
fun keep(regex: String) {
|
fun keep(regex: String) {
|
||||||
transformations.add(RegexItemTransformationRule(regex.toRegex()) { name, _, metaItem ->
|
transformations.add(
|
||||||
|
RegexItemTransformationRule(regex.toRegex()) { name, _, metaItem ->
|
||||||
setItem(name, metaItem)
|
setItem(name, metaItem)
|
||||||
})
|
})
|
||||||
}
|
}
|
@ -1,11 +1,14 @@
|
|||||||
package hep.dataforge.names
|
package hep.dataforge.names
|
||||||
|
|
||||||
|
import kotlinx.serialization.*
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The general interface for working with names.
|
* The general interface for working with names.
|
||||||
* The name is a dot separated list of strings like `token1.token2.token3`.
|
* The name is a dot separated list of strings like `token1.token2.token3`.
|
||||||
* Each token could contain additional index in square brackets.
|
* Each token could contain additional index in square brackets.
|
||||||
*/
|
*/
|
||||||
|
@Serializable
|
||||||
class Name(val tokens: List<NameToken>) {
|
class Name(val tokens: List<NameToken>) {
|
||||||
|
|
||||||
val length get() = tokens.size
|
val length get() = tokens.size
|
||||||
@ -50,9 +53,21 @@ class Name(val tokens: List<NameToken>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializer(Name::class)
|
||||||
companion object {
|
companion object : KSerializer<Name> {
|
||||||
const val NAME_SEPARATOR = "."
|
const val NAME_SEPARATOR = "."
|
||||||
|
|
||||||
|
val EMPTY = Name(emptyList())
|
||||||
|
|
||||||
|
override val descriptor: SerialDescriptor = PrimitiveDescriptor("hep.dataforge.names.Name", PrimitiveKind.STRING)
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): Name {
|
||||||
|
return decoder.decodeString().toName()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: Name) {
|
||||||
|
encoder.encodeString(value.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,6 +76,7 @@ class Name(val tokens: List<NameToken>) {
|
|||||||
* Following symbols are prohibited in name tokens: `{}.:\`.
|
* Following symbols are prohibited in name tokens: `{}.:\`.
|
||||||
* A name token could have appendix in square brackets called *index*
|
* A name token could have appendix in square brackets called *index*
|
||||||
*/
|
*/
|
||||||
|
@Serializable
|
||||||
data class NameToken(val body: String, val index: String = "") {
|
data class NameToken(val body: String, val index: String = "") {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -80,6 +96,19 @@ data class NameToken(val body: String, val index: String = "") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun hasIndex() = index.isNotEmpty()
|
fun hasIndex() = index.isNotEmpty()
|
||||||
|
|
||||||
|
@Serializer(NameToken::class)
|
||||||
|
companion object : KSerializer<NameToken> {
|
||||||
|
override val descriptor: SerialDescriptor = PrimitiveDescriptor("hep.dataforge.names.NameToken", PrimitiveKind.STRING)
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): NameToken {
|
||||||
|
return decoder.decodeString().toName().first()!!
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: NameToken) {
|
||||||
|
encoder.encodeString(value.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -87,7 +116,7 @@ data class NameToken(val body: String, val index: String = "") {
|
|||||||
* This operation is rather heavy so it should be used with care in high performance code.
|
* This operation is rather heavy so it should be used with care in high performance code.
|
||||||
*/
|
*/
|
||||||
fun String.toName(): Name {
|
fun String.toName(): Name {
|
||||||
if (isBlank()) return EmptyName
|
if (isBlank()) return Name.EMPTY
|
||||||
val tokens = sequence {
|
val tokens = sequence {
|
||||||
var bodyBuilder = StringBuilder()
|
var bodyBuilder = StringBuilder()
|
||||||
var queryBuilder = StringBuilder()
|
var queryBuilder = StringBuilder()
|
||||||
@ -139,7 +168,7 @@ fun String.toName(): Name {
|
|||||||
* Convert the [String] to a [Name] by simply wrapping it in a single name token without parsing.
|
* Convert the [String] to a [Name] by simply wrapping it in a single name token without parsing.
|
||||||
* The input string could contain dots and braces, but they are just escaped, not parsed.
|
* The input string could contain dots and braces, but they are just escaped, not parsed.
|
||||||
*/
|
*/
|
||||||
fun String.asName(): Name = if (isBlank()) EmptyName else NameToken(this).asName()
|
fun String.asName(): Name = if (isBlank()) Name.EMPTY else NameToken(this).asName()
|
||||||
|
|
||||||
operator fun NameToken.plus(other: Name): Name = Name(listOf(this) + other.tokens)
|
operator fun NameToken.plus(other: Name): Name = Name(listOf(this) + other.tokens)
|
||||||
|
|
||||||
@ -153,8 +182,6 @@ fun Name.appendLeft(other: String): Name = NameToken(other) + this
|
|||||||
|
|
||||||
fun NameToken.asName() = Name(listOf(this))
|
fun NameToken.asName() = Name(listOf(this))
|
||||||
|
|
||||||
val EmptyName = Name(emptyList())
|
|
||||||
|
|
||||||
fun Name.isEmpty(): Boolean = this.length == 0
|
fun Name.isEmpty(): Boolean = this.length == 0
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -182,6 +209,8 @@ fun Name.startsWith(token: NameToken): Boolean = first() == token
|
|||||||
|
|
||||||
fun Name.endsWith(token: NameToken): Boolean = last() == token
|
fun Name.endsWith(token: NameToken): Boolean = last() == token
|
||||||
|
|
||||||
fun Name.startsWith(name: Name): Boolean = tokens.subList(0, name.length) == name.tokens
|
fun Name.startsWith(name: Name): Boolean =
|
||||||
|
this.length >= name.length && tokens.subList(0, name.length) == name.tokens
|
||||||
|
|
||||||
fun Name.endsWith(name: Name): Boolean = tokens.subList(length - name.length, length) == name.tokens
|
fun Name.endsWith(name: Name): Boolean =
|
||||||
|
this.length >= name.length && tokens.subList(length - name.length, length) == name.tokens
|
@ -1,5 +1,7 @@
|
|||||||
package hep.dataforge.values
|
package hep.dataforge.values
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of supported Value types.
|
* The list of supported Value types.
|
||||||
@ -7,6 +9,7 @@ package hep.dataforge.values
|
|||||||
* Time value and binary value are represented by string
|
* Time value and binary value are represented by string
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
@Serializable
|
||||||
enum class ValueType {
|
enum class ValueType {
|
||||||
NUMBER, STRING, BOOLEAN, NULL
|
NUMBER, STRING, BOOLEAN, NULL
|
||||||
}
|
}
|
||||||
@ -228,6 +231,8 @@ fun FloatArray.asValue(): Value = if (isEmpty()) Null else ListValue(map { Numbe
|
|||||||
|
|
||||||
fun ByteArray.asValue(): Value = if (isEmpty()) Null else ListValue(map { NumberValue(it) })
|
fun ByteArray.asValue(): Value = if (isEmpty()) Null else ListValue(map { NumberValue(it) })
|
||||||
|
|
||||||
|
fun <E : Enum<E>> E.asValue(): Value = EnumValue(this)
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create Value from String using closest match conversion
|
* Create Value from String using closest match conversion
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
package hep.dataforge.values
|
||||||
|
|
||||||
|
import hep.dataforge.meta.boolean
|
||||||
|
import hep.dataforge.meta.enum
|
||||||
|
import hep.dataforge.meta.string
|
||||||
|
import kotlinx.serialization.*
|
||||||
|
import kotlinx.serialization.builtins.list
|
||||||
|
|
||||||
|
@Serializer(Value::class)
|
||||||
|
object ValueSerializer : KSerializer<Value> {
|
||||||
|
private val listSerializer by lazy { ValueSerializer.list }
|
||||||
|
|
||||||
|
override val descriptor: SerialDescriptor =
|
||||||
|
SerialDescriptor("hep.dataforge.values.Value") {
|
||||||
|
boolean("isList")
|
||||||
|
enum<ValueType>("valueType")
|
||||||
|
string("value")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Decoder.decodeValue(): Value {
|
||||||
|
return when (decode(ValueType.serializer())) {
|
||||||
|
ValueType.NULL -> Null
|
||||||
|
ValueType.NUMBER -> decodeDouble().asValue() //TODO differentiate?
|
||||||
|
ValueType.BOOLEAN -> decodeBoolean().asValue()
|
||||||
|
ValueType.STRING -> decodeString().asValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): Value {
|
||||||
|
val isList = decoder.decodeBoolean()
|
||||||
|
return if (isList) {
|
||||||
|
listSerializer.deserialize(decoder).asValue()
|
||||||
|
} else {
|
||||||
|
decoder.decodeValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Encoder.encodeValue(value: Value) {
|
||||||
|
encode(ValueType.serializer(), value.type)
|
||||||
|
when (value.type) {
|
||||||
|
ValueType.NULL -> {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
ValueType.NUMBER -> encodeDouble(value.double)
|
||||||
|
ValueType.BOOLEAN -> encodeBoolean(value.boolean)
|
||||||
|
ValueType.STRING -> encodeString(value.string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: Value) {
|
||||||
|
encoder.encodeBoolean(value.isList())
|
||||||
|
if (value.isList()) {
|
||||||
|
listSerializer.serialize(encoder, value.list)
|
||||||
|
} else {
|
||||||
|
encoder.encodeValue(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
package hep.dataforge.values
|
package hep.dataforge.values
|
||||||
|
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
import hep.dataforge.meta.buildMeta
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if value is null
|
* Check if value is null
|
||||||
@ -22,6 +21,7 @@ val Value.boolean
|
|||||||
val Value.int get() = number.toInt()
|
val Value.int get() = number.toInt()
|
||||||
val Value.double get() = number.toDouble()
|
val Value.double get() = number.toDouble()
|
||||||
val Value.float get() = number.toFloat()
|
val Value.float get() = number.toFloat()
|
||||||
|
val Value.short get() = number.toShort()
|
||||||
val Value.long get() = number.toLong()
|
val Value.long get() = number.toLong()
|
||||||
|
|
||||||
val Value.stringList: List<String> get() = list.map { it.string }
|
val Value.stringList: List<String> get() = list.map { it.string }
|
||||||
@ -34,4 +34,4 @@ val Value.doubleArray: DoubleArray
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun Value.toMeta() = buildMeta { Meta.VALUE_KEY put this }
|
fun Value.toMeta() = Meta { Meta.VALUE_KEY put this }
|
@ -1,5 +1,6 @@
|
|||||||
package hep.dataforge.meta
|
package hep.dataforge.meta
|
||||||
|
|
||||||
|
import hep.dataforge.meta.scheme.*
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
@ -10,26 +11,29 @@ class MetaDelegateTest {
|
|||||||
NO
|
NO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class InnerSpec : Scheme() {
|
||||||
|
var innerValue by string()
|
||||||
|
|
||||||
|
companion object : SchemeSpec<InnerSpec>(::InnerSpec)
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestScheme : Scheme() {
|
||||||
|
var myValue by string()
|
||||||
|
var safeValue by double(2.2)
|
||||||
|
var enumValue by enum(TestEnum.YES) { enum<TestEnum>() }
|
||||||
|
var inner by spec(InnerSpec)
|
||||||
|
|
||||||
|
companion object : SchemeSpec<TestScheme>(::TestScheme)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun delegateTest() {
|
fun delegateTest() {
|
||||||
|
|
||||||
class InnerSpec(override val config: Config) : Specific {
|
val testObject = TestScheme.empty()
|
||||||
var innerValue by string()
|
|
||||||
}
|
|
||||||
|
|
||||||
val innerSpec = specification(::InnerSpec)
|
|
||||||
|
|
||||||
val testObject = object : Specific {
|
|
||||||
override val config: Config = Config()
|
|
||||||
var myValue by string()
|
|
||||||
var safeValue by double(2.2)
|
|
||||||
var enumValue by enum(TestEnum.YES)
|
|
||||||
var inner by spec(innerSpec)
|
|
||||||
}
|
|
||||||
testObject.config["myValue"] = "theString"
|
testObject.config["myValue"] = "theString"
|
||||||
testObject.enumValue = TestEnum.NO
|
testObject.enumValue = TestEnum.NO
|
||||||
|
|
||||||
testObject.inner = innerSpec.build { innerValue = "ddd"}
|
testObject.inner = InnerSpec { innerValue = "ddd" }
|
||||||
|
|
||||||
assertEquals("theString", testObject.myValue)
|
assertEquals("theString", testObject.myValue)
|
||||||
assertEquals(TestEnum.NO, testObject.enumValue)
|
assertEquals(TestEnum.NO, testObject.enumValue)
|
||||||
|
@ -11,12 +11,12 @@ class MetaExtensionTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testEnum(){
|
fun testEnum(){
|
||||||
val meta = buildMeta{"enum" put TestEnum.test}
|
val meta = Meta{"enum" put TestEnum.test}
|
||||||
meta["enum"].enum<TestEnum>()
|
meta["enum"].enum<TestEnum>()
|
||||||
}
|
}
|
||||||
@Test
|
@Test
|
||||||
fun testEnumByString(){
|
fun testEnumByString(){
|
||||||
val meta = buildMeta{"enum" put TestEnum.test.name}
|
val meta = Meta{"enum" put TestEnum.test.name}
|
||||||
println(meta["enum"].enum<TestEnum>())
|
println(meta["enum"].enum<TestEnum>())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,13 +16,13 @@ class MetaTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun metaEqualityTest() {
|
fun metaEqualityTest() {
|
||||||
val meta1 = buildMeta {
|
val meta1 = Meta {
|
||||||
"a" put 22
|
"a" put 22
|
||||||
"b" put {
|
"b" put {
|
||||||
"c" put "ddd"
|
"c" put "ddd"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val meta2 = buildMeta {
|
val meta2 = Meta {
|
||||||
"b" put {
|
"b" put {
|
||||||
"c" put "ddd"
|
"c" put "ddd"
|
||||||
}
|
}
|
||||||
@ -33,13 +33,13 @@ class MetaTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun metaToMap(){
|
fun metaToMap(){
|
||||||
val meta = buildMeta {
|
val meta = Meta {
|
||||||
"a" put 22
|
"a" put 22
|
||||||
"b" put {
|
"b" put {
|
||||||
"c" put "ddd"
|
"c" put "ddd"
|
||||||
}
|
}
|
||||||
"list" put (0..4).map {
|
"list" put (0..4).map {
|
||||||
buildMeta {
|
Meta {
|
||||||
"value" put it
|
"value" put it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import kotlin.test.assertEquals
|
|||||||
class MutableMetaTest{
|
class MutableMetaTest{
|
||||||
@Test
|
@Test
|
||||||
fun testRemove(){
|
fun testRemove(){
|
||||||
val meta = buildMeta {
|
val meta = Meta {
|
||||||
"aNode" put {
|
"aNode" put {
|
||||||
"innerNode" put {
|
"innerNode" put {
|
||||||
"innerValue" put true
|
"innerValue" put true
|
||||||
@ -14,7 +14,7 @@ class MutableMetaTest{
|
|||||||
"b" put 22
|
"b" put 22
|
||||||
"c" put "StringValue"
|
"c" put "StringValue"
|
||||||
}
|
}
|
||||||
}.toConfig()
|
}.asConfig()
|
||||||
|
|
||||||
meta.remove("aNode.c")
|
meta.remove("aNode.c")
|
||||||
assertEquals(meta["aNode.c"], null)
|
assertEquals(meta["aNode.c"], null)
|
||||||
|
@ -1,22 +1,27 @@
|
|||||||
package hep.dataforge.meta
|
package hep.dataforge.meta
|
||||||
|
|
||||||
|
import hep.dataforge.meta.scheme.asScheme
|
||||||
|
import hep.dataforge.meta.scheme.getProperty
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
|
||||||
class StyledTest{
|
class SchemeTest{
|
||||||
@Test
|
@Test
|
||||||
fun testSNS(){
|
fun testMetaScheme(){
|
||||||
val meta = buildMeta {
|
val styled = Meta {
|
||||||
repeat(10){
|
repeat(10){
|
||||||
"b.a[$it]" put {
|
"b.a[$it]" put {
|
||||||
"d" put it
|
"d" put it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.seal().withStyle()
|
}.asScheme()
|
||||||
|
|
||||||
|
val meta = styled.toMeta()
|
||||||
|
|
||||||
assertEquals(10, meta.values().count())
|
assertEquals(10, meta.values().count())
|
||||||
|
|
||||||
val bNode = meta["b"].node
|
val bNode = styled.getProperty("b").node
|
||||||
|
|
||||||
val aNodes = bNode?.getIndexed("a")
|
val aNodes = bNode?.getIndexed("a")
|
||||||
|
|
@ -1,21 +1,29 @@
|
|||||||
package hep.dataforge.meta
|
package hep.dataforge.meta
|
||||||
|
|
||||||
|
import hep.dataforge.meta.scheme.Scheme
|
||||||
|
import hep.dataforge.meta.scheme.Specification
|
||||||
|
import hep.dataforge.meta.scheme.numberList
|
||||||
|
import hep.dataforge.names.Name
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class SpecificationTest {
|
class SpecificationTest {
|
||||||
class TestSpecific(override val config: Config) : Specific {
|
class TestStyled(config: Config, defaultProvider: (Name) -> MetaItem<*>?) :
|
||||||
|
Scheme(config, defaultProvider) {
|
||||||
var list by numberList(1, 2, 3)
|
var list by numberList(1, 2, 3)
|
||||||
|
|
||||||
companion object : Specification<TestSpecific> {
|
companion object : Specification<TestStyled> {
|
||||||
override fun wrap(config: Config): TestSpecific = TestSpecific(config)
|
override fun wrap(
|
||||||
|
config: Config,
|
||||||
|
defaultProvider: (Name) -> MetaItem<*>?
|
||||||
|
): TestStyled = TestStyled(config, defaultProvider)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testSpecific() {
|
fun testSpecific() {
|
||||||
val testObject = TestSpecific.build {
|
val testObject = TestStyled {
|
||||||
list = emptyList()
|
list = emptyList()
|
||||||
}
|
}
|
||||||
assertEquals(emptyList(), testObject.list)
|
assertEquals(emptyList(), testObject.list)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package hep.dataforge.descriptors
|
package hep.dataforge.meta.descriptors
|
||||||
|
|
||||||
import hep.dataforge.values.ValueType
|
import hep.dataforge.values.ValueType
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
@ -6,15 +6,15 @@ import kotlin.test.assertEquals
|
|||||||
|
|
||||||
class DescriptorTest {
|
class DescriptorTest {
|
||||||
|
|
||||||
val descriptor = NodeDescriptor.build {
|
val descriptor = NodeDescriptor {
|
||||||
node("aNode") {
|
defineNode("aNode") {
|
||||||
info = "A root demo node"
|
info = "A root demo node"
|
||||||
value("b") {
|
defineValue("b") {
|
||||||
info = "b number value"
|
info = "b number value"
|
||||||
type(ValueType.NUMBER)
|
type(ValueType.NUMBER)
|
||||||
}
|
}
|
||||||
node("otherNode") {
|
defineNode("otherNode") {
|
||||||
value("otherValue") {
|
defineValue("otherValue") {
|
||||||
type(ValueType.BOOLEAN)
|
type(ValueType.BOOLEAN)
|
||||||
default(false)
|
default(false)
|
||||||
info = "default value"
|
info = "default value"
|
@ -3,7 +3,8 @@ package hep.dataforge.output.html
|
|||||||
import hep.dataforge.context.Context
|
import hep.dataforge.context.Context
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
import hep.dataforge.output.Output
|
import hep.dataforge.output.Output
|
||||||
import hep.dataforge.output.TextRenderer
|
import hep.dataforge.output.Renderer
|
||||||
|
import hep.dataforge.output.TextFormat
|
||||||
import hep.dataforge.output.html.HtmlBuilder.Companion.HTML_CONVERTER_TYPE
|
import hep.dataforge.output.html.HtmlBuilder.Companion.HTML_CONVERTER_TYPE
|
||||||
import hep.dataforge.provider.Type
|
import hep.dataforge.provider.Type
|
||||||
import hep.dataforge.provider.top
|
import hep.dataforge.provider.top
|
||||||
@ -14,11 +15,11 @@ import kotlinx.html.p
|
|||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
|
||||||
class HtmlOutput<T : Any>(override val context: Context, private val consumer: TagConsumer<*>) : Output<T> {
|
class HtmlRenderer<T : Any>(override val context: Context, private val consumer: TagConsumer<*>) : Renderer<T> {
|
||||||
private val cache = HashMap<KClass<*>, HtmlBuilder<*>>()
|
private val cache = HashMap<KClass<*>, HtmlBuilder<*>>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the first [TextRenderer] matching the given object type.
|
* Find the first [TextFormat] matching the given object type.
|
||||||
*/
|
*/
|
||||||
override fun render(obj: T, meta: Meta) {
|
override fun render(obj: T, meta: Meta) {
|
||||||
|
|
||||||
@ -47,7 +48,7 @@ class HtmlOutput<T : Any>(override val context: Context, private val consumer: T
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A text or binary renderer based on [kotlinx.io.core.Output]
|
* A text or binary renderer based on [Renderer]
|
||||||
*/
|
*/
|
||||||
@Type(HTML_CONVERTER_TYPE)
|
@Type(HTML_CONVERTER_TYPE)
|
||||||
interface HtmlBuilder<T : Any> {
|
interface HtmlBuilder<T : Any> {
|
@ -1,13 +1,9 @@
|
|||||||
package hep.dataforge.output
|
package hep.dataforge.output
|
||||||
|
|
||||||
import hep.dataforge.context.AbstractPlugin
|
import hep.dataforge.context.*
|
||||||
import hep.dataforge.context.Context
|
|
||||||
import hep.dataforge.context.PluginFactory
|
|
||||||
import hep.dataforge.context.PluginTag
|
|
||||||
import hep.dataforge.context.PluginTag.Companion.DATAFORGE_GROUP
|
import hep.dataforge.context.PluginTag.Companion.DATAFORGE_GROUP
|
||||||
import hep.dataforge.meta.EmptyMeta
|
import hep.dataforge.meta.EmptyMeta
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
import hep.dataforge.names.EmptyName
|
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@ -22,14 +18,14 @@ interface OutputManager {
|
|||||||
* Get an output specialized for given type, name and stage.
|
* Get an output specialized for given type, name and stage.
|
||||||
* @param stage represents the node or directory for the output. Empty means root node.
|
* @param stage represents the node or directory for the output. Empty means root node.
|
||||||
* @param name represents the name inside the node.
|
* @param name represents the name inside the node.
|
||||||
* @param meta configuration for [Output] (not for rendered object)
|
* @param meta configuration for [Renderer] (not for rendered object)
|
||||||
*/
|
*/
|
||||||
operator fun <T : Any> get(
|
operator fun <T : Any> get(
|
||||||
type: KClass<out T>,
|
type: KClass<out T>,
|
||||||
name: Name,
|
name: Name,
|
||||||
stage: Name = EmptyName,
|
stage: Name = Name.EMPTY,
|
||||||
meta: Meta = EmptyMeta
|
meta: Meta = EmptyMeta
|
||||||
): Output<T>
|
): Renderer<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -42,28 +38,35 @@ val Context.output: OutputManager get() = plugins.get() ?: ConsoleOutputManager(
|
|||||||
*/
|
*/
|
||||||
inline operator fun <reified T : Any> OutputManager.get(
|
inline operator fun <reified T : Any> OutputManager.get(
|
||||||
name: Name,
|
name: Name,
|
||||||
stage: Name = EmptyName,
|
stage: Name = Name.EMPTY,
|
||||||
meta: Meta = EmptyMeta
|
meta: Meta = EmptyMeta
|
||||||
): Output<T> {
|
): Renderer<T> {
|
||||||
return get(T::class, name, stage, meta)
|
return get(T::class, name, stage, meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Directly render an object using the most suitable renderer
|
* Directly render an object using the most suitable renderer
|
||||||
*/
|
*/
|
||||||
fun OutputManager.render(obj: Any, name: Name, stage: Name = EmptyName, meta: Meta = EmptyMeta) =
|
fun OutputManager.render(obj: Any, name: Name, stage: Name = Name.EMPTY, meta: Meta = EmptyMeta) =
|
||||||
get(obj::class, name, stage).render(obj, meta)
|
get(obj::class, name, stage).render(obj, meta)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* System console output.
|
* System console output.
|
||||||
* The [ConsoleOutput] is used when no other [OutputManager] is provided.
|
* The [CONSOLE_RENDERER] is used when no other [OutputManager] is provided.
|
||||||
*/
|
*/
|
||||||
expect val ConsoleOutput: Output<Any>
|
val CONSOLE_RENDERER: Renderer<Any> = object : Renderer<Any> {
|
||||||
|
override fun render(obj: Any, meta: Meta) {
|
||||||
|
println(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val context: Context get() = Global
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
class ConsoleOutputManager : AbstractPlugin(), OutputManager {
|
class ConsoleOutputManager : AbstractPlugin(), OutputManager {
|
||||||
override val tag: PluginTag get() = ConsoleOutputManager.tag
|
override val tag: PluginTag get() = ConsoleOutputManager.tag
|
||||||
|
|
||||||
override fun <T : Any> get(type: KClass<out T>, name: Name, stage: Name, meta: Meta): Output<T> = ConsoleOutput
|
override fun <T : Any> get(type: KClass<out T>, name: Name, stage: Name, meta: Meta): Renderer<T> = CONSOLE_RENDERER
|
||||||
|
|
||||||
companion object : PluginFactory<ConsoleOutputManager> {
|
companion object : PluginFactory<ConsoleOutputManager> {
|
||||||
override val tag = PluginTag("output.console", group = DATAFORGE_GROUP)
|
override val tag = PluginTag("output.console", group = DATAFORGE_GROUP)
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user