0.5.0 #70

Merged
altavir merged 49 commits from dev into master 2021-08-13 21:42:33 +03:00
129 changed files with 2998 additions and 4041 deletions

View File

@ -1,6 +1,9 @@
name: Gradle build
on: [ push ]
on:
push:
branches: [ dev, master ]
pull_request:
jobs:
build:
@ -8,20 +11,22 @@ jobs:
matrix:
os: [ macOS-latest, windows-latest ]
runs-on: ${{matrix.os}}
timeout-minutes: 40
steps:
- name: Checkout the repo
uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v1
uses: DeLaGuardo/setup-graalvm@4.0
with:
java-version: 11
- name: Add msys to path
if: matrix.os == 'windows-latest'
run: SETX PATH "%PATH%;C:\msys64\mingw64\bin"
graalvm: 21.2.0
java: java11
arch: amd64
- name: Cache gradle
uses: actions/cache@v2
with:
path: ~/.gradle/caches
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }}
restore-keys: |
${{ runner.os }}-gradle-
@ -33,4 +38,4 @@ jobs:
restore-keys: |
${{ runner.os }}-gradle-
- name: Build
run: ./gradlew build --no-daemon --stacktrace
run: ./gradlew build --build-cache --no-daemon --stacktrace

View File

@ -2,32 +2,27 @@ name: Dokka publication
on:
push:
branches:
- master
branches: [ master ]
jobs:
build:
runs-on: ubuntu-20.04
timeout-minutes: 40
steps:
- name: Checkout the repo
uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v1
- uses: actions/checkout@v2
- uses: DeLaGuardo/setup-graalvm@4.0
with:
java-version: 11
- name: Cache gradle
uses: actions/cache@v2
graalvm: 21.2.0
java: java11
arch: amd64
- uses: actions/cache@v2
with:
path: ~/.gradle/caches
key: ubuntu-20.04-gradle-${{ hashFiles('*.gradle.kts') }}
key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }}
restore-keys: |
ubuntu-20.04-gradle-
- name: Build
run: |
./gradlew dokkaHtmlMultiModule --no-daemon --no-parallel --stacktrace
mv build/dokka/htmlMultiModule/-modules.html build/dokka/htmlMultiModule/index.html
- name: Deploy to GitHub Pages
uses: JamesIves/github-pages-deploy-action@4.1.0
${{ runner.os }}-gradle-
- run: ./gradlew dokkaHtmlMultiModule --build-cache --no-daemon --no-parallel --stacktrace
- uses: JamesIves/github-pages-deploy-action@4.1.0
with:
branch: gh-pages
folder: build/dokka/htmlMultiModule

View File

@ -3,8 +3,7 @@ name: Gradle publish
on:
workflow_dispatch:
release:
types:
- created
types: [ created ]
jobs:
publish:
@ -12,22 +11,23 @@ jobs:
name: publish
strategy:
matrix:
os: [macOS-latest, windows-latest]
os: [ macOS-latest, windows-latest ]
runs-on: ${{matrix.os}}
steps:
- name: Checkout the repo
uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v1
uses: DeLaGuardo/setup-graalvm@4.0
with:
java-version: 11
- name: Add msys to path
if: matrix.os == 'windows-latest'
run: SETX PATH "%PATH%;C:\msys64\mingw64\bin"
graalvm: 21.2.0
java: java11
arch: amd64
- name: Cache gradle
uses: actions/cache@v2
with:
path: ~/.gradle/caches
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }}
restore-keys: |
${{ runner.os }}-gradle-
@ -40,20 +40,14 @@ jobs:
${{ runner.os }}-gradle-
- name: Publish Windows Artifacts
if: matrix.os == 'windows-latest'
shell: cmd
run: >
./gradlew release --no-daemon
-Ppublishing.enabled=true
-Ppublishing.github.user=${{ secrets.PUBLISHING_GITHUB_USER }}
-Ppublishing.github.token=${{ secrets.PUBLISHING_GITHUB_TOKEN }}
-Ppublishing.space.user=${{ secrets.PUBLISHING_SPACE_USER }}
-Ppublishing.space.token=${{ secrets.PUBLISHING_SPACE_TOKEN }}
./gradlew release --no-daemon --build-cache -Ppublishing.enabled=true
-Ppublishing.space.user=${{ secrets.SPACE_APP_ID }}
-Ppublishing.space.token=${{ secrets.SPACE_APP_SECRET }}
- name: Publish Mac Artifacts
if: matrix.os == 'macOS-latest'
run: >
./gradlew release --no-daemon
-Ppublishing.enabled=true
-Ppublishing.platform=macosX64
-Ppublishing.github.user=${{ secrets.PUBLISHING_GITHUB_USER }}
-Ppublishing.github.token=${{ secrets.PUBLISHING_GITHUB_TOKEN }}
-Ppublishing.space.user=${{ secrets.PUBLISHING_SPACE_USER }}
-Ppublishing.space.token=${{ secrets.PUBLISHING_SPACE_TOKEN }}
./gradlew release --no-daemon --build-cache -Ppublishing.enabled=true -Ppublishing.platform=macosX64
-Ppublishing.space.user=${{ secrets.SPACE_APP_ID }}
-Ppublishing.space.token=${{ secrets.SPACE_APP_SECRET }}

View File

@ -11,6 +11,31 @@
### Fixed
### Security
## [0.5.0]
### Added
- Experimental `listOfSpec` delegate.
### Changed
- **API breaking** Config is deprecated, use `ObservableMeta` instead.
- **API breaking** Descriptor no has a member property `defaultValue` instead of `defaultItem()` extension. It caches default value state on the first call. It is done because computing default on each call is too expensive.
- Kotlin 1.5.10
- Build tools 0.10.0
- Relaxed type restriction on `MetaConverter`. Now nullables are available.
- **Huge API-breaking refactoring of Meta**. Meta now can have both value and children. There is only one kind of descriptor now.
- **API breaking** `String.toName()` is replaced by `Name.parse()`
- **API breaking** Configurable`config` changed to `meta`
### Removed
- `Config`
- Public PluginManager mutability
- Tables and tables-exposed moved to the separate project `tables.kt`
- BinaryMetaFormat. Use CBOR encoding instead
### Fixed
- Proper json array index treatment.
- Proper json index for single-value array.
### Security
## [0.4.0]
### Added

View File

@ -35,12 +35,6 @@
> **Maturity**: PROTOTYPE
<hr/>
* ### [dataforge-tables](dataforge-tables)
>
>
> **Maturity**: PROTOTYPE
<hr/>
* ### [dataforge-workspace](dataforge-workspace)
>
>

View File

@ -4,7 +4,7 @@ plugins {
allprojects {
group = "space.kscience"
version = "0.4.0"
version = "0.5.0"
}
subprojects {
@ -15,10 +15,6 @@ readme {
readmeTemplate = file("docs/templates/README-TEMPLATE.md")
}
changelog{
version = project.version.toString()
}
ksciencePublish {
github("dataforge-core")
space("https://maven.pkg.jetbrains.space/mipt-npm/p/sci/maven")

View File

@ -164,11 +164,6 @@ public final class space/kscience/dataforge/context/PluginFactory$Companion {
}
public final class space/kscience/dataforge/context/PluginManager : java/lang/Iterable, kotlin/jvm/internal/markers/KMappedMarker, space/kscience/dataforge/context/ContextAware {
public fun <init> (Lspace/kscience/dataforge/context/Context;)V
public final fun fetch (Lspace/kscience/dataforge/context/PluginFactory;Lspace/kscience/dataforge/meta/Meta;Z)Lspace/kscience/dataforge/context/Plugin;
public final fun fetch (Lspace/kscience/dataforge/context/PluginFactory;ZLkotlin/jvm/functions/Function1;)Lspace/kscience/dataforge/context/Plugin;
public static synthetic fun fetch$default (Lspace/kscience/dataforge/context/PluginManager;Lspace/kscience/dataforge/context/PluginFactory;Lspace/kscience/dataforge/meta/Meta;ZILjava/lang/Object;)Lspace/kscience/dataforge/context/Plugin;
public static synthetic fun fetch$default (Lspace/kscience/dataforge/context/PluginManager;Lspace/kscience/dataforge/context/PluginFactory;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lspace/kscience/dataforge/context/Plugin;
public final fun find (ZLkotlin/jvm/functions/Function1;)Lspace/kscience/dataforge/context/Plugin;
public static synthetic fun find$default (Lspace/kscience/dataforge/context/PluginManager;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lspace/kscience/dataforge/context/Plugin;
public final fun get (Lkotlin/reflect/KClass;Lspace/kscience/dataforge/context/PluginTag;Z)Ljava/lang/Object;
@ -178,7 +173,6 @@ public final class space/kscience/dataforge/context/PluginManager : java/lang/It
public fun getContext ()Lspace/kscience/dataforge/context/Context;
public fun iterator ()Ljava/util/Iterator;
public final fun list (Z)Ljava/util/Collection;
public final fun remove (Lspace/kscience/dataforge/context/Plugin;)V
}
public final class space/kscience/dataforge/context/PluginTag : space/kscience/dataforge/meta/MetaRepr {
@ -228,28 +222,6 @@ public final class space/kscience/dataforge/context/SlfLogManager$Companion : sp
public fun invoke (Lspace/kscience/dataforge/meta/Meta;Lspace/kscience/dataforge/context/Context;)Lspace/kscience/dataforge/context/SlfLogManager;
}
public abstract interface annotation class space/kscience/dataforge/descriptors/Attribute : java/lang/annotation/Annotation {
public abstract fun key ()Ljava/lang/String;
public abstract fun value ()Ljava/lang/String;
}
public abstract interface annotation class space/kscience/dataforge/descriptors/Attributes : java/lang/annotation/Annotation {
public abstract fun attrs ()[Lspace/kscience/dataforge/descriptors/Attribute;
}
public abstract interface annotation class space/kscience/dataforge/descriptors/ItemDef : java/lang/annotation/Annotation {
public abstract fun info ()Ljava/lang/String;
public abstract fun multiple ()Z
public abstract fun required ()Z
}
public abstract interface annotation class space/kscience/dataforge/descriptors/ValueDef : java/lang/annotation/Annotation {
public abstract fun allowed ()[Ljava/lang/String;
public abstract fun def ()Ljava/lang/String;
public abstract fun enumeration ()Ljava/lang/Class;
public abstract fun type ()[Lspace/kscience/dataforge/values/ValueType;
}
public final class space/kscience/dataforge/properties/PropertyKt {
}

View File

@ -3,10 +3,7 @@ package space.kscience.dataforge.context
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import space.kscience.dataforge.meta.Laminate
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaRepr
import space.kscience.dataforge.meta.itemSequence
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.misc.Named
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.provider.Provider
@ -26,6 +23,7 @@ import kotlin.jvm.Synchronized
public open class Context internal constructor(
final override val name: Name,
public val parent: Context?,
plugins: Set<Plugin>, // set of unattached plugins
meta: Meta,
) : Named, MetaRepr, Provider, CoroutineScope {
@ -42,20 +40,20 @@ public open class Context internal constructor(
/**
* A [PluginManager] for current context
*/
public val plugins: PluginManager by lazy { PluginManager(this) }
public val plugins: PluginManager by lazy { PluginManager(this, plugins) }
override val defaultTarget: String get() = Plugin.TARGET
public fun content(target: String, inherit: Boolean): Map<Name, Any> {
return if (inherit) {
when (target) {
PROPERTY_TARGET -> properties.itemSequence().toMap()
PROPERTY_TARGET -> properties.nodeSequence().toMap()
Plugin.TARGET -> plugins.list(true).associateBy { it.name }
else -> emptyMap()
}
} else {
when (target) {
PROPERTY_TARGET -> properties.layers.firstOrNull()?.itemSequence()?.toMap() ?: emptyMap()
PROPERTY_TARGET -> properties.layers.firstOrNull()?.nodeSequence()?.toMap() ?: emptyMap()
Plugin.TARGET -> plugins.list(false).associateBy { it.name }
else -> emptyMap()
}
@ -97,8 +95,8 @@ public open class Context internal constructor(
override fun toMeta(): Meta = Meta {
"parent" to parent?.name
"properties" put properties.layers.firstOrNull()
"plugins" put plugins.map { it.toMeta() }
properties.layers.firstOrNull()?.let { set("properties", it) }
"plugins" putIndexed plugins.map { it.toMeta() }
}
public companion object {

View File

@ -1,14 +1,15 @@
package space.kscience.dataforge.context
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaBuilder
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.seal
import space.kscience.dataforge.meta.toMutableMeta
import space.kscience.dataforge.misc.DFBuilder
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.names.toName
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.set
@ -25,12 +26,12 @@ public class ContextBuilder internal constructor(
internal val factories = HashMap<PluginFactory<*>, Meta>()
internal var meta = meta.toMutableMeta()
public fun properties(action: MetaBuilder.() -> Unit) {
public fun properties(action: MutableMeta.() -> Unit) {
meta.action()
}
public fun name(string: String) {
this.name = string.toName()
this.name = Name.parse(string)
}
@OptIn(DFExperimental::class)
@ -38,20 +39,20 @@ public class ContextBuilder internal constructor(
parent.gatherInSequence<PluginFactory<*>>(PluginFactory.TYPE).values
.find { it.tag.matches(tag) } ?: error("Can't resolve plugin factory for $tag")
public fun plugin(tag: PluginTag, metaBuilder: MetaBuilder.() -> Unit = {}) {
public fun plugin(tag: PluginTag, mutableMeta: MutableMeta.() -> Unit = {}) {
val factory = findPluginFactory(tag)
factories[factory] = Meta(metaBuilder)
factories[factory] = Meta(mutableMeta)
}
public fun plugin(factory: PluginFactory<*>, meta: Meta) {
factories[factory] = meta
}
public fun plugin(factory: PluginFactory<*>, metaBuilder: MetaBuilder.() -> Unit = {}) {
factories[factory] = Meta(metaBuilder)
public fun plugin(factory: PluginFactory<*>, mutableMeta: MutableMeta.() -> Unit = {}) {
factories[factory] = Meta(mutableMeta)
}
public fun plugin(name: String, group: String = "", version: String = "", action: MetaBuilder.() -> Unit = {}) {
public fun plugin(name: String, group: String = "", version: String = "", action: MutableMeta.() -> Unit = {}) {
plugin(PluginTag(name, group, version), action)
}
@ -63,13 +64,33 @@ public class ContextBuilder internal constructor(
}
public fun build(): Context {
val contextName = name ?: "@auto[${hashCode().toUInt().toString(16)}]".toName()
return Context(contextName, parent, meta.seal()).apply {
factories.forEach { (factory, meta) ->
@Suppress("DEPRECATION")
plugins.load(factory, meta)
val contextName = name ?: NameToken("@auto",hashCode().toUInt().toString(16)).asName()
val plugins = HashMap<PluginTag, Plugin>()
fun addPlugin(factory: PluginFactory<*>, meta: Meta) {
val existing = plugins[factory.tag]
// Add if does not exist
if (existing == null) {
//TODO bypass if parent already has plugin with given meta?
val plugin = factory(meta, parent)
for ((depFactory, deoMeta) in plugin.dependsOn()) {
addPlugin(depFactory, deoMeta)
}
parent.logger.info { "Loading plugin ${plugin.name} into $contextName" }
plugins[plugin.tag] = plugin
} else if (existing.meta != meta) {
error("Plugin with tag ${factory.tag} and meta $meta already exists in $contextName")
}
//bypass if exists with the same meta
}
factories.forEach { (factory, meta) ->
addPlugin(factory, meta)
}
return Context(contextName, parent, plugins.values.toSet(), meta.seal())
}
}

View File

@ -1,6 +1,6 @@
package space.kscience.dataforge.context
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.Job
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.names.asName
@ -13,8 +13,8 @@ internal expect val globalLoggerFactory: PluginFactory<out LogManager>
* A global root context. Closing [Global] terminates the framework.
*/
@ThreadLocal
private object GlobalContext : Context("GLOBAL".asName(), null, Meta.EMPTY) {
override val coroutineContext: CoroutineContext = GlobalScope.coroutineContext + Job()
private object GlobalContext : Context("GLOBAL".asName(), null, emptySet(), Meta.EMPTY) {
override val coroutineContext: CoroutineContext = Job() + CoroutineName("GlobalContext")
}
public val Global: Context get() = GlobalContext

View File

@ -6,7 +6,6 @@ import space.kscience.dataforge.meta.MetaRepr
import space.kscience.dataforge.misc.Named
import space.kscience.dataforge.misc.Type
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.toName
import space.kscience.dataforge.provider.Provider
/**
@ -31,7 +30,7 @@ public interface Plugin : Named, ContextAware, Provider, MetaRepr {
/**
* The name of this plugin ignoring version and group
*/
override val name: Name get() = tag.name.toName()
override val name: Name get() = Name.parse(tag.name)
/**
* Plugin dependencies which are required to attach this plugin. Plugin

View File

@ -1,7 +1,6 @@
package space.kscience.dataforge.context
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaBuilder
import kotlin.reflect.KClass
@ -11,12 +10,10 @@ import kotlin.reflect.KClass
* @property context A context for this plugin manager
* @author Alexander Nozik
*/
public class PluginManager(override val context: Context) : ContextAware, Iterable<Plugin> {
/**
* A set of loaded plugins
*/
private val plugins: HashSet<Plugin> = HashSet()
public class PluginManager internal constructor(
override val context: Context,
private val plugins: Set<Plugin>
) : ContextAware, Iterable<Plugin> {
init {
plugins.forEach { it.attach(context) }
@ -76,70 +73,7 @@ public class PluginManager(override val context: Context) : ContextAware, Iterab
public inline operator fun <reified T : Plugin> get(factory: PluginFactory<T>, recursive: Boolean = true): T? =
get(factory.type, factory.tag, recursive)
/**
* Load given plugin into this manager and return loaded instance.
* Throw error if plugin of the same type and tag already exists in manager.
*
* @param plugin
* @return
*/
@Deprecated("Use immutable contexts instead")
private fun <T : Plugin> load(plugin: T): T {
if (get(plugin::class, plugin.tag, recursive = false) != null) {
error("Plugin with tag ${plugin.tag} already exists in ${context.name}")
} else {
for ((factory, meta) in plugin.dependsOn()) {
fetch(factory, meta, true)
}
logger.info { "Loading plugin ${plugin.name} into ${context.name}" }
plugin.attach(context)
plugins.add(plugin)
return plugin
}
}
/**
* Load a plugin using its factory
*/
@Deprecated("Use immutable contexts instead")
internal fun <T : Plugin> load(factory: PluginFactory<T>, meta: Meta = Meta.EMPTY): T =
load(factory(meta, context))
/**
* Remove a plugin from [PluginManager]
*/
@Deprecated("Use immutable contexts instead")
public fun remove(plugin: Plugin) {
if (plugins.contains(plugin)) {
Global.logger.info { "Removing plugin ${plugin.name} from ${context.name}" }
plugin.detach()
plugins.remove(plugin)
}
}
/**
* Get an existing plugin with given meta or load new one using provided factory
*/
@Deprecated("Use immutable contexts instead")
public fun <T : Plugin> fetch(factory: PluginFactory<T>, meta: Meta = Meta.EMPTY, recursive: Boolean = true): T {
val loaded = get(factory.type, factory.tag, recursive)
return when {
loaded == null -> load(factory(meta, context))
loaded.meta == meta -> loaded // if meta is the same, return existing plugin
else -> throw RuntimeException("Can't load plugin with tag ${factory.tag}. Plugin with this tag and different configuration already exists in context.")
}
}
@Deprecated("Use immutable contexts instead")
public fun <T : Plugin> fetch(
factory: PluginFactory<T>,
recursive: Boolean = true,
metaBuilder: MetaBuilder.() -> Unit,
): T = fetch(factory, Meta(metaBuilder), recursive)
override fun iterator(): Iterator<Plugin> = plugins.iterator()
}
/**

View File

@ -1,35 +1,34 @@
package space.kscience.dataforge.properties
import space.kscience.dataforge.meta.Config
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.set
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.transformations.MetaConverter
import space.kscience.dataforge.meta.transformations.nullableItemToObject
import space.kscience.dataforge.meta.transformations.nullableObjectToMetaItem
import space.kscience.dataforge.meta.transformations.nullableMetaToObject
import space.kscience.dataforge.meta.transformations.nullableObjectToMeta
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.startsWith
@DFExperimental
public class ConfigProperty<T : Any>(
public val config: Config,
public class MetaProperty<T : Any>(
public val meta: ObservableMutableMeta,
public val name: Name,
public val converter: MetaConverter<T>,
) : Property<T?> {
override var value: T?
get() = converter.nullableItemToObject(config[name])
get() = converter.nullableMetaToObject(meta[name])
set(value) {
config[name] = converter.nullableObjectToMetaItem(value)
meta[name] = converter.nullableObjectToMeta(value) ?: Meta.EMPTY
}
override fun onChange(owner: Any?, callback: (T?) -> Unit) {
config.onChange(owner) { name, oldItem, newItem ->
if (name.startsWith(this.name) && oldItem != newItem) callback(converter.nullableItemToObject(newItem))
meta.onChange(owner) { name ->
if (name.startsWith(this@MetaProperty.name)) callback(converter.nullableMetaToObject(get(name)))
}
}
override fun removeChangeListener(owner: Any?) {
config.removeListener(owner)
meta.removeListener(owner)
}
}

View File

@ -1,13 +1,14 @@
package space.kscience.dataforge.properties
import space.kscience.dataforge.meta.ItemPropertyProvider
import space.kscience.dataforge.meta.Scheme
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.startsWith
import space.kscience.dataforge.names.toName
import kotlin.reflect.KMutableProperty1
@DFExperimental
public fun <P : ItemPropertyProvider, T : Any> P.property(property: KMutableProperty1<P, T?>): Property<T?> =
public fun <S : Scheme, T : Any> S.property(property: KMutableProperty1<S, T?>): Property<T?> =
object : Property<T?> {
override var value: T?
get() = property.get(this@property)
@ -16,15 +17,15 @@ public fun <P : ItemPropertyProvider, T : Any> P.property(property: KMutableProp
}
override fun onChange(owner: Any?, callback: (T?) -> Unit) {
this@property.onChange(this) { name, oldItem, newItem ->
if (name.startsWith(property.name.toName()) && oldItem != newItem) {
this@property.meta.onChange(this) { name ->
if (name.startsWith(Name.parse(property.name))) {
callback(property.get(this@property))
}
}
}
override fun removeChangeListener(owner: Any?) {
this@property.removeListener(this@property)
this@property.meta.removeListener(this@property)
}
}

View File

@ -16,7 +16,6 @@
package space.kscience.dataforge.provider
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.toName
import kotlin.jvm.JvmInline
/**
@ -33,11 +32,7 @@ public value class Path(public val tokens: List<PathToken>) : Iterable<PathToken
public companion object {
public const val PATH_SEGMENT_SEPARATOR: String = "/"
public fun parse(path: String): Path {
val head = path.substringBefore(PATH_SEGMENT_SEPARATOR)
val tail = path.substringAfter(PATH_SEGMENT_SEPARATOR)
return PathToken.parse(head).asPath() + parse(tail)
}
public fun parse(path: String): Path = Path(path.split(PATH_SEGMENT_SEPARATOR).map { PathToken.parse(it) })
}
}
@ -67,7 +62,7 @@ public data class PathToken(val name: Name, val target: String? = null) {
public const val TARGET_SEPARATOR: String = "::"
public fun parse(token: String): PathToken {
val target = token.substringBefore(TARGET_SEPARATOR, "")
val name = token.substringAfter(TARGET_SEPARATOR).toName()
val name = Name.parse(token.substringAfter(TARGET_SEPARATOR))
if (target.contains("[")) TODO("target separators in queries are not supported")
return PathToken(name, target)
}

View File

@ -2,7 +2,6 @@ package space.kscience.dataforge.context
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.appendLeft
import space.kscience.dataforge.names.toName
import kotlin.test.Test
import kotlin.test.assertEquals
@ -12,17 +11,16 @@ class ContextTest {
override val tag get() = PluginTag("test")
override fun content(target: String): Map<Name, Any> {
return when(target){
"test" -> listOf("a", "b", "c.d").associate { it.toName() to it.toName() }
return when (target) {
"test" -> listOf("a", "b", "c.d").associate { Name.parse(it) to Name.parse(it) }
else -> emptyMap()
}
}
}
@Test
fun testPluginManager() {
val context = Global.buildContext{
val context = Global.buildContext {
name("test")
plugin(DummyPlugin())
}

View File

@ -14,7 +14,7 @@ internal class TestScheme : Scheme() {
}
@DFExperimental
class ItemPropertiesTest {
class MetaPropertiesTest {
@Test
fun testBinding() {
val scheme = TestScheme.empty()

View File

@ -0,0 +1,14 @@
package space.kscience.dataforge.provider
import kotlin.test.Test
import kotlin.test.assertEquals
class PathTest {
@Test
fun testParse(){
val nameString = "a.b.c.d"
val pathString = "a.b/c.d"
assertEquals(1, Path.parse(nameString).length)
assertEquals(2, Path.parse(pathString).length)
}
}

View File

@ -16,35 +16,32 @@
package space.kscience.dataforge.descriptors
import space.kscience.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
)
//@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

View File

@ -6,7 +6,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import space.kscience.dataforge.data.*
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaBuilder
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.seal
import space.kscience.dataforge.meta.toMutableMeta
import space.kscience.dataforge.misc.DFBuilder
@ -29,7 +29,7 @@ public data class ActionEnv(
* Action environment
*/
@DFBuilder
public class MapActionBuilder<T, R>(public var name: Name, public var meta: MetaBuilder, public val actionMeta: Meta) {
public class MapActionBuilder<T, R>(public var name: Name, public var meta: MutableMeta, public val actionMeta: Meta) {
public lateinit var result: suspend ActionEnv.(T) -> R
/**
@ -68,7 +68,8 @@ internal class MapAction<in T : Any, out R : Any>(
//getting new meta
val newMeta = builder.meta.seal()
@OptIn(DFInternal::class) val newData = Data(outputType, newMeta, dependencies = listOf(data)) {
@OptIn(DFInternal::class)
val newData = Data(outputType, newMeta, dependencies = listOf(data)) {
builder.result(env, data.await())
}
//setting the data node

View File

@ -6,19 +6,18 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.fold
import space.kscience.dataforge.data.*
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaBuilder
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.misc.DFBuilder
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.misc.DFInternal
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.toName
import kotlin.reflect.KType
import kotlin.reflect.typeOf
public class JoinGroup<T : Any, R : Any>(public var name: String, internal val set: DataSet<T>) {
public var meta: MetaBuilder = MetaBuilder()
public var meta: MutableMeta = MutableMeta()
public lateinit var result: suspend ActionEnv.(Map<Name, T>) -> R
@ -95,7 +94,7 @@ internal class ReduceAction<T : Any, R : Any>(
val groupMeta = group.meta
val env = ActionEnv(groupName.toName(), groupMeta, meta)
val env = ActionEnv(Name.parse(groupName), groupMeta, meta)
@OptIn(DFInternal::class) val res: Data<R> = dataFlow.reduceToData(
outputType,
meta = groupMeta

View File

@ -7,12 +7,11 @@ import kotlinx.coroutines.launch
import space.kscience.dataforge.data.*
import space.kscience.dataforge.meta.Laminate
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaBuilder
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.toMutableMeta
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.misc.DFInternal
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.toName
import kotlin.collections.set
import kotlin.reflect.KType
import kotlin.reflect.typeOf
@ -20,7 +19,7 @@ import kotlin.reflect.typeOf
public class SplitBuilder<T : Any, R : Any>(public val name: Name, public val meta: Meta) {
public class FragmentRule<T : Any, R : Any>(public val name: Name, public var meta: MetaBuilder) {
public class FragmentRule<T : Any, R : Any>(public val name: Name, public var meta: MutableMeta) {
public lateinit var result: suspend (T) -> R
public fun result(f: suspend (T) -> R) {
@ -36,7 +35,7 @@ public class SplitBuilder<T : Any, R : Any>(public val name: Name, public val me
* @param rule the rule to transform fragment name and meta using
*/
public fun fragment(name: String, rule: FragmentRule<T, R>.() -> Unit) {
fragments[name.toName()] = rule
fragments[Name.parse(name)] = rule
}
}

View File

@ -115,4 +115,4 @@ public suspend inline fun <reified T : Any> ActiveDataTree<T>.emit(
public suspend inline fun <reified T : Any> ActiveDataTree<T>.emit(
name: String,
noinline block: suspend ActiveDataTree<T>.() -> Unit,
): Unit = emit(name.toName(), ActiveDataTree(typeOf<T>(), block))
): Unit = emit(Name.parse(name), ActiveDataTree(typeOf<T>(), block))

View File

@ -49,6 +49,7 @@ public interface Data<out T : Any> : Goal<T>, MetaRepr {
/**
* An empty data containing only meta
*/
@OptIn(DelicateCoroutinesApi::class)
public fun empty(meta: Meta): Data<Nothing> = object : Data<Nothing> {
override val type: KType = TYPE_OF_NOTHING
override val meta: Meta = meta

View File

@ -4,11 +4,10 @@ import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaBuilder
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.names.toName
import kotlin.reflect.KType
public interface DataSetBuilder<in T : Any> {
@ -39,17 +38,17 @@ public interface DataSetBuilder<in T : Any> {
/**
* Append data to node
*/
public suspend infix fun String.put(data: Data<T>): Unit = emit(toName(), data)
public suspend infix fun String.put(data: Data<T>): Unit = emit(Name.parse(this), data)
/**
* Append node
*/
public suspend infix fun String.put(dataSet: DataSet<T>): Unit = emit(toName(), dataSet)
public suspend infix fun String.put(dataSet: DataSet<T>): Unit = emit(Name.parse(this), dataSet)
/**
* Build and append node
*/
public suspend infix fun String.put(block: suspend DataSetBuilder<T>.() -> Unit): Unit = emit(toName(), block)
public suspend infix fun String.put(block: suspend DataSetBuilder<T>.() -> Unit): Unit = emit(Name.parse(this), block)
}
private class SubSetBuilder<in T : Any>(
@ -77,15 +76,15 @@ public suspend fun <T : Any> DataSetBuilder<T>.emit(name: Name, block: suspend D
public suspend fun <T : Any> DataSetBuilder<T>.emit(name: String, data: Data<T>) {
emit(name.toName(), data)
emit(Name.parse(name), data)
}
public suspend fun <T : Any> DataSetBuilder<T>.emit(name: String, set: DataSet<T>) {
this.emit(name.toName(), set)
this.emit(Name.parse(name), set)
}
public suspend fun <T : Any> DataSetBuilder<T>.emit(name: String, block: suspend DataSetBuilder<T>.() -> Unit): Unit =
this@emit.emit(name.toName(), block)
this@emit.emit(Name.parse(name), block)
public suspend fun <T : Any> DataSetBuilder<T>.emit(data: NamedData<T>) {
emit(data.name, data.data)
@ -115,17 +114,25 @@ public suspend inline fun <reified T : Any> DataSetBuilder<T>.produce(
/**
* Emit a static data with the fixed value
*/
public suspend inline fun <reified T : Any> DataSetBuilder<T>.static(name: String, data: T, meta: Meta = Meta.EMPTY): Unit =
public suspend inline fun <reified T : Any> DataSetBuilder<T>.static(
name: String,
data: T,
meta: Meta = Meta.EMPTY
): Unit =
emit(name, Data.static(data, meta))
public suspend inline fun <reified T : Any> DataSetBuilder<T>.static(name: Name, data: T, meta: Meta = Meta.EMPTY): Unit =
public suspend inline fun <reified T : Any> DataSetBuilder<T>.static(
name: Name,
data: T,
meta: Meta = Meta.EMPTY
): Unit =
emit(name, Data.static(data, meta))
public suspend inline fun <reified T : Any> DataSetBuilder<T>.static(
name: String,
data: T,
metaBuilder: MetaBuilder.() -> Unit,
): Unit = emit(name.toName(), Data.static(data, Meta(metaBuilder)))
mutableMeta: MutableMeta.() -> Unit,
): Unit = emit(Name.parse(name), Data.static(data, Meta(mutableMeta)))
/**
* Update data with given node data and meta with node meta.

View File

@ -57,7 +57,7 @@ public interface DataTree<out T : Any> : DataSet<T> {
}
}
public suspend fun <T: Any> DataSet<T>.getData(name: String): Data<T>? = getData(name.toName())
public suspend fun <T: Any> DataSet<T>.getData(name: String): Data<T>? = getData(Name.parse(name))
/**
* Get a [DataTreeItem] with given [name] or null if the item does not exist

View File

@ -52,7 +52,9 @@ public interface GroupRule {
scope.launch {
set.updates.collect { name ->
val data = set.getData(name)
val tagValue = data?.meta[key].string ?: defaultTagValue
@Suppress("NULLABLE_EXTENSION_OPERATOR_WITH_SAFE_CALL_RECEIVER")
val tagValue = data?.meta?.get(key)?.string ?: defaultTagValue
map.getOrPut(tagValue) { ActiveDataTree(set.dataType) }.emit(name, data)
}
}

View File

@ -5,7 +5,10 @@ import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.*
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.isEmpty
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.names.removeHeadOrNull
import kotlin.reflect.KType
@ -64,7 +67,7 @@ public fun <T : Any> DataSet<T>.branch(branchName: Name): DataSet<T> = if (branc
override val updates: Flow<Name> get() = this@branch.updates.mapNotNull { it.removeHeadOrNull(branchName) }
}
public fun <T : Any> DataSet<T>.branch(branchName: String): DataSet<T> = this@branch.branch(branchName.toName())
public fun <T : Any> DataSet<T>.branch(branchName: String): DataSet<T> = this@branch.branch(Name.parse(branchName))
@DFExperimental
public suspend fun <T : Any> DataSet<T>.rootData(): Data<T>? = getData(Name.EMPTY)

View File

@ -1,7 +1,7 @@
package space.kscience.dataforge.data
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaBuilder
import space.kscience.dataforge.meta.MutableMeta
/**
@ -17,4 +17,4 @@ public suspend fun DataSetBuilder<*>.meta(meta: Meta): Unit = emit(DataSet.META_
/**
* Add meta-data node to a [DataSet]
*/
public suspend fun DataSetBuilder<*>.meta(metaBuilder: MetaBuilder.() -> Unit): Unit = meta(Meta(metaBuilder))
public suspend fun DataSetBuilder<*>.meta(mutableMeta: MutableMeta.() -> Unit): Unit = meta(Meta(mutableMeta))

View File

@ -2,7 +2,7 @@ package space.kscience.dataforge.data
import kotlinx.coroutines.flow.*
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaBuilder
import space.kscience.dataforge.meta.MutableMeta
import space.kscience.dataforge.meta.seal
import space.kscience.dataforge.meta.toMutableMeta
import space.kscience.dataforge.misc.DFInternal
@ -140,7 +140,7 @@ public suspend inline fun <T : Any, reified R : Any> Flow<NamedData<T>>.foldToDa
public suspend fun <T : Any, R : Any> DataSet<T>.map(
outputType: KType,
coroutineContext: CoroutineContext = EmptyCoroutineContext,
metaTransform: MetaBuilder.() -> Unit = {},
metaTransform: MutableMeta.() -> Unit = {},
block: suspend (T) -> R,
): DataTree<R> = DataTree<R>(outputType) {
populate(
@ -156,7 +156,7 @@ public suspend fun <T : Any, R : Any> DataSet<T>.map(
@OptIn(DFInternal::class)
public suspend inline fun <T : Any, reified R : Any> DataSet<T>.map(
coroutineContext: CoroutineContext = EmptyCoroutineContext,
noinline metaTransform: MetaBuilder.() -> Unit = {},
noinline metaTransform: MutableMeta.() -> Unit = {},
noinline block: suspend (T) -> R,
): DataTree<R> = map(typeOf<R>(), coroutineContext, metaTransform, block)

View File

@ -6,7 +6,6 @@ import kotlinx.coroutines.flow.map
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.matches
import space.kscience.dataforge.names.toName
import kotlin.reflect.KType
import kotlin.reflect.full.isSubtypeOf
import kotlin.reflect.typeOf
@ -61,4 +60,4 @@ public suspend fun <R : Any> DataSet<*>.selectOne(type: KType, name: Name): Name
public suspend inline fun <reified R : Any> DataSet<*>.selectOne(name: Name): NamedData<R>? = selectOne(typeOf<R>(), name)
public suspend inline fun <reified R : Any> DataSet<*>.selectOne(name: String): NamedData<R>? =
selectOne(typeOf<R>(), name.toName())
selectOne(typeOf<R>(), Name.parse(name))

View File

@ -3,7 +3,7 @@ package space.kscience.dataforge.data
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collect
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.toName
import space.kscience.dataforge.names.asName
import kotlin.test.Test
import kotlin.test.assertEquals
@ -72,7 +72,7 @@ internal class DataTreeBuilderTest {
}
}
val rootNode = ActiveDataTree<Int> {
setAndObserve("sub".toName(), subNode)
setAndObserve("sub".asName(), subNode)
}
launch {

View File

@ -10,43 +10,49 @@ import space.kscience.dataforge.io.MetaFormat
import space.kscience.dataforge.io.MetaFormatFactory
import space.kscience.dataforge.io.readUtf8String
import space.kscience.dataforge.io.writeUtf8String
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.ItemDescriptor
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.get
import space.kscience.dataforge.meta.isLeaf
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.withIndex
import space.kscience.dataforge.values.ListValue
import space.kscience.dataforge.values.Null
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.parseValue
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.set
public fun Meta.toYaml(): YamlMap {
val map: Map<String, Any?> = items.entries.associate { (key, item) ->
key.toString() to when (item) {
is MetaItemValue -> {
item.value.value
}
is MetaItemNode -> {
item.node.toYaml()
}
key.toString() to if (item.isLeaf) {
item.value?.value
} else {
item.toYaml()
}
}
return YamlMap(map)
}
private class YamlMeta(private val yamlMap: YamlMap, private val descriptor: NodeDescriptor? = null) : MetaBase() {
private class YamlMeta(private val yamlMap: YamlMap, private val descriptor: MetaDescriptor? = null) : Meta {
private fun buildItems(): Map<NameToken, MetaItem> {
val map = LinkedHashMap<NameToken, MetaItem>()
override val value: Value?
get() = yamlMap.getStringOrNull(null)?.parseValue()
private fun buildItems(): Map<NameToken, Meta> {
val map = LinkedHashMap<NameToken, Meta>()
yamlMap.content.entries.forEach { (key, value) ->
val stringKey = key.toString()
val itemDescriptor = descriptor?.items?.get(stringKey)
val itemDescriptor = descriptor?.get(stringKey)
val token = NameToken(stringKey)
when (value) {
YamlNull -> Null.asMetaItem()
is YamlLiteral -> map[token] = value.content.parseValue().asMetaItem()
is YamlMap -> map[token] = value.toMeta().asMetaItem()
YamlNull -> Meta(Null)
is YamlLiteral -> map[token] = Meta(value.content.parseValue())
is YamlMap -> map[token] = value.toMeta()
is YamlList -> if (value.all { it is YamlLiteral }) {
val listValue = ListValue(
value.map {
@ -54,29 +60,33 @@ private class YamlMeta(private val yamlMap: YamlMap, private val descriptor: Nod
(it as YamlLiteral).content.parseValue()
}
)
map[token] = MetaItemValue(listValue)
map[token] = Meta(listValue)
} else value.forEachIndexed { index, yamlElement ->
val indexKey = (itemDescriptor as? NodeDescriptor)?.indexKey ?: ItemDescriptor.DEFAULT_INDEX_KEY
val indexKey = itemDescriptor?.indexKey
val indexValue: String = (yamlElement as? YamlMap)?.getStringOrNull(indexKey)
?: index.toString() //In case index is non-string, the backward transformation will be broken.
val tokenWithIndex = token.withIndex(indexValue)
map[tokenWithIndex] = yamlElement.toMetaItem(itemDescriptor)
map[tokenWithIndex] = yamlElement.toMeta(itemDescriptor)
}
}
}
return map
}
override val items: Map<NameToken, MetaItem> get() = buildItems()
override val items: Map<NameToken, Meta> get() = buildItems()
override fun toString(): String = Meta.toString(this)
override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta)
override fun hashCode(): Int = Meta.hashCode(this)
}
public fun YamlElement.toMetaItem(descriptor: ItemDescriptor? = null): MetaItem = when (this) {
YamlNull -> Null.asMetaItem()
is YamlLiteral -> content.parseValue().asMetaItem()
is YamlMap -> toMeta().asMetaItem()
public fun YamlElement.toMeta(descriptor: MetaDescriptor? = null): Meta = when (this) {
YamlNull -> Meta(Null)
is YamlLiteral -> Meta(content.parseValue())
is YamlMap -> toMeta()
//We can't return multiple items therefore we create top level node
is YamlList -> YamlMap(mapOf("@yamlArray" to this)).toMetaItem(descriptor)
is YamlList -> YamlMap(mapOf("@yamlArray" to this)).toMeta(descriptor)
}
public fun YamlMap.toMeta(): Meta = YamlMeta(this)
@ -88,13 +98,13 @@ public fun YamlMap.toMeta(): Meta = YamlMeta(this)
@DFExperimental
public class YamlMetaFormat(private val meta: Meta) : MetaFormat {
override fun writeMeta(output: Output, meta: Meta, descriptor: NodeDescriptor?) {
override fun writeMeta(output: Output, meta: Meta, descriptor: MetaDescriptor?) {
val yaml = meta.toYaml()
val string = Yaml.encodeToString(yaml)
output.writeUtf8String(string)
}
override fun readMeta(input: Input, descriptor: NodeDescriptor?): Meta {
override fun readMeta(input: Input, descriptor: MetaDescriptor?): Meta {
val yaml = Yaml.decodeYamlMapFromString(input.readUtf8String())
return yaml.toMeta()
}
@ -113,10 +123,10 @@ public class YamlMetaFormat(private val meta: Meta) : MetaFormat {
private val default = YamlMetaFormat()
override fun writeMeta(output: Output, meta: Meta, descriptor: NodeDescriptor?): Unit =
override fun writeMeta(output: Output, meta: Meta, descriptor: MetaDescriptor?): Unit =
default.writeMeta(output, meta, descriptor)
override fun readMeta(input: Input, descriptor: NodeDescriptor?): Meta =
override fun readMeta(input: Input, descriptor: MetaDescriptor?): Meta =
default.readMeta(input, descriptor)
}
}

View File

@ -1,133 +0,0 @@
package space.kscience.dataforge.io
import io.ktor.utils.io.core.*
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.values.*
/**
* A DataForge-specific simplified binary format for meta
* TODO add description
*/
public object BinaryMetaFormat : MetaFormat, MetaFormatFactory {
override val shortName: String = "bin"
override val key: Short = 0x4249//BI
override fun invoke(meta: Meta, context: Context): MetaFormat = this
override fun readMeta(input: Input, descriptor: NodeDescriptor?): Meta {
return (input.readMetaItem() as MetaItemNode).node
}
private fun Output.writeChar(char: Char) = writeByte(char.code.toByte())
private fun Output.writeString(str: String) {
writeInt(str.length)
writeFully(str.encodeToByteArray())
}
public fun Output.writeValue(value: Value): Unit = when (value.type) {
ValueType.NUMBER -> when (value.value) {
is Short -> {
writeChar('s')
writeShort(value.short)
}
is Int -> {
writeChar('i')
writeInt(value.int)
}
is Long -> {
writeChar('l')
writeLong(value.long)
}
is Float -> {
writeChar('f')
writeFloat(value.float)
}
else -> {
writeChar('d')
writeDouble(value.double)
}
}
ValueType.STRING -> {
writeChar('S')
writeString(value.string)
}
ValueType.BOOLEAN -> {
if (value.boolean) {
writeChar('+')
} else {
writeChar('-')
}
}
ValueType.NULL -> {
writeChar('N')
}
ValueType.LIST -> {
writeChar('L')
writeInt(value.list.size)
value.list.forEach {
writeValue(it)
}
}
}
override fun writeMeta(
output: Output,
meta: Meta,
descriptor: space.kscience.dataforge.meta.descriptors.NodeDescriptor?,
) {
output.writeChar('M')
output.writeInt(meta.items.size)
meta.items.forEach { (key, item) ->
output.writeString(key.toString())
when (item) {
is MetaItemValue -> {
output.writeValue(item.value)
}
is MetaItemNode -> {
writeObject(output, item.node)
}
}
}
}
private fun Input.readString(): String {
val length = readInt()
val array = readBytes(length)
return array.decodeToString()
}
@Suppress("UNCHECKED_CAST")
public fun Input.readMetaItem(): TypedMetaItem<MetaBuilder> {
return when (val keyChar = readByte().toInt().toChar()) {
'S' -> MetaItemValue(StringValue(readString()))
'N' -> MetaItemValue(Null)
'+' -> MetaItemValue(True)
'-' -> MetaItemValue(True)
's' -> MetaItemValue(NumberValue(readShort()))
'i' -> MetaItemValue(NumberValue(readInt()))
'l' -> MetaItemValue(NumberValue(readInt()))
'f' -> MetaItemValue(NumberValue(readFloat()))
'd' -> MetaItemValue(NumberValue(readDouble()))
'L' -> {
val length = readInt()
val list = (1..length).map { (readMetaItem() as MetaItemValue).value }
MetaItemValue(Value.of(list))
}
'M' -> {
val length = readInt()
val meta = Meta {
(1..length).forEach { _ ->
val name = readString()
val item = readMetaItem()
set(name, item)
}
}
MetaItemNode(meta)
}
else -> error("Unknown serialization key character: $keyChar")
}
}
}

View File

@ -4,7 +4,7 @@ import io.ktor.utils.io.core.Output
import space.kscience.dataforge.meta.*
public class EnvelopeBuilder : Envelope {
private val metaBuilder = MetaBuilder()
private val metaBuilder = MutableMeta()
override var data: Binary? = null
override var meta: Meta
@ -13,7 +13,7 @@ public class EnvelopeBuilder : Envelope {
metaBuilder.update(value)
}
public fun meta(block: MetaBuilder.() -> Unit) {
public fun meta(block: MutableMeta.() -> Unit) {
metaBuilder.apply(block)
}

View File

@ -10,12 +10,11 @@ import space.kscience.dataforge.io.PartDescriptor.Companion.SEPARATOR_KEY
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.names.toName
private class PartDescriptor : Scheme() {
var offset by int(0)
var size by int(0)
var partMeta by node("meta".toName())
var partMeta by node("meta".asName())
companion object : SchemeSpec<PartDescriptor>(::PartDescriptor) {
val MULTIPART_KEY = ENVELOPE_NODE_KEY + "multipart"
@ -86,15 +85,15 @@ public fun EnvelopeBuilder.envelopes(
public fun Envelope.parts(): EnvelopeParts {
if (data == null) return emptyList()
//TODO add zip folder reader
val parts = meta.getIndexed(PARTS_KEY).values.mapNotNull { it.node }.map {
val parts = meta.getIndexed(PARTS_KEY).values.map {
PartDescriptor.read(it)
}
return if (parts.isEmpty()) {
listOf(EnvelopePart(data!!, meta[MULTIPART_KEY].node))
listOf(EnvelopePart(data!!, meta[MULTIPART_KEY]))
} else {
parts.map {
val binary = data!!.view(it.offset, it.size)
val meta = Laminate(it.partMeta, meta[MULTIPART_KEY].node)
val meta = Laminate(it.partMeta, meta[MULTIPART_KEY])
EnvelopePart(binary, meta)
}
}

View File

@ -6,13 +6,11 @@ import space.kscience.dataforge.context.Factory
import space.kscience.dataforge.io.IOFormat.Companion.NAME_KEY
import space.kscience.dataforge.io.IOFormatFactory.Companion.IO_FORMAT_TYPE
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaItemValue
import space.kscience.dataforge.meta.MetaRepr
import space.kscience.dataforge.misc.Named
import space.kscience.dataforge.misc.Type
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.values.Value
import kotlin.reflect.KType
import kotlin.reflect.typeOf
@ -117,19 +115,19 @@ public object DoubleIOFormat : IOFormat<Double>, IOFormatFactory<Double> {
override fun readObject(input: Input): Double = input.readDouble()
}
public object ValueIOFormat : IOFormat<Value>, IOFormatFactory<Value> {
override fun invoke(meta: Meta, context: Context): IOFormat<Value> = this
override val name: Name = "value".asName()
override val type: KType get() = typeOf<Value>()
override fun writeObject(output: Output, obj: Value) {
BinaryMetaFormat.run { output.writeValue(obj) }
}
override fun readObject(input: Input): Value {
return (BinaryMetaFormat.run { input.readMetaItem() } as? MetaItemValue)?.value
?: error("The item is not a value")
}
}
//public object ValueIOFormat : IOFormat<Value>, IOFormatFactory<Value> {
// override fun invoke(meta: Meta, context: Context): IOFormat<Value> = this
//
// override val name: Name = "value".asName()
//
// override val type: KType get() = typeOf<Value>()
//
// override fun writeObject(output: Output, obj: Value) {
// BinaryMetaFormat.run { output.writeValue(obj) }
// }
//
// override fun readObject(input: Input): Value {
// return (BinaryMetaFormat.run { input.readMetaItem() } as? MetaItemValue)?.value
// ?: error("The item is not a value")
// }
//}

View File

@ -6,9 +6,11 @@ import space.kscience.dataforge.io.IOFormat.Companion.META_KEY
import space.kscience.dataforge.io.IOFormat.Companion.NAME_KEY
import space.kscience.dataforge.io.IOFormatFactory.Companion.IO_FORMAT_TYPE
import space.kscience.dataforge.io.MetaFormatFactory.Companion.META_FORMAT_TYPE
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.string
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.toName
import kotlin.native.concurrent.ThreadLocal
import kotlin.reflect.KClass
@ -19,13 +21,13 @@ public class IOPlugin(meta: Meta) : AbstractPlugin(meta) {
context.gather<IOFormatFactory<*>>(IO_FORMAT_TYPE).values
}
public fun <T : Any> resolveIOFormat(item: MetaItem, type: KClass<out T>): IOFormat<T>? {
val key = item.string ?: item.node[NAME_KEY]?.string ?: error("Format name not defined")
val name = key.toName()
public fun <T : Any> resolveIOFormat(item: Meta, type: KClass<out T>): IOFormat<T>? {
val key = item.string ?: item[NAME_KEY]?.string ?: error("Format name not defined")
val name = Name.parse(key)
return ioFormatFactories.find { it.name == name }?.let {
@Suppress("UNCHECKED_CAST")
if (it.type != type) error("Format type ${it.type} is not the same as requested type $type")
else it.invoke(item.node[META_KEY].node ?: Meta.EMPTY, context) as IOFormat<T>
else it.invoke(item[META_KEY] ?: Meta.EMPTY, context) as IOFormat<T>
}
}
@ -47,10 +49,10 @@ public class IOPlugin(meta: Meta) : AbstractPlugin(meta) {
private fun resolveEnvelopeFormat(name: Name, meta: Meta = Meta.EMPTY): EnvelopeFormat? =
envelopeFormatFactories.find { it.name == name }?.invoke(meta, context)
public fun resolveEnvelopeFormat(item: MetaItem): EnvelopeFormat? {
val name = item.string ?: item.node[NAME_KEY]?.string ?: error("Envelope format name not defined")
val meta = item.node[META_KEY].node ?: Meta.EMPTY
return resolveEnvelopeFormat(name.toName(), meta)
public fun resolveEnvelopeFormat(item: Meta): EnvelopeFormat? {
val name = item.string ?: item[NAME_KEY]?.string ?: error("Envelope format name not defined")
val meta = item[META_KEY] ?: Meta.EMPTY
return resolveEnvelopeFormat(Name.parse(name), meta)
}
override fun content(target: String): Map<Name, Any> {
@ -62,7 +64,7 @@ public class IOPlugin(meta: Meta) : AbstractPlugin(meta) {
}
public companion object : PluginFactory<IOPlugin> {
public val defaultMetaFormats: List<MetaFormatFactory> = listOf(JsonMetaFormat, BinaryMetaFormat)
public val defaultMetaFormats: List<MetaFormatFactory> = listOf(JsonMetaFormat)
public val defaultEnvelopeFormats: List<EnvelopeFormatFactory> =
listOf(TaggedEnvelopeFormat, TaglessEnvelopeFormat)

View File

@ -10,10 +10,9 @@ import kotlinx.serialization.json.JsonObject
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.io.IOFormat.Companion.NAME_KEY
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.meta.node
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.toJson
import space.kscience.dataforge.meta.toMetaItem
import space.kscience.dataforge.meta.toMeta
import kotlin.reflect.KType
import kotlin.reflect.typeOf
@ -24,7 +23,7 @@ public class JsonMetaFormat(private val json: Json = DEFAULT_JSON) : MetaFormat
override val type: KType get() = typeOf<Meta>()
override fun writeMeta(output: Output, meta: Meta, descriptor: NodeDescriptor?) {
override fun writeMeta(output: Output, meta: Meta, descriptor: MetaDescriptor?) {
val jsonObject = meta.toJson(descriptor)
output.writeUtf8String(json.encodeToString(JsonObject.serializer(), jsonObject))
}
@ -33,11 +32,10 @@ public class JsonMetaFormat(private val json: Json = DEFAULT_JSON) : MetaFormat
NAME_KEY put name.toString()
}
override fun readMeta(input: Input, descriptor: NodeDescriptor?): Meta {
override fun readMeta(input: Input, descriptor: MetaDescriptor?): Meta {
val str = input.readUtf8String()//readByteArray().decodeToString()
val jsonElement = json.parseToJsonElement(str)
val item = jsonElement.toMetaItem(descriptor)
return item.node ?: Meta.EMPTY
return jsonElement.toMeta(descriptor)
}
public companion object : MetaFormatFactory {
@ -50,10 +48,10 @@ public class JsonMetaFormat(private val json: Json = DEFAULT_JSON) : MetaFormat
private val default = JsonMetaFormat()
override fun writeMeta(output: Output, meta: Meta, descriptor: NodeDescriptor?): Unit =
override fun writeMeta(output: Output, meta: Meta, descriptor: MetaDescriptor?): Unit =
default.run { writeMeta(output, meta, descriptor) }
override fun readMeta(input: Input, descriptor: NodeDescriptor?): Meta =
override fun readMeta(input: Input, descriptor: MetaDescriptor?): Meta =
default.run { readMeta(input, descriptor) }
}
}

View File

@ -7,7 +7,7 @@ import io.ktor.utils.io.core.use
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.io.MetaFormatFactory.Companion.META_FORMAT_TYPE
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.misc.Type
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
@ -30,10 +30,10 @@ public interface MetaFormat : IOFormat<Meta> {
public fun writeMeta(
output: Output,
meta: Meta,
descriptor: NodeDescriptor? = null,
descriptor: MetaDescriptor? = null,
)
public fun readMeta(input: Input, descriptor: NodeDescriptor? = null): Meta
public fun readMeta(input: Input, descriptor: MetaDescriptor? = null): Meta
}
@Type(META_FORMAT_TYPE)

View File

@ -10,7 +10,7 @@ import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.string
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.names.toName
/**
* A streaming-friendly envelope format with a short binary tag.
@ -121,7 +121,7 @@ public class TaggedEnvelopeFormat(
override fun invoke(meta: Meta, context: Context): EnvelopeFormat {
val io = context.io
val metaFormatName = meta["name"].string?.toName() ?: JsonMetaFormat.name
val metaFormatName = meta["name"].string?.let { Name.parse(it) } ?: JsonMetaFormat.name
//Check if appropriate factory exists
io.metaFormatFactories.find { it.name == metaFormatName } ?: error("Meta format could not be resolved")

View File

@ -2,12 +2,12 @@ package space.kscience.dataforge.io
import kotlinx.serialization.json.*
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.JsonMeta.Companion.JSON_ARRAY_KEY
import space.kscience.dataforge.values.ListValue
import space.kscience.dataforge.values.number
import space.kscience.dataforge.values.double
import kotlin.test.Test
import kotlin.test.assertEquals
fun Meta.toByteArray(format: MetaFormat = JsonMetaFormat) = buildByteArray {
format.writeObject(this@buildByteArray, this@toByteArray)
}
@ -17,20 +17,6 @@ fun MetaFormat.fromByteArray(packet: ByteArray): Meta {
}
class MetaFormatTest {
@Test
fun testBinaryMetaFormat() {
val meta = Meta {
"a" put 22
"node" put {
"b" put "DDD"
"c" put 11.1
"array" put doubleArrayOf(1.0, 2.0, 3.0)
}
}
val bytes = meta.toByteArray(BinaryMetaFormat)
val result = BinaryMetaFormat.fromByteArray(bytes)
assertEquals(meta, result)
}
@Test
fun testJsonMetaFormat() {
@ -51,36 +37,36 @@ class MetaFormatTest {
if (meta[it] != result[it]) error("${meta[it]} != ${result[it]}")
}
assertEquals<Meta>(meta, result)
assertEquals(meta, result)
}
@Test
fun testJsonToMeta() {
val json = buildJsonArray {
//top level array
add(buildJsonArray {
add(JsonPrimitive(88))
add(buildJsonObject {
addJsonArray {
add(88)
addJsonObject {
put("c", "aasdad")
put("d", true)
})
})
}
}
add("value")
add(buildJsonArray {
add(JsonPrimitive(1.0))
add(JsonPrimitive(2.0))
add(JsonPrimitive(3.0))
})
addJsonArray {
add(1.0)
add(2.0)
add(3.0)
}
}
val meta = json.toMetaItem().node!!
val meta = json.toMeta()
assertEquals(true, meta["$JSON_ARRAY_KEY[0].$JSON_ARRAY_KEY[1].d"].boolean)
assertEquals("value", meta["$JSON_ARRAY_KEY[1]"].string)
assertEquals(listOf(1.0, 2.0, 3.0), meta["$JSON_ARRAY_KEY[2"].value?.list?.map { it.number.toDouble() })
assertEquals(true, meta["${Meta.JSON_ARRAY_KEY}[0].${Meta.JSON_ARRAY_KEY}[1].d"].boolean)
assertEquals("value", meta["${Meta.JSON_ARRAY_KEY}[1]"].string)
assertEquals(listOf(1.0, 2.0, 3.0), meta["${Meta.JSON_ARRAY_KEY}[2]"]?.value?.list?.map { it.double })
}
@Test
fun testJsonStringToMeta(){
fun testJsonStringToMeta() {
val jsonString = """
{
"comments": [
@ -98,8 +84,8 @@ class MetaFormatTest {
}
""".trimIndent()
val json = Json.parseToJsonElement(jsonString)
val meta = json.toMetaItem().node!!
assertEquals(ListValue.EMPTY, meta["comments"].value)
val meta = json.toMeta()
assertEquals(ListValue.EMPTY, meta["comments"]?.value)
}
}

View File

@ -5,9 +5,8 @@ import kotlinx.serialization.cbor.Cbor
import kotlinx.serialization.json.Json
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaSerializer
import space.kscience.dataforge.meta.TypedMetaItem
import space.kscience.dataforge.meta.seal
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.toName
import kotlin.test.Test
import kotlin.test.assertEquals
@ -28,7 +27,7 @@ class MetaSerializerTest {
fun testMetaSerialization() {
val string = JSON_PRETTY.encodeToString(MetaSerializer, meta)
println(string)
val restored = JSON_PLAIN.decodeFromString(MetaSerializer, string)
val restored = JSON_PLAIN.decodeFromString(MetaSerializer, string).seal()
assertEquals(meta, restored)
}
@ -43,7 +42,7 @@ class MetaSerializerTest {
@Test
fun testNameSerialization() {
val name = "a.b.c".toName()
val name = Name.parse("a.b.c")
val string = JSON_PRETTY.encodeToString(Name.serializer(), name)
val restored = JSON_PLAIN.decodeFromString(Name.serializer(), string)
assertEquals(name, restored)
@ -52,7 +51,7 @@ class MetaSerializerTest {
@OptIn(ExperimentalSerializationApi::class)
@Test
fun testMetaItemDescriptor() {
val descriptor = TypedMetaItem.serializer(MetaSerializer).descriptor.getElementDescriptor(0)
val descriptor = MetaSerializer.descriptor.getElementDescriptor(0)
println(descriptor)
}
}

View File

@ -3,7 +3,7 @@ package space.kscience.dataforge.io
import io.ktor.utils.io.core.*
import io.ktor.utils.io.streams.asOutput
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.isEmpty
import space.kscience.dataforge.misc.DFExperimental
import java.nio.file.Files
@ -97,7 +97,7 @@ public inline fun <reified T : Any> IOPlugin.resolveIOFormat(): IOFormat<T>? {
public fun IOPlugin.readMetaFile(
path: Path,
formatOverride: MetaFormat? = null,
descriptor: NodeDescriptor? = null,
descriptor: MetaDescriptor? = null,
): Meta {
if (!Files.exists(path)) error("Meta file $path does not exist")
@ -125,7 +125,7 @@ public fun IOPlugin.writeMetaFile(
path: Path,
meta: Meta,
metaFormat: MetaFormatFactory = JsonMetaFormat,
descriptor: NodeDescriptor? = null,
descriptor: MetaDescriptor? = null,
) {
val actualPath = if (Files.isDirectory(path)) {
path.resolve("@" + metaFormat.name.toString())

File diff suppressed because it is too large Load Diff

View File

@ -1,110 +0,0 @@
package space.kscience.dataforge.meta
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus
import kotlin.collections.set
import kotlin.jvm.Synchronized
//TODO add validator to configuration
/**
* Mutable meta representing object state
*/
@Serializable(Config.Companion::class)
public class Config : AbstractMutableMeta<Config>(), ItemPropertyProvider {
private val listeners = HashSet<ItemListener>()
@Synchronized
private fun itemChanged(name: Name, oldItem: MetaItem?, newItem: MetaItem?) {
listeners.forEach { it.action(name, oldItem, newItem) }
}
/**
* Add change listener to this meta. Owner is declared to be able to remove listeners later. Listener without owner could not be removed
*/
@Synchronized
override fun onChange(owner: Any?, action: (Name, MetaItem?, MetaItem?) -> Unit) {
listeners.add(ItemListener(owner, action))
}
/**
* Remove all listeners belonging to given owner
*/
@Synchronized
override fun removeListener(owner: Any?) {
listeners.removeAll { it.owner === owner }
}
override fun replaceItem(key: NameToken, oldItem: TypedMetaItem<Config>?, newItem: TypedMetaItem<Config>?) {
if (newItem == null) {
children.remove(key)
if (oldItem != null && oldItem is MetaItemNode<Config>) {
oldItem.node.removeListener(this)
}
} else {
children[key] = newItem
if (newItem is MetaItemNode) {
newItem.node.onChange(this) { name, oldChild, newChild ->
itemChanged(key + name, oldChild, newChild)
}
}
}
itemChanged(key.asName(), oldItem, newItem)
}
/**
* Attach configuration node instead of creating one
*/
override fun wrapNode(meta: Meta): Config = meta.asConfig()
override fun empty(): Config = Config()
public companion object ConfigSerializer : KSerializer<Config> {
public 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)
}
}
}
public operator fun Config.get(token: NameToken): TypedMetaItem<Config>? = items[token]
/**
* Create a mutable copy of this [Meta]. The copy is created event if initial [Meta] is a [Config].
* Listeners are not preserved
*/
public fun Meta.toConfig(): Config = Config().also { builder ->
this.items.mapValues { entry ->
val item = entry.value
builder[entry.key.asName()] = when (item) {
is MetaItemValue -> item.value
is MetaItemNode -> MetaItemNode(item.node.asConfig())
}
}
}
/**
* Create a copy of this config, optionally applying the given [block].
* The listeners of the original Config are not retained.
*/
public inline fun Config.copy(block: Config.() -> Unit = {}): Config = toConfig().apply(block)
/**
* Return this [Meta] as [Config] if it is [Config] and create a new copy otherwise
*/
public fun Meta.asConfig(): Config = this as? Config ?: toConfig()

View File

@ -1,25 +1,19 @@
package space.kscience.dataforge.meta
import space.kscience.dataforge.misc.DFBuilder
import space.kscience.dataforge.names.Name
import kotlin.properties.ReadWriteProperty
/**
* A container that holds a [Config].
* A container that holds a [ObservableMeta].
*/
public interface Configurable {
/**
* Backing config
*/
public val config: Config
public val meta: MutableMeta
}
public fun <T : Configurable> T.configure(meta: Meta): T = this.apply { config.update(meta) }
public fun <T : Configurable> T.configure(meta: Meta): T = this.apply { this.meta.update(meta) }
@DFBuilder
public inline fun <T : Configurable> T.configure(action: Config.() -> Unit): T = apply { config.apply(action) }
/* Node delegates */
public fun Configurable.config(key: Name? = null): ReadWriteProperty<Any?, Config?> = config.node(key)
public inline fun <T : Configurable> T.configure(action: MutableMeta.() -> Unit): T = apply { meta.apply(action) }

View File

@ -1,111 +0,0 @@
package space.kscience.dataforge.meta
import space.kscience.dataforge.meta.transformations.MetaConverter
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.values.Value
import kotlin.properties.ReadOnlyProperty
/* Meta delegates */
public typealias ItemDelegate = ReadOnlyProperty<Any?, MetaItem?>
public fun ItemProvider.item(key: Name? = null): ItemDelegate = ReadOnlyProperty { _, property ->
get(key ?: property.name.asName())
}
//TODO add caching for sealed nodes
/**
* Apply a converter to this delegate creating a delegate with a custom type
*/
public fun <R : Any> ItemDelegate.convert(
converter: MetaConverter<R>,
): ReadOnlyProperty<Any?, R?> = ReadOnlyProperty { thisRef, property ->
this@convert.getValue(thisRef, property)?.let(converter::itemToObject)
}
/*
*
*/
public fun <R : Any> ItemDelegate.convert(
converter: MetaConverter<R>,
default: () -> R,
): ReadOnlyProperty<Any?, R> = ReadOnlyProperty<Any?, R> { thisRef, property ->
this@convert.getValue(thisRef, property)?.let(converter::itemToObject) ?: default()
}
/**
* A converter with a custom reader transformation
*/
public fun <R> ItemDelegate.convert(
reader: (MetaItem?) -> R,
): ReadOnlyProperty<Any?, R> = ReadOnlyProperty<Any?, R> { thisRef, property ->
this@convert.getValue(thisRef, property).let(reader)
}
/* Read-only delegates for [ItemProvider] */
/**
* A property delegate that uses custom key
*/
public fun ItemProvider.value(key: Name? = null): ReadOnlyProperty<Any?, Value?> =
item(key).convert(MetaConverter.value)
public fun ItemProvider.string(key: Name? = null): ReadOnlyProperty<Any?, String?> =
item(key).convert(MetaConverter.string)
public fun ItemProvider.boolean(key: Name? = null): ReadOnlyProperty<Any?, Boolean?> =
item(key).convert(MetaConverter.boolean)
public fun ItemProvider.number(key: Name? = null): ReadOnlyProperty<Any?, Number?> =
item(key).convert(MetaConverter.number)
public fun ItemProvider.double(key: Name? = null): ReadOnlyProperty<Any?, Double?> =
item(key).convert(MetaConverter.double)
public fun ItemProvider.float(key: Name? = null): ReadOnlyProperty<Any?, Float?> =
item(key).convert(MetaConverter.float)
public fun ItemProvider.int(key: Name? = null): ReadOnlyProperty<Any?, Int?> =
item(key).convert(MetaConverter.int)
public fun ItemProvider.long(key: Name? = null): ReadOnlyProperty<Any?, Long?> =
item(key).convert(MetaConverter.long)
public fun ItemProvider.node(key: Name? = null): ReadOnlyProperty<Any?, Meta?> =
item(key).convert(MetaConverter.meta)
public fun ItemProvider.string(default: String, key: Name? = null): ReadOnlyProperty<Any?, String> =
item(key).convert(MetaConverter.string) { default }
public fun ItemProvider.boolean(default: Boolean, key: Name? = null): ReadOnlyProperty<Any?, Boolean> =
item(key).convert(MetaConverter.boolean) { default }
public fun ItemProvider.number(default: Number, key: Name? = null): ReadOnlyProperty<Any?, Number> =
item(key).convert(MetaConverter.number) { default }
public fun ItemProvider.double(default: Double, key: Name? = null): ReadOnlyProperty<Any?, Double> =
item(key).convert(MetaConverter.double) { default }
public fun ItemProvider.float(default: Float, key: Name? = null): ReadOnlyProperty<Any?, Float> =
item(key).convert(MetaConverter.float) { default }
public fun ItemProvider.int(default: Int, key: Name? = null): ReadOnlyProperty<Any?, Int> =
item(key).convert(MetaConverter.int) { default }
public fun ItemProvider.long(default: Long, key: Name? = null): ReadOnlyProperty<Any?, Long> =
item(key).convert(MetaConverter.long) { default }
public inline fun <reified E : Enum<E>> ItemProvider.enum(default: E, key: Name? = null): ReadOnlyProperty<Any?, E> =
item(key).convert(MetaConverter.enum()) { default }
public fun ItemProvider.string(key: Name? = null, default: () -> String): ReadOnlyProperty<Any?, String> =
item(key).convert(MetaConverter.string, default)
public fun ItemProvider.boolean(key: Name? = null, default: () -> Boolean): ReadOnlyProperty<Any?, Boolean> =
item(key).convert(MetaConverter.boolean, default)
public fun ItemProvider.number(key: Name? = null, default: () -> Number): ReadOnlyProperty<Any?, Number> =
item(key).convert(MetaConverter.number, default)

View File

@ -1,88 +0,0 @@
package space.kscience.dataforge.meta
import space.kscience.dataforge.names.*
public fun interface ItemProvider {
//getItem used instead of get in order to provide extension freedom
public fun getItem(name: Name): MetaItem?
public companion object {
public val EMPTY: ItemProvider = ItemProvider { null }
}
}
/* 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 [MetaItemNode]
*/
public operator fun ItemProvider?.get(name: Name): MetaItem? = this?.getItem(name)
/**
* Root item of this provider
*/
public val ItemProvider.rootItem: MetaItem? get() = get(Name.EMPTY)
/**
* The root node of this item provider if it is present
*/
public val ItemProvider.rootNode: Meta? get() = rootItem.node
/**
* Parse [Name] from [key] using full name notation and pass it to [Meta.get]
*/
public operator fun ItemProvider?.get(key: String): MetaItem? = this?.get(key.toName())
/**
* Create a provider that uses given provider for default values if those are not found in this provider
*/
public fun ItemProvider.withDefault(default: ItemProvider?): ItemProvider = if (default == null) {
this
} else {
ItemProvider {
this[it] ?: default[it]
}
}
/**
* Get all items matching given name. The index of the last element, if present is used as a [Regex],
* against which indexes of elements are matched.
*/
public fun ItemProvider.getIndexed(name: Name): Map<String?, MetaItem> {
val root: Meta = when (name.length) {
0 -> error("Can't use empty name for 'getIndexed'")
1 -> this.rootNode ?: return emptyMap()
else -> this[name.cutLast()].node ?: return emptyMap()
}
val (body, index) = name.lastOrNull()!!
return if (index == null) {
root.items.filter { it.key.body == body }.mapKeys { it.key.index }
} else {
val regex = index.toRegex()
root.items.filter { it.key.body == body && (regex.matches(it.key.index ?: "")) }
.mapKeys { it.key.index }
}
}
public fun ItemProvider.getIndexed(name: String): Map<String?, MetaItem> = this@getIndexed.getIndexed(name.toName())
/**
* Return a provider referencing a child node
*/
public fun ItemProvider.getChild(childName: Name): ItemProvider = get(childName).node ?: ItemProvider.EMPTY
public fun ItemProvider.getChild(childName: String): ItemProvider = getChild(childName.toName())
///**
// * Get all items matching given name.
// */
//@Suppress("UNCHECKED_CAST")
//public fun <M : TypedMeta<M>> M.getIndexed(name: Name): Map<String?, MetaItem<M>> =
// (this as Meta).getIndexed(name) as Map<String?, MetaItem<M>>
//
//public fun <M : TypedMeta<M>> M.getIndexed(name: String): Map<String?, MetaItem<M>> =
// getIndexed(name.toName())

View File

@ -3,177 +3,240 @@
package space.kscience.dataforge.meta
import kotlinx.serialization.json.*
import space.kscience.dataforge.meta.JsonMeta.Companion.JSON_ARRAY_KEY
import space.kscience.dataforge.meta.descriptors.ItemDescriptor
import space.kscience.dataforge.meta.descriptors.ItemDescriptor.Companion.DEFAULT_INDEX_KEY
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.meta.descriptors.ValueDescriptor
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.get
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.withIndex
import space.kscience.dataforge.values.*
private const val jsonArrayKey: String = "@jsonArray"
public val Meta.Companion.JSON_ARRAY_KEY: String get() = jsonArrayKey
/**
* @param descriptor reserved for custom serialization in future
*/
public fun Value.toJson(descriptor: ValueDescriptor? = null): JsonElement = when (type) {
public fun Value.toJson(descriptor: MetaDescriptor? = null): JsonElement = when (type) {
ValueType.NUMBER -> JsonPrimitive(numberOrNull)
ValueType.STRING -> JsonPrimitive(string)
ValueType.BOOLEAN -> JsonPrimitive(boolean)
ValueType.LIST -> JsonArray(list.map { it.toJson() })
ValueType.LIST -> JsonArray(list.map { it.toJson(descriptor) })
ValueType.NULL -> JsonNull
}
//Use these methods to customize JSON key mapping
@Suppress("NULLABLE_EXTENSION_OPERATOR_WITH_SAFE_CALL_RECEIVER")
private fun String.toJsonKey(descriptor: ItemDescriptor?) = descriptor?.attributes?.get("jsonName").string ?: toString()
private fun String.toJsonKey(descriptor: MetaDescriptor?) = descriptor?.attributes?.get("jsonName").string ?: toString()
//private fun NodeDescriptor?.getDescriptor(key: String) = this?.items?.get(key)
/**
* Convert given [Meta] to [JsonObject]. Primitives and nodes are copied as is, same name siblings are treated as json arrays
*/
private fun Meta.toJsonWithIndex(descriptor: NodeDescriptor?, indexValue: String?): JsonObject {
val elementMap = HashMap<String, JsonElement>()
fun MetaItem.toJsonElement(itemDescriptor: ItemDescriptor?, index: String?): JsonElement = when (this) {
is MetaItemValue -> {
value.toJson(itemDescriptor as? ValueDescriptor)
}
is MetaItemNode -> {
node.toJsonWithIndex(itemDescriptor as? NodeDescriptor, index)
private fun Meta.toJsonWithIndex(descriptor: MetaDescriptor?, index: String?): JsonElement = if (items.isEmpty()) {
value?.toJson(descriptor) ?: JsonObject(emptyMap())
} else {
val pairs: MutableList<Pair<String, JsonElement>> = items.entries.groupBy {
it.key.body
}.mapTo(ArrayList()) { (body, list) ->
val childDescriptor = descriptor?.children?.get(body)
if (list.size == 1) {
val (token, element) = list.first()
//do not add empty element
val child: JsonElement = element.toJsonWithIndex(childDescriptor, token.index)
body to child
} else {
val elements: List<JsonElement> = list.sortedBy { it.key.index }.mapIndexed { index, entry ->
//Use index if it is not equal to the item order
val actualIndex = if (index.toString() != entry.key.index) entry.key.index else null
entry.value.toJsonWithIndex(childDescriptor, actualIndex)
}
body to JsonArray(elements)
}
}
fun addElement(key: String) {
val itemDescriptor = descriptor?.items?.get(key)
val jsonKey = key.toJsonKey(itemDescriptor)
val items: Map<String?, MetaItem> = getIndexed(key)
when (items.size) {
0 -> {
//do nothing
}
1 -> {
elementMap[jsonKey] = items.values.first().toJsonElement(itemDescriptor, null)
}
else -> {
val array = buildJsonArray {
items.forEach { (index, item) ->
add(item.toJsonElement(itemDescriptor, index))
}
}
elementMap[jsonKey] = array
}
}
//Add index if needed
if (index != null) {
pairs += (descriptor?.indexKey ?: Meta.INDEX_KEY) to JsonPrimitive(index)
}
((descriptor?.items?.keys ?: emptySet()) + items.keys.map { it.body }).forEach(::addElement)
if (indexValue != null) {
val indexKey = descriptor?.indexKey ?: DEFAULT_INDEX_KEY
elementMap[indexKey] = JsonPrimitive(indexValue)
//Add value if needed
if (value != null) {
pairs += Meta.VALUE_KEY to value!!.toJson(descriptor)
}
return JsonObject(elementMap)
JsonObject(pairs.toMap())
}
public fun Meta.toJson(descriptor: NodeDescriptor? = null): JsonObject = toJsonWithIndex(descriptor, null)
public fun Meta.toJson(descriptor: MetaDescriptor? = null): JsonObject {
val element = toJsonWithIndex(descriptor, null)
return if (element is JsonObject) {
element
} else {
buildJsonObject {
put("@value", element)
}
}
}
public fun JsonObject.toMeta(descriptor: NodeDescriptor? = null): JsonMeta = JsonMeta(this, descriptor)
public fun JsonPrimitive.toValue(descriptor: ValueDescriptor?): Value {
/**
* Convert a Json primitive to a [Value]
*/
public fun JsonPrimitive.toValue(descriptor: MetaDescriptor?): Value {
return when (this) {
JsonNull -> Null
else -> {
if (isString) {
StringValue(content)
} else {
//consider using LazyParse
content.parseValue()
}
}
}
}
public fun JsonElement.toMetaItem(descriptor: ItemDescriptor? = null): TypedMetaItem<JsonMeta> = when (this) {
is JsonPrimitive -> {
val value = this.toValue(descriptor as? ValueDescriptor)
MetaItemValue(value)
}
is JsonObject -> {
val meta = JsonMeta(this, descriptor as? NodeDescriptor)
MetaItemNode(meta)
}
/**
* Turn this [JsonElement] into a [ListValue] with recursion or return null if it contains objects
*/
private fun JsonElement.toValueOrNull(descriptor: MetaDescriptor?): Value? = when (this) {
is JsonPrimitive -> toValue(descriptor)
is JsonObject -> get(Meta.VALUE_KEY)?.toValueOrNull(descriptor)
is JsonArray -> {
if (this.all { it is JsonPrimitive }) {
val value = if (isEmpty()) {
Null
} else {
map<JsonElement, Value> {
//We already checked that all values are primitives
(it as JsonPrimitive).toValue(descriptor as? ValueDescriptor)
}.asValue()
}
MetaItemValue(value)
} else {
//We can't return multiple items therefore we create top level node
buildJsonObject { put(JSON_ARRAY_KEY, this@toMetaItem) }.toMetaItem(descriptor)
if (isEmpty()) ListValue.EMPTY else {
val values = map { it.toValueOrNull(descriptor) }
values.map { it ?: return null }.asValue()
}
}
}
/**
* A meta wrapping json object
* Fill a mutable map with children produced from [element] with given top level [key]
*/
public class JsonMeta(private val json: JsonObject, private val descriptor: NodeDescriptor? = null) : MetaBase() {
private fun buildItems(): Map<NameToken, TypedMetaItem<JsonMeta>> {
val map = LinkedHashMap<NameToken, TypedMetaItem<JsonMeta>>()
json.forEach { (jsonKey, value) ->
val key = NameToken(jsonKey)
val itemDescriptor = descriptor?.items?.get(jsonKey)
when (value) {
is JsonPrimitive -> {
map[key] = MetaItemValue(value.toValue(itemDescriptor as? ValueDescriptor))
}
is JsonObject -> {
map[key] = MetaItemNode(
JsonMeta(
value,
itemDescriptor as? NodeDescriptor
)
)
}
is JsonArray -> if (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)
private fun MutableMap<NameToken, SealedMeta>.addJsonElement(
key: String,
element: JsonElement,
descriptor: MetaDescriptor?
) {
when (element) {
is JsonPrimitive -> put(NameToken(key), Meta(element.toValue(descriptor)))
is JsonArray -> {
val value = element.toValueOrNull(descriptor)
if (value != null) {
put(NameToken(key), Meta(value))
} else {
val indexKey = descriptor?.indexKey ?: Meta.INDEX_KEY
element.forEachIndexed { serial, childElement ->
val index = (childElement as? JsonObject)?.get(indexKey)?.jsonPrimitive?.content
?: serial.toString()
val child: SealedMeta = when (childElement) {
is JsonObject -> childElement.toMeta(descriptor)
is JsonArray -> {
val childValue = childElement.toValueOrNull(null)
if (childValue == null) {
SealedMeta(null,
hashMapOf<NameToken, SealedMeta>().apply {
addJsonElement(Meta.JSON_ARRAY_KEY, childElement, null)
}
)
} else {
Meta(childValue)
}
}
)
map[key] = MetaItemValue(listValue)
} else value.forEachIndexed { index, jsonElement ->
val indexKey = (itemDescriptor as? NodeDescriptor)?.indexKey ?: DEFAULT_INDEX_KEY
val indexValue: String = (jsonElement as? JsonObject)
?.get(indexKey)?.jsonPrimitive?.contentOrNull
?: index.toString() //In case index is non-string, the backward transformation will be broken.
val token = key.withIndex(indexValue)
map[token] = jsonElement.toMetaItem(itemDescriptor)
is JsonPrimitive -> Meta(childElement.toValue(null))
}
put(NameToken(key, index), child)
}
}
}
return map
is JsonObject -> {
val indexKey = descriptor?.indexKey ?: Meta.INDEX_KEY
val index = element[indexKey]?.jsonPrimitive?.content
put(NameToken(key, index), element.toMeta(descriptor))
}
}
}
override val items: Map<NameToken, TypedMetaItem<JsonMeta>> by lazy(::buildItems)
public companion object {
/**
* A key representing top-level json array of nodes, which could not be directly represented by a meta node
*/
public const val JSON_ARRAY_KEY: String = "@jsonArray"
public fun JsonObject.toMeta(descriptor: MetaDescriptor? = null): SealedMeta {
val map = LinkedHashMap<NameToken, SealedMeta>()
forEach { (key, element) ->
if (key != Meta.VALUE_KEY) {
map.addJsonElement(key, element, descriptor?.get(key))
}
}
}
return SealedMeta(get(Meta.VALUE_KEY)?.toValueOrNull(descriptor), map)
}
public fun JsonElement.toMeta(descriptor: MetaDescriptor? = null): SealedMeta = when (this) {
is JsonPrimitive -> Meta(toValue(descriptor))
is JsonObject -> toMeta(descriptor)
is JsonArray -> SealedMeta(null,
linkedMapOf<NameToken, SealedMeta>().apply {
addJsonElement(Meta.JSON_ARRAY_KEY, this@toMeta, null)
}
)
}
//
///**
// * A meta wrapping json object
// */
//public class JsonMeta(
// private val json: JsonElement,
// private val descriptor: MetaDescriptor? = null
//) : TypedMeta<JsonMeta> {
//
// private val indexName by lazy { descriptor?.indexKey ?: Meta.INDEX_KEY }
//
// override val value: Value? by lazy {
// json.toValueOrNull(descriptor)
// }
//
// private fun MutableMap<NameToken, JsonMeta>.appendArray(json: JsonArray, key: String) {
// json.forEachIndexed { index, child ->
// if (child is JsonArray) {
// appendArray(child, key)
// } else {
// //Use explicit index or order for index
// val tokenIndex = (child as? JsonObject)
// ?.get(indexName)
// ?.jsonPrimitive?.content
// ?: index.toString()
// val token = NameToken(key, tokenIndex)
// this[token] = JsonMeta(child)
// }
// }
// }
//
// override val items: Map<NameToken, JsonMeta> by lazy {
// val map = HashMap<NameToken, JsonMeta>()
// when (json) {
// is JsonObject -> json.forEach { (name, child) ->
// //skip value key
// if (name != Meta.VALUE_KEY) {
// if (child is JsonArray && child.any { it is JsonObject }) {
// map.appendArray(child, name)
// } else {
//
// val index = (child as? JsonObject)?.get(indexName)?.jsonPrimitive?.content
// val token = NameToken(name, index)
// map[token] = JsonMeta(child, descriptor?.get(name))
// }
// }
// }
// is JsonArray -> {
// //return children only if it is not value
// if (value == null) map.appendArray(json, JSON_ARRAY_KEY)
// }
// else -> {
// //do nothing
// }
// }
// map
// }
//
// override fun toString(): String = Meta.toString(this)
// override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta)
// override fun hashCode(): Int = Meta.hashCode(this)
//
// public companion object {
// /**
// * A key representing top-level json array of nodes, which could not be directly represented by a meta node
// */
// public const val JSON_ARRAY_KEY: String = "@jsonArray"
// }
//}

View File

@ -2,37 +2,53 @@ package space.kscience.dataforge.meta
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.values.Value
/**
* 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.
*/
public class Laminate(layers: List<Meta>) : MetaBase() {
public class Laminate internal constructor(public val layers: List<Meta>) : TypedMeta<Laminate> {
public val layers: List<Meta> = layers.flatMap {
if (it is Laminate) {
it.layers
} else {
listOf(it)
override val value: Value? = layers.firstNotNullOfOrNull { it.value }
override val items: Map<NameToken, Laminate> by lazy {
layers.map { it.items.keys }.flatten().associateWith { key ->
Laminate(layers.mapNotNull { it.items[key] })
}
}
override val items: Map<NameToken, TypedMetaItem<Meta>> by lazy {
layers.map { it.items.keys }.flatten().associateWith { key ->
layers.asSequence().map { it.items[key] }.filterNotNull().let(replaceRule)
}
override fun getMeta(name: Name): Laminate? {
val childLayers = layers.mapNotNull { it.getMeta(name) }
return if (childLayers.isEmpty()) null else Laminate(childLayers)
}
/**
* Generate sealed meta using [mergeRule]
* Generate sealed meta by interweaving all layers. If a value is present in at least on layer, it will be present
* in the result.
*/
public fun merge(): SealedMeta {
val items = layers.map { it.items.keys }.flatten().associateWith { key ->
layers.asSequence().map { it.items[key] }.filterNotNull().merge()
}
return SealedMeta(items)
return SealedMeta(value, items)
}
/**
* Generate sealed meta by stacking layers. If node is present in the upper layer, then the lower layers will be
* ignored event if they have values that are not present on top layer.
*/
public fun top(): SealedMeta {
val items = layers.map { it.items.keys }.flatten().associateWith { key ->
layers.asSequence().map { it.items[key] }.filterNotNull().first().seal()
}
return SealedMeta(value, items)
}
override fun toString(): String = Meta.toString(this)
override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta)
override fun hashCode(): Int = Meta.hashCode(this)
public companion object {
/**
@ -40,33 +56,21 @@ public class Laminate(layers: List<Meta>) : MetaBase() {
*
* TODO add picture
*/
public val replaceRule: (Sequence<MetaItem>) -> TypedMetaItem<SealedMeta> = { it.first().seal() }
public val replaceRule: (Sequence<Meta>) -> SealedMeta? = { it.firstOrNull()?.seal() }
private fun Sequence<MetaItem>.merge(): TypedMetaItem<SealedMeta> {
return when {
all { it is MetaItemValue } -> //If all items are values, take first
first().seal()
all { it is MetaItemNode } -> {
//list nodes in item
val nodes = map { (it as MetaItemNode).node }
//represent as key->value entries
val entries = nodes.flatMap { it.items.entries.asSequence() }
//group by keys
val groups = entries.groupBy { it.key }
// recursively apply the rule
val items = groups.mapValues { entry ->
entry.value.asSequence().map { it.value }.merge()
}
MetaItemNode(SealedMeta(items))
}
else -> map {
when (it) {
is MetaItemValue -> MetaItemNode(Meta { Meta.VALUE_KEY put it.value })
is MetaItemNode -> it
}
}.merge()
private fun Sequence<Meta>.merge(): SealedMeta {
val value = firstNotNullOfOrNull { it.value }
//list nodes in item
val nodes = toList()
//represent as key->value entries
val entries = nodes.flatMap { it.items.entries.asSequence() }
//group by keys
val groups = entries.groupBy { it.key }
// recursively apply the rule
val items = groups.mapValues { entry ->
entry.value.asSequence().map { it.value }.merge()
}
return SealedMeta(value, items)
}
@ -74,17 +78,27 @@ public class Laminate(layers: List<Meta>) : MetaBase() {
* The values a replaced but meta children are joined
* TODO add picture
*/
public val mergeRule: (Sequence<MetaItem>) -> TypedMetaItem<SealedMeta> = { it.merge() }
public val mergeRule: (Sequence<Meta>) -> SealedMeta? = { it.merge() }
}
}
@Suppress("FunctionName")
public fun Laminate(vararg layers: Meta?): Laminate = Laminate(layers.filterNotNull())
public fun Laminate(layers: Collection<Meta?>): Laminate {
val flatLayers = layers.flatMap {
if (it is Laminate) {
it.layers
} else {
listOf(it)
}
}.filterNotNull()
return Laminate(flatLayers)
}
public fun Laminate(vararg layers: Meta?): Laminate = Laminate(listOf(*layers))
/**
* Performance optimized version of get method
*/
public fun Laminate.getFirst(name: Name): MetaItem? {
public fun Laminate.getFirst(name: Name): Meta? {
layers.forEach { layer ->
layer[name]?.let { return it }
}

View File

@ -1,50 +1,57 @@
package space.kscience.dataforge.meta
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import space.kscience.dataforge.misc.Type
import space.kscience.dataforge.misc.unsafeCast
import space.kscience.dataforge.names.*
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.*
/**
* The object that could be represented as [Meta]. Meta provided by [toMeta] method should fully represent object state.
* Meaning that two states with the same meta are equal.
*/
@Serializable(MetaSerializer::class)
public interface MetaRepr {
public fun toMeta(): Meta
}
/**
* Generic meta tree representation. Elements are [TypedMetaItem] objects that could be represented by three different entities:
* * [MetaItemValue] (leaf)
* * [MetaItemNode] single node
*
* * Same name siblings are supported via elements with the same [Name] but different queries
* A container for meta nodes
*/
public interface Meta : MetaRepr, ItemProvider {
/**
* Top level items of meta tree
*/
public val items: Map<NameToken, MetaItem>
public fun interface MetaProvider : ValueProvider {
public fun getMeta(name: Name): Meta?
override fun getItem(name: Name): MetaItem? {
if (name.isEmpty()) return MetaItemNode(this)
return name.firstOrNull()?.let { token ->
val tail = name.cutFirst()
when (tail.length) {
0 -> items[token]
else -> items[token]?.node?.get(tail)
}
override fun getValue(name: Name): Value? = getMeta(name)?.value
}
/**
* A meta node
* TODO add documentation
* Same name siblings are supported via elements with the same [Name] but different indices.
*/
@Type(Meta.TYPE)
@Serializable(MetaSerializer::class)
public interface Meta : MetaRepr, MetaProvider {
public val value: Value?
public val items: Map<NameToken, Meta>
override fun getMeta(name: Name): Meta? {
tailrec fun Meta.find(name: Name): Meta? = if (name.isEmpty()) {
this
} else {
items[name.firstOrNull()!!]?.find(name.cutFirst())
}
return find(name)
}
override fun toMeta(): Meta = this
override fun equals(other: Any?): Boolean
override fun hashCode(): Int
override fun toString(): String
override fun equals(other: Any?): Boolean
override fun hashCode(): Int
public companion object {
public const val TYPE: String = "meta"
@ -53,43 +60,194 @@ public interface Meta : MetaRepr, ItemProvider {
* A key for single value node
*/
public const val VALUE_KEY: String = "@value"
public const val INDEX_KEY: String = "@index"
public fun equals(meta1: Meta, meta2: Meta): Boolean = meta1.items == meta2.items
public val EMPTY: Meta = object : MetaBase() {
override val items: Map<NameToken, MetaItem> = emptyMap()
public fun hashCode(meta: Meta): Int {
var result = meta.value?.hashCode() ?: 0
result = 31 * result + meta.items.hashCode()
return result
}
}
}
public operator fun Meta.get(token: NameToken): MetaItem? = items.get(token)
/**
* Get a sequence of [Name]-[Value] pairs
*/
public fun Meta.valueSequence(): Sequence<Pair<Name, Value>> {
return items.asSequence().flatMap { (key, item) ->
when (item) {
is MetaItemValue -> sequenceOf(key.asName() to item.value)
is MetaItemNode -> item.node.valueSequence().map { pair -> (key.asName() + pair.first) to pair.second }
public fun equals(meta1: Meta?, meta2: Meta?): Boolean {
if (meta1 == null && meta2 == null) return true
if (meta1 == null || meta2 == null) return false
if (meta1.value != meta2.value) return false
if (meta1.items.keys != meta2.items.keys) return false
return meta1.items.keys.all {
equals(meta1[it], meta2[it])
}
}
private val json = Json {
prettyPrint = true
useArrayPolymorphism = true
}
public fun toString(meta: Meta): String = json.encodeToString(MetaSerializer, meta)
public val EMPTY: Meta = SealedMeta(null, emptyMap())
}
}
/**
* Get a sequence of all [Name]-[TypedMetaItem] pairs for all items including nodes
* True if this [Meta] does not have children
*/
public fun Meta.itemSequence(): Sequence<Pair<Name, MetaItem>> = sequence {
public val Meta.isLeaf: Boolean get() = items.isEmpty()
public operator fun Meta.get(token: NameToken): Meta? = items[token]
/**
* 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]
*/
public operator fun Meta.get(name: Name): Meta? = getMeta(name)
/**
* Parse [Name] from [key] using full name notation and pass it to [Meta.get]
*/
public operator fun Meta.get(key: String): Meta? = this[Name.parse(key)]
/**
* Get all items matching given name. The index of the last element, if present is used as a [Regex],
* against which indexes of elements are matched.
*/
public fun Meta.getIndexed(name: Name): Map<String?, Meta> {
val root: Meta = when (name.length) {
0 -> error("Can't use empty name for 'getIndexed'")
1 -> this
else -> this[name.cutLast()] ?: return emptyMap()
}
val (body, index) = name.lastOrNull()!!
return if (index == null) {
root.items
.filter { it.key.body == body }
.mapKeys { it.key.index }
} else {
val regex = index.toRegex()
root.items
.filter { it.key.body == body && (regex.matches(it.key.index ?: "")) }
.mapKeys { it.key.index }
}
}
/**
* A meta node that ensures that all of its descendants has at least the same type.
*
*/
public interface TypedMeta<out M : TypedMeta<M>> : Meta {
override val items: Map<NameToken, M>
override fun getMeta(name: Name): M? {
tailrec fun M.find(name: Name): M? = if (name.isEmpty()) {
this
} else {
items[name.firstOrNull()!!]?.find(name.cutFirst())
}
return self.find(name)
}
override fun toMeta(): Meta = this
}
/**
* Access self as a recursive type instance
*/
public inline val <M : TypedMeta<M>> TypedMeta<M>.self: M get() = unsafeCast()
//public typealias Meta = TypedMeta<*>
public operator fun <M : TypedMeta<M>> TypedMeta<M>.get(token: NameToken): M? = items[token]
/**
* Perform recursive item search using given [name]. Each [NameToken] is treated as a name in [TypedMeta.items] of a parent node.
*
* If [name] is empty return current [Meta]
*/
public tailrec operator fun <M : TypedMeta<M>> TypedMeta<M>.get(name: Name): M? = if (name.isEmpty()) {
self
} else {
get(name.firstOrNull()!!)?.get(name.cutFirst())
}
/**
* Parse [Name] from [key] using full name notation and pass it to [TypedMeta.get]
*/
public operator fun <M : TypedMeta<M>> TypedMeta<M>.get(key: String): M? = this[Name.parse(key)]
/**
* Get a sequence of [Name]-[Value] pairs using top-down traversal of the tree
*/
public fun Meta.valueSequence(): Sequence<Pair<Name, Value>> = sequence {
items.forEach { (key, item) ->
item.value?.let { itemValue ->
yield(key.asName() to itemValue)
}
yieldAll(item.valueSequence().map { pair -> (key.asName() + pair.first) to pair.second })
}
}
/**
* Get a sequence of all [Name]-[TypedMeta] pairs in a top-down traversal
*/
public fun Meta.nodeSequence(): Sequence<Pair<Name, Meta>> = sequence {
items.forEach { (key, item) ->
yield(key.asName() to item)
if (item is MetaItemNode) {
yieldAll(item.node.itemSequence().map { (innerKey, innerItem) ->
(key + innerKey) to innerItem
})
}
yieldAll(item.nodeSequence().map { (innerKey, innerItem) ->
(key + innerKey) to innerItem
})
}
}
public operator fun Meta.iterator(): Iterator<Pair<Name, MetaItem>> = itemSequence().iterator()
public operator fun Meta.iterator(): Iterator<Pair<Name, Meta>> = nodeSequence().iterator()
public fun Meta.isEmpty(): Boolean = this === Meta.EMPTY || this.items.isEmpty()
public fun Meta.isEmpty(): Boolean = this === Meta.EMPTY
|| (value == null && (items.isEmpty() || items.values.all { it.isEmpty() }))
/* Get operations*/
/**
* Get all items matching given name. The index of the last element, if present is used as a [Regex],
* against which indexes of elements are matched.
*/
@Suppress("UNCHECKED_CAST")
public fun <M : TypedMeta<M>> TypedMeta<M>.getIndexed(name: Name): Map<String?, M> =
(this as Meta).getIndexed(name) as Map<String?, M>
public fun <M : TypedMeta<M>> TypedMeta<M>.getIndexed(name: String): Map<String?, Meta> = getIndexed(Name.parse(name))
public val Meta?.string: String? get() = this?.value?.string
public val Meta?.boolean: Boolean? get() = this?.value?.boolean
public val Meta?.number: Number? get() = this?.value?.numberOrNull
public val Meta?.double: Double? get() = number?.toDouble()
public val Meta?.float: Float? get() = number?.toFloat()
public val Meta?.int: Int? get() = number?.toInt()
public val Meta?.long: Long? get() = number?.toLong()
public val Meta?.short: Short? get() = number?.toShort()
public inline fun <reified E : Enum<E>> Meta?.enum(): E? = this?.value?.let {
if (it is EnumValue<*>) {
it.value as E
} else {
string?.let { str -> enumValueOf<E>(str) }
}
}
public val Meta.stringList: List<String>? get() = value?.list?.map { it.string }
/**
* Create a provider that uses given provider for default values if those are not found in this provider
*/
public fun Meta.withDefault(default: Meta?): Meta = if (default == null) {
this
} else {
Laminate(this, default)
}

View File

@ -1,141 +0,0 @@
package space.kscience.dataforge.meta
import space.kscience.dataforge.misc.DFBuilder
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.values.EnumValue
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.asValue
import kotlin.jvm.JvmName
/**
* DSL builder for meta. Is not intended to store mutable state
*/
@DFBuilder
public class MetaBuilder : AbstractMutableMeta<MetaBuilder>() {
override fun wrapNode(meta: Meta): MetaBuilder = if (meta is MetaBuilder) meta else meta.toMutableMeta()
override fun empty(): MetaBuilder = MetaBuilder()
public infix fun String.put(item: MetaItem?) {
set(this, item)
}
public infix fun String.put(value: Value?) {
set(this, value)
}
public infix fun String.put(string: String?) {
set(this, string?.asValue())
}
public infix fun String.put(number: Number?) {
set(this, number?.asValue())
}
public infix fun String.put(boolean: Boolean?) {
set(this, boolean?.asValue())
}
public infix fun String.put(enum: Enum<*>) {
set(this, EnumValue(enum))
}
@JvmName("putValues")
public infix fun String.put(iterable: Iterable<Value>) {
set(this, iterable.asValue())
}
@JvmName("putNumbers")
public infix fun String.put(iterable: Iterable<Number>) {
set(this, iterable.map { it.asValue() }.asValue())
}
@JvmName("putStrings")
public infix fun String.put(iterable: Iterable<String>) {
set(this, iterable.map { it.asValue() }.asValue())
}
public infix fun String.put(array: DoubleArray) {
set(this, array.asValue())
}
public infix fun String.put(meta: Meta?) {
this@MetaBuilder[this] = meta
}
public infix fun String.put(repr: MetaRepr?) {
set(this, repr?.toMeta())
}
@JvmName("putMetas")
public infix fun String.put(value: Iterable<Meta>) {
set(this,value.toList())
}
public inline infix fun String.put(metaBuilder: MetaBuilder.() -> Unit) {
this@MetaBuilder[this] = MetaBuilder().apply(metaBuilder)
}
public infix fun Name.put(value: Value?) {
set(this, value)
}
public infix fun Name.put(string: String?) {
set(this, string?.asValue())
}
public infix fun Name.put(number: Number?) {
set(this, number?.asValue())
}
public infix fun Name.put(boolean: Boolean?) {
set(this, boolean?.asValue())
}
public infix fun Name.put(enum: Enum<*>) {
set(this, EnumValue(enum))
}
@JvmName("putValues")
public infix fun Name.put(iterable: Iterable<Value>) {
set(this, iterable.asValue())
}
public infix fun Name.put(meta: Meta?) {
this@MetaBuilder[this] = meta
}
public infix fun Name.put(repr: MetaRepr?) {
set(this, repr?.toMeta())
}
@JvmName("putMetas")
public infix fun Name.put(value: Iterable<Meta>) {
set(this, value.toList())
}
public infix fun Name.put(metaBuilder: MetaBuilder.() -> Unit) {
this@MetaBuilder[this] = MetaBuilder().apply(metaBuilder)
}
}
/**
* For safety, builder always copies the initial meta even if it is builder itself
*/
public fun Meta.toMutableMeta(): MetaBuilder {
return MetaBuilder().also { builder ->
items.mapValues { entry ->
val item = entry.value
builder[entry.key.asName()] = when (item) {
is MetaItemValue -> item.value
is MetaItemNode -> MetaItemNode(item.node.toMutableMeta())
}
}
}
}
/**
* Build a [MetaBuilder] using given transformation
*/
@Suppress("FunctionName")
public inline fun Meta(builder: MetaBuilder.() -> Unit): MetaBuilder = MetaBuilder().apply(builder)

View File

@ -0,0 +1,85 @@
package space.kscience.dataforge.meta
import space.kscience.dataforge.meta.transformations.MetaConverter
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.values.*
import kotlin.properties.ReadOnlyProperty
/* Meta delegates */
public fun MetaProvider.node(key: Name? = null): ReadOnlyProperty<Any?, Meta?> = ReadOnlyProperty { _, property ->
getMeta(key ?: property.name.asName())
}
public fun <T> MetaProvider.node(
key: Name? = null,
converter: MetaConverter<T>
): ReadOnlyProperty<Any?, T?> = ReadOnlyProperty { _, property ->
getMeta(key ?: property.name.asName())?.let { converter.metaToObject(it) }
}
/**
* A property delegate that uses custom key
*/
public fun MetaProvider.value(key: Name? = null): ReadOnlyProperty<Any?, Value?> = ReadOnlyProperty { _, property ->
getMeta(key ?: property.name.asName())?.value
}
public fun <R> MetaProvider.value(
key: Name? = null,
reader: (Value?) -> R
): ReadOnlyProperty<Any?, R> = ReadOnlyProperty { _, property ->
reader(getMeta(key ?: property.name.asName())?.value)
}
//TODO add caching for sealed nodes
/* Read-only delegates for [Meta] */
public fun MetaProvider.string(key: Name? = null): ReadOnlyProperty<Any?, String?> = value(key) { it?.string }
public fun MetaProvider.boolean(key: Name? = null): ReadOnlyProperty<Any?, Boolean?> = value(key) { it?.boolean }
public fun MetaProvider.number(key: Name? = null): ReadOnlyProperty<Any?, Number?> = value(key) { it?.numberOrNull }
public fun MetaProvider.double(key: Name? = null): ReadOnlyProperty<Any?, Double?> = value(key) { it?.double }
public fun MetaProvider.float(key: Name? = null): ReadOnlyProperty<Any?, Float?> = value(key) { it?.float }
public fun MetaProvider.int(key: Name? = null): ReadOnlyProperty<Any?, Int?> = value(key) { it?.int }
public fun MetaProvider.long(key: Name? = null): ReadOnlyProperty<Any?, Long?> = value(key) { it?.long }
public fun MetaProvider.string(default: String, key: Name? = null): ReadOnlyProperty<Any?, String> =
value(key) { it?.string ?: default }
public fun MetaProvider.boolean(default: Boolean, key: Name? = null): ReadOnlyProperty<Any?, Boolean> =
value(key) { it?.boolean ?: default }
public fun MetaProvider.number(default: Number, key: Name? = null): ReadOnlyProperty<Any?, Number> =
value(key) { it?.numberOrNull ?: default }
public fun MetaProvider.double(default: Double, key: Name? = null): ReadOnlyProperty<Any?, Double> =
value(key) { it?.double ?: default }
public fun MetaProvider.float(default: Float, key: Name? = null): ReadOnlyProperty<Any?, Float> =
value(key) { it?.float ?: default }
public fun MetaProvider.int(default: Int, key: Name? = null): ReadOnlyProperty<Any?, Int> =
value(key) { it?.int ?: default }
public fun MetaProvider.long(default: Long, key: Name? = null): ReadOnlyProperty<Any?, Long> =
value(key) { it?.long ?: default }
public inline fun <reified E : Enum<E>> MetaProvider.enum(default: E, key: Name? = null): ReadOnlyProperty<Any?, E> =
value<E>(key) { it?.enum<E>() ?: default }
public fun MetaProvider.string(key: Name? = null, default: () -> String): ReadOnlyProperty<Any?, String> =
value(key) { it?.string ?: default() }
public fun MetaProvider.boolean(key: Name? = null, default: () -> Boolean): ReadOnlyProperty<Any?, Boolean> =
value(key) { it?.boolean ?: default() }
public fun MetaProvider.number(key: Name? = null, default: () -> Number): ReadOnlyProperty<Any?, Number> =
value(key) { it?.numberOrNull ?: default() }

View File

@ -1,90 +0,0 @@
package space.kscience.dataforge.meta
import kotlinx.serialization.Serializable
import space.kscience.dataforge.values.*
/**
* A member of the meta tree. Could be represented as one of following:
* * a [MetaItemValue] (leaf)
* * a [MetaItemNode] (node)
*/
@Serializable(MetaItemSerializer::class)
public sealed class TypedMetaItem<out M : Meta>() {
abstract override fun equals(other: Any?): Boolean
abstract override fun hashCode(): Int
public companion object {
public fun of(arg: Any?): MetaItem {
return when (arg) {
null -> Null.asMetaItem()
is MetaItem -> arg
is Meta -> arg.asMetaItem()
is ItemProvider -> arg.rootItem ?: Null.asMetaItem()
else -> Value.of(arg).asMetaItem()
}
}
}
}
public typealias MetaItem = TypedMetaItem<*>
@Serializable(MetaItemSerializer::class)
public class MetaItemValue(public val value: Value) : TypedMetaItem<Nothing>() {
override fun toString(): String = value.toString()
override fun equals(other: Any?): Boolean {
return this.value == (other as? MetaItemValue)?.value
}
override fun hashCode(): Int {
return value.hashCode()
}
}
@Serializable(MetaItemSerializer::class)
public class MetaItemNode<M : Meta>(public val node: M) : TypedMetaItem<M>() {
//Fixing serializer for node could cause class cast problems, but it should not since Meta descendants are not serializable
override fun toString(): String = node.toString()
override fun equals(other: Any?): Boolean = node == (other as? MetaItemNode<*>)?.node
override fun hashCode(): Int = node.hashCode()
}
public fun Value.asMetaItem(): MetaItemValue = MetaItemValue(this)
public fun <M : Meta> M.asMetaItem(): MetaItemNode<M> = MetaItemNode(this)
/**
* Unsafe methods to access values and nodes directly from [TypedMetaItem]
*/
public val MetaItem?.value: Value?
get() = (this as? MetaItemValue)?.value
?: (this?.node?.get(Meta.VALUE_KEY) as? MetaItemValue)?.value
public val MetaItem?.string: String? get() = value?.string
public val MetaItem?.boolean: Boolean? get() = value?.boolean
public val MetaItem?.number: Number? get() = value?.numberOrNull
public val MetaItem?.double: Double? get() = number?.toDouble()
public val MetaItem?.float: Float? get() = number?.toFloat()
public val MetaItem?.int: Int? get() = number?.toInt()
public val MetaItem?.long: Long? get() = number?.toLong()
public val MetaItem?.short: Short? get() = number?.toShort()
public inline fun <reified E : Enum<E>> MetaItem?.enum(): E? =
if (this is MetaItemValue && this.value is EnumValue<*>) {
this.value.value as E
} else {
string?.let { enumValueOf<E>(it) }
}
public val MetaItem.stringList: List<String>? get() = value?.list?.map { it.string }
public val <M : Meta> TypedMetaItem<M>?.node: M?
get() = when (this) {
null -> null
is MetaItemValue -> null//error("Trying to interpret value meta item as node item")
is MetaItemNode -> node
}

View File

@ -1,80 +1,48 @@
package space.kscience.dataforge.meta
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.InternalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.MapSerializer
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.JsonDecoder
import kotlinx.serialization.json.JsonEncoder
import kotlinx.serialization.json.JsonObject
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.NameTokenSerializer
import space.kscience.dataforge.values.ValueSerializer
public object MetaItemSerializer : KSerializer<MetaItem> {
/**
* Serialized for [Meta]
*/
public object MetaSerializer : KSerializer<Meta> {
private val genericMetaSerializer = SealedMeta.serializer()
@OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class)
override val descriptor: SerialDescriptor = buildSerialDescriptor("MetaItem", PolymorphicKind.SEALED) {
element<Boolean>("isNode")
element("value", buildSerialDescriptor("MetaItem.value", SerialKind.CONTEXTUAL))
override val descriptor: SerialDescriptor = genericMetaSerializer.descriptor
override fun deserialize(decoder: Decoder): Meta = if (decoder is JsonDecoder) {
decoder.decodeJsonElement().toMeta()
} else {
genericMetaSerializer.deserialize(decoder)
}
override fun deserialize(decoder: Decoder): MetaItem {
decoder.decodeStructure(descriptor) {
//Force strict serialization order
require(decodeElementIndex(descriptor) == 0) { "Node flag must be first item serialized" }
val isNode = decodeBooleanElement(descriptor, 0)
require(decodeElementIndex(descriptor) == 1) { "Missing MetaItem content" }
val item = if (isNode) {
decodeSerializableElement(descriptor,1, MetaSerializer).asMetaItem()
} else {
decodeSerializableElement(descriptor,1, ValueSerializer).asMetaItem()
}
require(decodeElementIndex(descriptor) == CompositeDecoder.DECODE_DONE){"Serialized MetaItem contains additional fields"}
return item
}
}
override fun serialize(encoder: Encoder, value: MetaItem) {
encoder.encodeStructure(descriptor) {
encodeBooleanElement(descriptor, 0, value is MetaItemNode)
when (value) {
is MetaItemValue -> encodeSerializableElement(descriptor, 1, ValueSerializer, value.value)
is MetaItemNode -> encodeSerializableElement(descriptor, 1, MetaSerializer, value.node)
}
override fun serialize(encoder: Encoder, value: Meta) {
if (encoder is JsonEncoder) {
encoder.encodeJsonElement(value.toJson())
} else {
genericMetaSerializer.serialize(encoder, value.seal())
}
}
}
/**
* Serialized for meta
* A serializer for [MutableMeta]
*/
public object MetaSerializer : KSerializer<Meta> {
public object MutableMetaSerializer : KSerializer<MutableMeta> {
private val mapSerializer: KSerializer<Map<NameToken, TypedMetaItem<Meta>>> = MapSerializer(
NameTokenSerializer,
MetaItemSerializer//MetaItem.serializer(MetaSerializer)
)
override val descriptor: SerialDescriptor = MetaSerializer.descriptor
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Meta")
override fun deserialize(decoder: Decoder): Meta {
return if (decoder is JsonDecoder) {
JsonObject.serializer().deserialize(decoder).toMeta()
} else {
object : MetaBase() {
override val items: Map<NameToken, MetaItem> = mapSerializer.deserialize(decoder)
}
}
override fun deserialize(decoder: Decoder): MutableMeta {
val meta = decoder.decodeSerializableValue(MetaSerializer)
return (meta as? MutableMeta) ?: meta.toMutableMeta()
}
override fun serialize(encoder: Encoder, value: Meta) {
if (encoder is JsonEncoder) {
JsonObject.serializer().serialize(encoder, value.toJson())
} else {
mapSerializer.serialize(encoder, value.items)
}
override fun serialize(encoder: Encoder, value: MutableMeta) {
encoder.encodeSerializableValue(MetaSerializer, value)
}
}

View File

@ -1,194 +0,0 @@
package space.kscience.dataforge.meta
import space.kscience.dataforge.meta.transformations.MetaConverter
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.values.*
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
/* Read-write delegates */
public typealias MutableItemDelegate = ReadWriteProperty<Any?, MetaItem?>
public fun MutableItemProvider.item(key: Name? = null): MutableItemDelegate = object : MutableItemDelegate {
override fun getValue(thisRef: Any?, property: KProperty<*>): MetaItem? {
return get(key ?: property.name.asName())
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: MetaItem?) {
val name = key ?: property.name.asName()
set(name, value)
}
}
/* Mutable converters */
/**
* A type converter for a mutable [TypedMetaItem] delegate
*/
public fun <R : Any> MutableItemDelegate.convert(
converter: MetaConverter<R>,
): ReadWriteProperty<Any?, R?> = object : ReadWriteProperty<Any?, R?> {
override fun getValue(thisRef: Any?, property: KProperty<*>): R? =
this@convert.getValue(thisRef, property)?.let(converter::itemToObject)
override fun setValue(thisRef: Any?, property: KProperty<*>, value: R?) {
val item = value?.let(converter::objectToMetaItem)
this@convert.setValue(thisRef, property, item)
}
}
public fun <R : Any> MutableItemDelegate.convert(
converter: MetaConverter<R>,
default: () -> R,
): ReadWriteProperty<Any?, R> = object : ReadWriteProperty<Any?, R> {
override fun getValue(thisRef: Any?, property: KProperty<*>): R =
this@convert.getValue(thisRef, property)?.let(converter::itemToObject) ?: default()
override fun setValue(thisRef: Any?, property: KProperty<*>, value: R) {
val item = value.let(converter::objectToMetaItem)
this@convert.setValue(thisRef, property, item)
}
}
public fun <R> MutableItemDelegate.convert(
reader: (MetaItem?) -> R,
writer: (R) -> MetaItem?,
): ReadWriteProperty<Any?, R> = object : ReadWriteProperty<Any?, R> {
override fun getValue(thisRef: Any?, property: KProperty<*>): R =
this@convert.getValue(thisRef, property).let(reader)
override fun setValue(thisRef: Any?, property: KProperty<*>, value: R) {
val item = value?.let(writer)
this@convert.setValue(thisRef, property, item)
}
}
/* Read-write delegates for [MutableItemProvider] */
/**
* A property delegate that uses custom key
*/
public fun MutableItemProvider.value(key: Name? = null): ReadWriteProperty<Any?, Value?> =
item(key).convert(MetaConverter.value)
public fun MutableItemProvider.string(key: Name? = null): ReadWriteProperty<Any?, String?> =
item(key).convert(MetaConverter.string)
public fun MutableItemProvider.boolean(key: Name? = null): ReadWriteProperty<Any?, Boolean?> =
item(key).convert(MetaConverter.boolean)
public fun MutableItemProvider.number(key: Name? = null): ReadWriteProperty<Any?, Number?> =
item(key).convert(MetaConverter.number)
public fun MutableItemProvider.string(default: String, key: Name? = null): ReadWriteProperty<Any?, String> =
item(key).convert(MetaConverter.string) { default }
public fun MutableItemProvider.boolean(default: Boolean, key: Name? = null): ReadWriteProperty<Any?, Boolean> =
item(key).convert(MetaConverter.boolean) { default }
public fun MutableItemProvider.number(default: Number, key: Name? = null): ReadWriteProperty<Any?, Number> =
item(key).convert(MetaConverter.number) { default }
public fun MutableItemProvider.value(key: Name? = null, default: () -> Value): ReadWriteProperty<Any?, Value> =
item(key).convert(MetaConverter.value, default)
public fun MutableItemProvider.string(key: Name? = null, default: () -> String): ReadWriteProperty<Any?, String> =
item(key).convert(MetaConverter.string, default)
public fun MutableItemProvider.boolean(key: Name? = null, default: () -> Boolean): ReadWriteProperty<Any?, Boolean> =
item(key).convert(MetaConverter.boolean, default)
public fun MutableItemProvider.number(key: Name? = null, default: () -> Number): ReadWriteProperty<Any?, Number> =
item(key).convert(MetaConverter.number, default)
public inline fun <reified E : Enum<E>> MutableItemProvider.enum(
default: E,
key: Name? = null,
): ReadWriteProperty<Any?, E> =
item(key).convert(MetaConverter.enum()) { default }
public fun MutableItemProvider.node(key: Name? = null): ReadWriteProperty<Any?, Meta?> = item(key).convert(
reader = { it.node },
writer = { it?.asMetaItem() }
)
public inline fun <reified M : MutableMeta<M>> M.node(key: Name? = null): ReadWriteProperty<Any?, M?> =
item(key).convert(reader = { it?.let { it.node as M } }, writer = { it?.let { MetaItemNode(it) } })
/* Number delegates */
public fun MutableItemProvider.int(key: Name? = null): ReadWriteProperty<Any?, Int?> =
item(key).convert(MetaConverter.int)
public fun MutableItemProvider.double(key: Name? = null): ReadWriteProperty<Any?, Double?> =
item(key).convert(MetaConverter.double)
public fun MutableItemProvider.long(key: Name? = null): ReadWriteProperty<Any?, Long?> =
item(key).convert(MetaConverter.long)
public fun MutableItemProvider.float(key: Name? = null): ReadWriteProperty<Any?, Float?> =
item(key).convert(MetaConverter.float)
/* Safe number delegates*/
public fun MutableItemProvider.int(default: Int, key: Name? = null): ReadWriteProperty<Any?, Int> =
item(key).convert(MetaConverter.int) { default }
public fun MutableItemProvider.double(default: Double, key: Name? = null): ReadWriteProperty<Any?, Double> =
item(key).convert(MetaConverter.double) { default }
public fun MutableItemProvider.long(default: Long, key: Name? = null): ReadWriteProperty<Any?, Long> =
item(key).convert(MetaConverter.long) { default }
public fun MutableItemProvider.float(default: Float, key: Name? = null): ReadWriteProperty<Any?, Float> =
item(key).convert(MetaConverter.float) { default }
/* Extra delegates for special cases */
public fun MutableItemProvider.stringList(
vararg default: String,
key: Name? = null,
): ReadWriteProperty<Any?, List<String>> = item(key).convert(
reader = { it?.stringList ?: listOf(*default) },
writer = { it.map { str -> str.asValue() }.asValue().asMetaItem() }
)
public fun MutableItemProvider.stringList(
key: Name? = null,
): ReadWriteProperty<Any?, List<String>?> = item(key).convert(
reader = { it?.stringList },
writer = { it?.map { str -> str.asValue() }?.asValue()?.asMetaItem() }
)
public fun MutableItemProvider.numberList(
vararg default: Number,
key: Name? = null,
): ReadWriteProperty<Any?, List<Number>> = item(key).convert(
reader = { it?.value?.list?.map { value -> value.numberOrNull ?: Double.NaN } ?: listOf(*default) },
writer = { it.map { num -> num.asValue() }.asValue().asMetaItem() }
)
/* A special delegate for double arrays */
public fun MutableItemProvider.doubleArray(
vararg default: Double,
key: Name? = null,
): ReadWriteProperty<Any?, DoubleArray> = item(key).convert(
reader = { it?.value?.doubleArray ?: doubleArrayOf(*default) },
writer = { DoubleArrayValue(it).asMetaItem() }
)
public fun <T> MutableItemProvider.listValue(
key: Name? = null,
writer: (T) -> Value = { Value.of(it) },
reader: (Value) -> T,
): ReadWriteProperty<Any?, List<T>?> = item(key).convert(MetaConverter.valueList(writer, reader))

View File

@ -1,129 +0,0 @@
package space.kscience.dataforge.meta
import space.kscience.dataforge.names.*
import space.kscience.dataforge.values.Value
public interface MutableItemProvider : ItemProvider {
public fun setItem(name: Name, item: MetaItem?)
}
public operator fun MutableItemProvider.set(name: Name, item: MetaItem?): Unit = setItem(name, item)
public operator fun MutableItemProvider.set(name: Name, value: Value?): Unit = set(name, value?.asMetaItem())
public operator fun MutableItemProvider.set(name: Name, meta: Meta?): Unit = set(name, meta?.asMetaItem())
public operator fun MutableItemProvider.set(key: String, item: MetaItem?): Unit = set(key.toName(), item)
public operator fun MutableItemProvider.set(key: String, meta: Meta?): Unit = set(key, meta?.asMetaItem())
@Suppress("NOTHING_TO_INLINE")
public inline fun MutableItemProvider.remove(name: Name): Unit = setItem(name, null)
@Suppress("NOTHING_TO_INLINE")
public inline fun MutableItemProvider.remove(name: String): Unit = remove(name.toName())
/**
* Universal unsafe set method
*/
public operator fun MutableItemProvider.set(name: Name, value: Any?) {
when (value) {
null -> remove(name)
else -> set(name, MetaItem.of(value))
}
}
public operator fun MutableItemProvider.set(name: NameToken, value: Any?): Unit =
set(name.asName(), value)
public operator fun MutableItemProvider.set(key: String, value: Any?): Unit =
set(key.toName(), value)
public operator fun MutableItemProvider.set(key: String, index: String, value: Any?): Unit =
set(key.toName().withIndex(index), value)
/* Same name siblings generation */
public fun MutableItemProvider.setIndexedItems(
name: Name,
items: Iterable<MetaItem>,
indexFactory: (MetaItem, index: Int) -> String = { _, index -> index.toString() },
) {
val tokens = name.tokens.toMutableList()
val last = tokens.last()
items.forEachIndexed { index, meta ->
val indexedToken = NameToken(last.body, last.index + indexFactory(meta, index))
tokens[tokens.lastIndex] = indexedToken
set(Name(tokens), meta)
}
}
public fun MutableItemProvider.setIndexed(
name: Name,
metas: Iterable<Meta>,
indexFactory: (Meta, index: Int) -> String = { _, index -> index.toString() },
) {
setIndexedItems(name, metas.map { MetaItemNode(it) }) { item, index -> indexFactory(item.node!!, index) }
}
public operator fun MutableItemProvider.set(name: Name, metas: Iterable<Meta>): Unit = setIndexed(name, metas)
public operator fun MutableItemProvider.set(name: String, metas: Iterable<Meta>): Unit =
setIndexed(name.toName(), metas)
/**
* Get a [MutableItemProvider] referencing a child node
*/
public fun MutableItemProvider.getChild(childName: Name): MutableItemProvider {
fun createProvider() = object : MutableItemProvider {
override fun setItem(name: Name, item: MetaItem?) {
this@getChild.setItem(childName + name, item)
}
override fun getItem(name: Name): MetaItem? = this@getChild.getItem(childName + name)
}
return when {
childName.isEmpty() -> this
this is MutableMeta<*> -> {
get(childName).node ?: createProvider()
}
else -> {
createProvider()
}
}
}
public fun MutableItemProvider.getChild(childName: String): MutableItemProvider = getChild(childName.toName())
/**
* Update existing mutable node with another node. The rules are following:
* * value replaces anything
* * node updates node and replaces anything but node
* * node list updates node list if number of nodes in the list is the same and replaces anything otherwise
*/
public fun MutableItemProvider.update(meta: Meta) {
meta.valueSequence().forEach { (name, value) -> set(name, value) }
}
/**
* Edit a provider child at given name location
*/
public fun MutableItemProvider.editChild(name: Name, builder: MutableItemProvider.() -> Unit): MutableItemProvider =
getChild(name).apply(builder)
/**
* Create a mutable item provider that uses given provider for default values if those are not found in this provider.
* Changes are propagated only to this provider.
*/
public fun MutableItemProvider.withDefault(default: ItemProvider?): MutableItemProvider =
if (default == null || (default is Meta && default.isEmpty())) {
//Optimize for use with empty default
this
} else object : MutableItemProvider {
override fun setItem(name: Name, item: MetaItem?) {
this@withDefault.setItem(name, item)
}
override fun getItem(name: Name): MetaItem? = this@withDefault.getItem(name) ?: default.getItem(name)
}

View File

@ -1,66 +1,365 @@
package space.kscience.dataforge.meta
import kotlinx.serialization.Serializable
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.*
import space.kscience.dataforge.values.EnumValue
import space.kscience.dataforge.values.MutableValueProvider
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.asValue
import kotlin.js.JsName
import kotlin.jvm.Synchronized
public interface MutableMeta<out M : MutableMeta<M>> : TypedMeta<M>, MutableItemProvider {
override val items: Map<NameToken, TypedMetaItem<M>>
/**
* Mark a meta builder
*/
@DslMarker
public annotation class MetaBuilder
/**
* A generic interface that gives access to getting and setting meta notes and values
*/
public interface MutableMetaProvider : MetaProvider, MutableValueProvider {
override fun getMeta(name: Name): MutableMeta?
public fun setMeta(name: Name, node: Meta?)
override fun setValue(name: Name, value: Value?) {
getMeta(name)?.value = value
}
}
/**
* A mutable meta node with attachable change listener.
*
* Changes in Meta are not thread safe.
* Mutable variant of [Meta]
* TODO documentation
*/
public abstract class AbstractMutableMeta<M : MutableMeta<M>> : AbstractTypedMeta<M>(), MutableMeta<M> {
protected val children: MutableMap<NameToken, TypedMetaItem<M>> = LinkedHashMap()
@Serializable(MutableMetaSerializer::class)
@MetaBuilder
public interface MutableMeta : Meta, MutableMetaProvider {
override val items: Map<NameToken, TypedMetaItem<M>>
get() = children
override val items: Map<NameToken, MutableMeta>
//protected abstract fun itemChanged(name: Name, oldItem: MetaItem<*>?, newItem: MetaItem<*>?)
/**
* Get or set value of this node
*/
override var value: Value?
protected open fun replaceItem(key: NameToken, oldItem: TypedMetaItem<M>?, newItem: TypedMetaItem<M>?) {
if (newItem == null) {
children.remove(key)
override fun getMeta(name: Name): MutableMeta? {
tailrec fun MutableMeta.find(name: Name): MutableMeta? = if (name.isEmpty()) {
this
} else {
children[key] = newItem
items[name.firstOrNull()!!]?.find(name.cutFirst())
}
//itemChanged(key.asName(), oldItem, newItem)
return find(name)
}
private fun wrapItem(item: MetaItem?): TypedMetaItem<M>? = when (item) {
null -> null
is MetaItemValue -> item
is MetaItemNode -> MetaItemNode(wrapNode(item.node))
override fun setValue(name: Name, value: Value?) {
getOrCreate(name).value = value
}
/**
* Transform given meta to node type of this meta tree
* Get existing node or create a new one
*/
protected abstract fun wrapNode(meta: Meta): M
public fun getOrCreate(name: Name): MutableMeta
//TODO to be moved to extensions with multi-receivers
public infix fun Name.put(value: Value?) {
setValue(this, value)
}
public infix fun Name.put(string: String) {
setValue(this, string.asValue())
}
public infix fun Name.put(number: Number) {
setValue(this, number.asValue())
}
public infix fun Name.put(boolean: Boolean) {
setValue(this, boolean.asValue())
}
public infix fun Name.put(enum: Enum<*>) {
setValue(this, EnumValue(enum))
}
public infix fun Name.putIndexed(iterable: Iterable<Meta>) {
setIndexed(this, iterable)
}
public infix fun Name.put(meta: Meta) {
setMeta(this, meta)
}
public infix fun Name.put(repr: MetaRepr) {
setMeta(this, repr.toMeta())
}
public infix fun Name.put(mutableMeta: MutableMeta.() -> Unit) {
setMeta(this, Meta(mutableMeta))
}
public infix fun String.put(meta: Meta) {
setMeta(Name.parse(this), meta)
}
public infix fun String.put(value: Value?) {
setValue(Name.parse(this), value)
}
public infix fun String.put(string: String) {
setValue(Name.parse(this), string.asValue())
}
public infix fun String.put(number: Number) {
setValue(Name.parse(this), number.asValue())
}
public infix fun String.put(boolean: Boolean) {
setValue(Name.parse(this), boolean.asValue())
}
public infix fun String.put(enum: Enum<*>) {
setValue(Name.parse(this), EnumValue(enum))
}
public infix fun String.put(array: DoubleArray) {
setValue(Name.parse(this), array.asValue())
}
public infix fun String.put(repr: MetaRepr) {
setMeta(Name.parse(this), repr.toMeta())
}
public infix fun String.putIndexed(iterable: Iterable<Meta>) {
setIndexed(Name.parse(this), iterable)
}
public infix fun String.put(builder: MutableMeta.() -> Unit) {
setMeta(Name.parse(this), MutableMeta(builder))
}
}
/**
* Set or replace node at given [name]
*/
public operator fun MutableMeta.set(name: Name, meta: Meta): Unit = setMeta(name, meta)
/**
* Set or replace value at given [name]
*/
public operator fun MutableMeta.set(name: Name, value: Value?): Unit = setValue(name, value)
public fun MutableMeta.getOrCreate(key: String): MutableMeta = getOrCreate(Name.parse(key))
public interface MutableTypedMeta<M : MutableTypedMeta<M>> : TypedMeta<M>, MutableMeta {
/**
* Create empty node
* Zero-copy attach or replace existing node. Node is used with any additional state, listeners, etc.
* In some cases it is possible to have the same node as a child to several others
*/
internal abstract fun empty(): M
@DFExperimental
public fun attach(name: Name, node: M)
override fun getMeta(name: Name): M?
override fun getOrCreate(name: Name): M
}
override fun setItem(name: Name, item: MetaItem?) {
public fun <M : MutableTypedMeta<M>> M.getOrCreate(key: String): M = getOrCreate(Name.parse(key))
public fun MutableMetaProvider.remove(name: Name) {
setMeta(name, null)
}
public fun MutableMetaProvider.remove(key: String) {
setMeta(Name.parse(key), null)
}
// node setters
public operator fun MutableMetaProvider.set(Key: NameToken, value: Meta): Unit = setMeta(Key.asName(), value)
public operator fun MutableMetaProvider.set(key: String, value: Meta): Unit = setMeta(Name.parse(key), value)
//value setters
public operator fun MutableMeta.set(name: NameToken, value: Value?): Unit = set(name.asName(), value)
public operator fun MutableMeta.set(key: String, value: Value?): Unit = set(Name.parse(key), value)
public operator fun MutableMeta.set(name: Name, value: String): Unit = set(name, value.asValue())
public operator fun MutableMeta.set(name: NameToken, value: String): Unit = set(name.asName(), value.asValue())
public operator fun MutableMeta.set(key: String, value: String): Unit = set(Name.parse(key), value.asValue())
public operator fun MutableMeta.set(name: Name, value: Boolean): Unit = set(name, value.asValue())
public operator fun MutableMeta.set(name: NameToken, value: Boolean): Unit = set(name.asName(), value.asValue())
public operator fun MutableMeta.set(key: String, value: Boolean): Unit = set(Name.parse(key), value.asValue())
public operator fun MutableMeta.set(name: Name, value: Number): Unit = set(name, value.asValue())
public operator fun MutableMeta.set(name: NameToken, value: Number): Unit = set(name.asName(), value.asValue())
public operator fun MutableMeta.set(key: String, value: Number): Unit = set(Name.parse(key), value.asValue())
public operator fun MutableMeta.set(name: Name, value: List<Value>): Unit = set(name, value.asValue())
public operator fun MutableMeta.set(name: NameToken, value: List<Value>): Unit = set(name.asName(), value.asValue())
public operator fun MutableMeta.set(key: String, value: List<Value>): Unit = set(Name.parse(key), value.asValue())
//public fun MutableMeta.set(key: String, index: String, value: Value?): Unit =
// set(key.toName().withIndex(index), value)
/* Same name siblings generation */
public fun MutableMeta.setIndexed(
name: Name,
metas: Iterable<Meta>,
indexFactory: (Meta, index: Int) -> String = { _, index -> index.toString() },
) {
val tokens = name.tokens.toMutableList()
val last = tokens.last()
metas.forEachIndexed { index, meta ->
val indexedToken = NameToken(last.body, (last.index ?: "") + indexFactory(meta, index))
tokens[tokens.lastIndex] = indexedToken
set(Name(tokens), meta)
}
}
public operator fun MutableMeta.set(name: Name, metas: Iterable<Meta>): Unit =
setIndexed(name, metas)
public operator fun MutableMeta.set(key: String, metas: Iterable<Meta>): Unit =
setIndexed(Name.parse(key), metas)
/**
* Update existing mutable node with another node. The rules are following:
* * value replaces anything
* * node updates node and replaces anything but node
* * node list updates node list if number of nodes in the list is the same and replaces anything otherwise
*/
public fun MutableMeta.update(meta: Meta) {
meta.valueSequence().forEach { (name, value) ->
set(name, value)
}
}
///**
// * Get child with given name or create a new one
// */
//public fun <M : MutableTypedMeta<M>> MutableTypedMeta<M>.getOrCreate(name: Name): M =
// get(name) ?: empty().also { attach(name, it) }
/**
* Edit node at [name]
*/
public fun <M : MutableTypedMeta<M>> MutableTypedMeta<M>.edit(name: Name, builder: M.() -> Unit): M =
getOrCreate(name).apply(builder)
/**
* Set a value at a given [name]. If node does not exist, create it.
*/
public operator fun <M : MutableTypedMeta<M>> MutableTypedMeta<M>.set(name: Name, value: Value?) {
edit(name) {
this.value = value
}
}
/**
* A general implementation of mutable [Meta] which implements both [MutableTypedMeta] and [ObservableMeta].
* The implementation uses blocking synchronization on mutation on JVM
*/
private class MutableMetaImpl(
value: Value?,
children: Map<NameToken, Meta> = emptyMap()
) : AbstractObservableMeta(), ObservableMutableMeta {
override var value = value
@Synchronized set(value) {
val oldValue = field
field = value
if (oldValue != value) {
invalidate(Name.EMPTY)
}
}
private val children: LinkedHashMap<NameToken, ObservableMutableMeta> =
LinkedHashMap(children.mapValues { (key, meta) ->
MutableMetaImpl(meta.value, meta.items).apply { adoptBy(this, key) }
})
override val items: Map<NameToken, ObservableMutableMeta> get() = children
private fun ObservableMeta.adoptBy(parent: MutableMetaImpl, key: NameToken) {
onChange(parent) { name ->
parent.invalidate(key + name)
}
}
@DFExperimental
override fun attach(name: Name, node: ObservableMutableMeta) {
when (name.length) {
0 -> error("Can't set a meta item for empty name")
0 -> error("Can't set a meta with empty name")
1 -> {
val token = name.firstOrNull()!!
val oldItem: TypedMetaItem<M>? = getItem(name)
replaceItem(token, oldItem, wrapItem(item))
replaceItem(name.first(), get(name), node)
}
else -> {
val token = name.firstOrNull()!!
//get existing or create new node. Query is ignored for new node
if (items[token] == null) {
replaceItem(token, null, MetaItemNode(empty()))
else -> get(name.cutLast())?.attach(name.lastOrNull()!!.asName(), node)
}
}
/**
* Create and attach empty node
*/
private fun createNode(name: Name): ObservableMutableMeta = when (name.length) {
0 -> throw IllegalArgumentException("Can't create a node with empty name")
1 -> {
val newNode = MutableMetaImpl(null)
children[name.first()] = newNode
newNode.adoptBy(this, name.first())
newNode
} //do not notify, no value changed
else -> getOrCreate(name.first().asName()).getOrCreate(name.cutFirst())
}
override fun getOrCreate(name: Name): ObservableMutableMeta =
if (name.isEmpty()) this else get(name) ?: createNode(name)
@Synchronized
private fun replaceItem(
key: NameToken,
oldItem: ObservableMutableMeta?,
newItem: ObservableMutableMeta?
) {
if (oldItem != newItem) {
if (newItem == null) {
//remove child and remove stale listener
children.remove(key)?.removeListener(this)
} else {
newItem.adoptBy(this, key)
children[key] = newItem
}
invalidate(key.asName())
}
}
private fun wrapItem(meta: Meta): MutableMetaImpl =
MutableMetaImpl(meta.value, meta.items.mapValuesTo(LinkedHashMap()) { wrapItem(it.value) })
override fun setMeta(name: Name, node: Meta?) {
val oldItem: ObservableMutableMeta? = get(name)
if (oldItem != node) {
when (name.length) {
0 -> error("Can't set a meta with empty name")
1 -> {
val token = name.firstOrNull()!!
replaceItem(token, oldItem, node?.let { wrapItem(node) })
}
else -> {
val token = name.firstOrNull()!!
//get existing or create new node. Index is ignored for new node
if (items[token] == null) {
replaceItem(token, null, MutableMetaImpl(null))
}
items[token]?.setMeta(name.cutFirst(), node)
}
items[token]?.node!!.set(name.cutFirst(), item)
}
invalidate(name)
}
}
}
@ -68,28 +367,94 @@ public abstract class AbstractMutableMeta<M : MutableMeta<M>> : AbstractTypedMet
/**
* Append the node with a same-name-sibling, automatically generating numerical index
*/
public fun MutableItemProvider.append(name: Name, value: Any?) {
public fun MutableMeta.append(name: Name, meta: Meta) {
require(!name.isEmpty()) { "Name could not be empty for append operation" }
val newIndex = name.lastOrNull()!!.index
if (newIndex != null) {
set(name, value)
set(name, meta)
} else {
val index = (getIndexed(name).keys.mapNotNull { it?.toIntOrNull() }.maxOrNull() ?: -1) + 1
set(name.withIndex(index.toString()), value)
set(name.withIndex(index.toString()), meta)
}
}
public fun MutableItemProvider.append(name: String, value: Any?): Unit = append(name.toName(), value)
public fun MutableMeta.append(key: String, meta: Meta): Unit = append(Name.parse(key), meta)
public fun MutableMeta.append(name: Name, value: Value): Unit = append(name, Meta(value))
public fun MutableMeta.append(key: String, value: Value): Unit = append(Name.parse(key), value)
///**
// * Apply existing node with given [builder] or create a new element with it.
// */
//@DFExperimental
//public fun MutableMeta.edit(name: Name, builder: MutableMeta.() -> Unit) {
// val item = when (val existingItem = get(name)) {
// null -> MutableMeta().also { set(name, it) }
// is MetaItemNode<MutableMeta> -> existingItem.node
// else -> error("Can't edit value meta item")
// }
// item.apply(builder)
//}
/**
* Apply existing node with given [builder] or create a new element with it.
* Create a mutable copy of this meta. The copy is created even if the Meta is already mutable
*/
@DFExperimental
public fun <M : AbstractMutableMeta<M>> M.edit(name: Name, builder: M.() -> Unit) {
val item = when (val existingItem = get(name)) {
null -> empty().also { set(name, it) }
is MetaItemNode<M> -> existingItem.node
else -> error("Can't edit value meta item")
}
item.apply(builder)
}
public fun Meta.toMutableMeta(): ObservableMutableMeta = MutableMetaImpl(value, items)
public fun Meta.asMutableMeta(): MutableMeta = (this as? MutableMeta) ?: toMutableMeta()
@Suppress("FunctionName")
@JsName("newMutableMeta")
public fun MutableMeta(): ObservableMutableMeta = MutableMetaImpl(null)
/**
* Build a [MutableMeta] using given transformation
*/
@Suppress("FunctionName")
public inline fun MutableMeta(builder: MutableMeta.() -> Unit = {}): ObservableMutableMeta =
MutableMeta().apply(builder)
/**
* Create a copy of this [Meta], optionally applying the given [block].
* The listeners of the original Config are not retained.
*/
public inline fun Meta.copy(block: MutableMeta.() -> Unit = {}): Meta =
toMutableMeta().apply(block)
private class MutableMetaWithDefault(
val source: MutableMeta, val default: Meta, val rootName: Name
) : MutableMeta by source {
override val items: Map<NameToken, MutableMeta>
get() {
val sourceKeys: Collection<NameToken> = source[rootName]?.items?.keys ?: emptyList()
val defaultKeys: Collection<NameToken> = default[rootName]?.items?.keys ?: emptyList()
//merging keys for primary and default node
return (sourceKeys + defaultKeys).associateWith {
MutableMetaWithDefault(source, default, rootName + it)
}
}
override var value: Value?
get() = source[rootName]?.value ?: default[rootName]?.value
set(value) {
source[rootName] = value
}
override fun getMeta(name: Name): MutableMeta = MutableMetaWithDefault(source, default, rootName + name)
override fun toString(): String = Meta.toString(this)
override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta)
override fun hashCode(): Int = Meta.hashCode(this)
}
/**
* Create a mutable item provider that uses given provider for default values if those are not found in this provider.
* Changes are propagated only to this provider.
*/
public fun MutableMeta.withDefault(default: Meta?): MutableMeta = if (default == null || default.isEmpty()) {
//Optimize for use with empty default
this
} else MutableMetaWithDefault(this, default, Name.EMPTY)

View File

@ -0,0 +1,171 @@
package space.kscience.dataforge.meta
import space.kscience.dataforge.meta.transformations.MetaConverter
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.values.*
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
/* Read-write delegates */
public fun MutableMetaProvider.node(key: Name? = null): ReadWriteProperty<Any?, Meta?> =
object : ReadWriteProperty<Any?, Meta?> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Meta? {
return getMeta(key ?: property.name.asName())
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Meta?) {
val name = key ?: property.name.asName()
setMeta(name, value)
}
}
public fun <T> MutableMetaProvider.node(key: Name? = null, converter: MetaConverter<T>): ReadWriteProperty<Any?, T?> =
object : ReadWriteProperty<Any?, T?> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T? {
return getMeta(key ?: property.name.asName())?.let { converter.metaToObject(it) }
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
val name = key ?: property.name.asName()
setMeta(name, value?.let { converter.objectToMeta(it) })
}
}
public fun MutableMetaProvider.value(key: Name? = null): ReadWriteProperty<Any?, Value?> =
object : ReadWriteProperty<Any?, Value?> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Value? =
getMeta(key ?: property.name.asName())?.value
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Value?) {
setValue(key ?: property.name.asName(), value)
}
}
public fun <T> MutableMetaProvider.value(
key: Name? = null,
writer: (T) -> Value? = { Value.of(it) },
reader: (Value?) -> T
): ReadWriteProperty<Any?, T> = object : ReadWriteProperty<Any?, T> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T =
reader(getMeta(key ?: property.name.asName())?.value)
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
setValue(key ?: property.name.asName(), writer(value))
}
}
/* Read-write delegates for [MutableItemProvider] */
public fun MutableMetaProvider.string(key: Name? = null): ReadWriteProperty<Any?, String?> =
value(key) { it?.string }
public fun MutableMetaProvider.boolean(key: Name? = null): ReadWriteProperty<Any?, Boolean?> =
value(key) { it?.boolean }
public fun MutableMetaProvider.number(key: Name? = null): ReadWriteProperty<Any?, Number?> =
value(key) { it?.number }
public fun MutableMetaProvider.string(default: String, key: Name? = null): ReadWriteProperty<Any?, String> =
value(key) { it?.string ?: default }
public fun MutableMetaProvider.boolean(default: Boolean, key: Name? = null): ReadWriteProperty<Any?, Boolean> =
value(key) { it?.boolean ?: default }
public fun MutableMetaProvider.number(default: Number, key: Name? = null): ReadWriteProperty<Any?, Number> =
value(key) { it?.number ?: default }
public fun MutableMetaProvider.string(key: Name? = null, default: () -> String): ReadWriteProperty<Any?, String> =
value(key) { it?.string ?: default() }
public fun MutableMetaProvider.boolean(key: Name? = null, default: () -> Boolean): ReadWriteProperty<Any?, Boolean> =
value(key) { it?.boolean ?: default() }
public fun MutableMetaProvider.number(key: Name? = null, default: () -> Number): ReadWriteProperty<Any?, Number> =
value(key) { it?.number ?: default() }
public inline fun <reified E : Enum<E>> MutableMetaProvider.enum(
default: E,
key: Name? = null,
): ReadWriteProperty<Any?, E> = value(key) { value -> value?.string?.let { enumValueOf<E>(it) } ?: default }
/* Number delegates */
public fun MutableMetaProvider.int(key: Name? = null): ReadWriteProperty<Any?, Int?> =
value(key) { it?.int }
public fun MutableMetaProvider.double(key: Name? = null): ReadWriteProperty<Any?, Double?> =
value(key) { it?.double }
public fun MutableMetaProvider.long(key: Name? = null): ReadWriteProperty<Any?, Long?> =
value(key) { it?.long }
public fun MutableMetaProvider.float(key: Name? = null): ReadWriteProperty<Any?, Float?> =
value(key) { it?.float }
/* Safe number delegates*/
public fun MutableMetaProvider.int(default: Int, key: Name? = null): ReadWriteProperty<Any?, Int> =
value(key) { it?.int ?: default }
public fun MutableMetaProvider.double(default: Double, key: Name? = null): ReadWriteProperty<Any?, Double> =
value(key) { it?.double ?: default }
public fun MutableMetaProvider.long(default: Long, key: Name? = null): ReadWriteProperty<Any?, Long> =
value(key) { it?.long ?: default }
public fun MutableMetaProvider.float(default: Float, key: Name? = null): ReadWriteProperty<Any?, Float> =
value(key) { it?.float ?: default }
/* Extra delegates for special cases */
public fun MutableMetaProvider.stringList(
vararg default: String,
key: Name? = null,
): ReadWriteProperty<Any?, List<String>> = value(
key,
writer = { list -> list.map { str -> str.asValue() }.asValue() },
reader = { it?.stringList ?: listOf(*default) },
)
public fun MutableMetaProvider.stringList(
key: Name? = null,
): ReadWriteProperty<Any?, List<String>?> = value(
key,
writer = { it -> it?.map { str -> str.asValue() }?.asValue() },
reader = { it?.stringList },
)
public fun MutableMetaProvider.numberList(
vararg default: Number,
key: Name? = null,
): ReadWriteProperty<Any?, List<Number>> = value(
key,
writer = { it.map { num -> num.asValue() }.asValue() },
reader = { it?.list?.map { value -> value.numberOrNull ?: Double.NaN } ?: listOf(*default) },
)
/* A special delegate for double arrays */
public fun MutableMetaProvider.doubleArray(
vararg default: Double,
key: Name? = null,
): ReadWriteProperty<Any?, DoubleArray> = value(
key,
writer = { DoubleArrayValue(it) },
reader = { it?.doubleArray ?: doubleArrayOf(*default) },
)
public fun <T> MutableMetaProvider.listValue(
key: Name? = null,
writer: (T) -> Value = { Value.of(it) },
reader: (Value) -> T,
): ReadWriteProperty<Any?, List<T>?> = value(
key,
writer = { it?.map(writer)?.asValue() },
reader = { it?.list?.map(reader) }
)

View File

@ -1,42 +0,0 @@
package space.kscience.dataforge.meta
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.startsWith
import space.kscience.dataforge.names.toName
import kotlin.reflect.KProperty1
internal data class ItemListener(
val owner: Any? = null,
val action: (name: Name, oldItem: MetaItem?, newItem: MetaItem?) -> Unit,
)
public interface ObservableItemProvider : ItemProvider {
public fun onChange(owner: Any?, action: (name: Name, oldItem: MetaItem?, newItem: MetaItem?) -> Unit)
public fun removeListener(owner: Any?)
}
public interface ItemPropertyProvider: ObservableItemProvider, MutableItemProvider
/**
* Use the value of the property in a [callBack].
* The callback is called once immediately after subscription to pass the initial value.
*
* Optional [owner] property is used for
*/
@DFExperimental
public fun <O : ObservableItemProvider, T> O.useProperty(
property: KProperty1<O, T>,
owner: Any? = null,
callBack: O.(T) -> Unit,
) {
//Pass initial value.
callBack(property.get(this))
onChange(owner) { name, oldItem, newItem ->
if (name.startsWith(property.name.toName()) && oldItem != newItem) {
callBack(property.get(this))
}
}
}

View File

@ -0,0 +1,90 @@
package space.kscience.dataforge.meta
import space.kscience.dataforge.names.*
import kotlin.jvm.Synchronized
import kotlin.reflect.KProperty1
internal data class MetaListener(
val owner: Any? = null,
val callback: Meta.(name: Name) -> Unit,
)
/**
* An item provider that could be observed and mutated
*/
public interface ObservableMeta : Meta {
/**
* Add change listener to this meta. Owner is declared to be able to remove listeners later. Listener without owner could not be removed
*/
public fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit)
/**
* Remove all listeners belonging to given owner
*/
public fun removeListener(owner: Any?)
/**
* Force-send invalidation signal for given name to all listeners
*/
public fun invalidate(name: Name)
}
/**
* A [Meta] which is both observable and mutable
*/
public interface ObservableMutableMeta : ObservableMeta, MutableMeta, MutableTypedMeta<ObservableMutableMeta> {
override fun getOrCreate(name: Name): ObservableMutableMeta
override fun getMeta(name: Name): ObservableMutableMeta? {
tailrec fun ObservableMutableMeta.find(name: Name): ObservableMutableMeta? = if (name.isEmpty()) {
this
} else {
items[name.firstOrNull()!!]?.find(name.cutFirst())
}
return find(name)
}
}
internal abstract class AbstractObservableMeta : ObservableMeta {
private val listeners = HashSet<MetaListener>()
override fun invalidate(name: Name) {
listeners.forEach { it.callback(this, name) }
}
@Synchronized
override fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit) {
listeners.add(MetaListener(owner, callback))
}
@Synchronized
override fun removeListener(owner: Any?) {
listeners.removeAll { it.owner === owner }
}
override fun toString(): String = Meta.toString(this)
override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta)
override fun hashCode(): Int = Meta.hashCode(this)
}
/**
* Use the value of the property in a [callBack].
* The callback is called once immediately after subscription to pass the initial value.
*
* Optional [owner] property is used for
*/
public fun <S : Scheme, T> S.useProperty(
property: KProperty1<S, T>,
owner: Any? = null,
callBack: S.(T) -> Unit,
) {
//Pass initial value.
callBack(property.get(this))
meta.onChange(owner) { name ->
if (name.startsWith(property.name.asName())) {
callBack(property.get(this@useProperty))
}
}
}

View File

@ -0,0 +1,82 @@
package space.kscience.dataforge.meta
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.*
import space.kscience.dataforge.values.Value
import kotlin.jvm.Synchronized
/**
* A class that takes [MutableMeta] provider and adds obsevability on top of that
*/
private class ObservableMetaWrapper(
val root: MutableMeta,
val absoluteName: Name,
val listeners: MutableSet<MetaListener>
) : ObservableMutableMeta {
override val items: Map<NameToken, ObservableMutableMeta>
get() = root.items.mapValues {
ObservableMetaWrapper(root, absoluteName + it.key, listeners)
}
override fun getMeta(name: Name): ObservableMutableMeta? =
root.getMeta(name)?.let { ObservableMetaWrapper(root, this.absoluteName + name, listeners) }
@Synchronized
override fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit) {
listeners.add(
MetaListener(Pair(owner, absoluteName)) { name ->
if (name.startsWith(absoluteName)) {
(this[absoluteName] ?: Meta.EMPTY).callback(name.removeHeadOrNull(absoluteName)!!)
}
}
)
}
override fun removeListener(owner: Any?) {
listeners.removeAll { it.owner === Pair(owner, absoluteName) }
}
override fun invalidate(name: Name) {
listeners.forEach { it.callback(this, name) }
}
override var value: Value?
get() = root.value
set(value) {
root.value = value
invalidate(Name.EMPTY)
}
override fun getOrCreate(name: Name): ObservableMutableMeta =
ObservableMetaWrapper(root, this.absoluteName + name, listeners)
override fun setMeta(name: Name, node: Meta?) {
val oldMeta = get(name)
root.setMeta(absoluteName + name, node)
if (oldMeta != node) {
invalidate(name)
}
}
override fun toMeta(): Meta = root[absoluteName]?.toMeta() ?: Meta.EMPTY
override fun toString(): String = Meta.toString(this)
override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta)
override fun hashCode(): Int = Meta.hashCode(this)
@DFExperimental
override fun attach(name: Name, node: ObservableMutableMeta) {
set(name, node)
node.onChange(this) { changeName ->
setMeta(name + changeName, node[changeName])
}
}
}
/**
* Cast this [MutableMeta] to [ObservableMutableMeta] or create an observable wrapper. Only changes made to the result
* are guaranteed to be observed.
*/
public fun MutableMeta.asObservable(): ObservableMutableMeta =
(this as? ObservableMutableMeta) ?: ObservableMetaWrapper(this, Name.EMPTY, hashSetOf())

View File

@ -1,132 +1,136 @@
package space.kscience.dataforge.meta
import space.kscience.dataforge.meta.descriptors.*
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.*
import space.kscience.dataforge.values.Value
import kotlin.jvm.Synchronized
/**
* A base for delegate-based or descriptor-based scheme. [Scheme] has an empty constructor to simplify usage from [Specification].
* Default item provider and [NodeDescriptor] are optional
* Default item provider and [MetaDescriptor] are optional
*/
public open class Scheme() : Described, MetaRepr, ItemPropertyProvider {
private var items: MutableItemProvider = Config()
private val listeners = HashSet<ItemListener>()
private var default: ItemProvider? = null
final override var descriptor: NodeDescriptor? = null
public open class Scheme : Described, MetaRepr, MutableMetaProvider, Configurable {
/**
* Add a listener to this scheme changes. If the inner provider is observable, then listening will be delegated to it.
* Otherwise, local listeners will be created.
* Meta to be mutated by this schme
*/
@Synchronized
override fun onChange(owner: Any?, action: (Name, MetaItem?, MetaItem?) -> Unit) {
(items as? ObservableItemProvider)?.onChange(owner, action)
?: run { listeners.add(ItemListener(owner, action)) }
}
private var targetMeta: MutableMeta = MutableMeta()
/**
* Remove all listeners belonging to given owner
* Default values provided by this scheme
*/
@Synchronized
override fun removeListener(owner: Any?) {
(items as? ObservableItemProvider)?.removeListener(owner)
?: listeners.removeAll { it.owner === owner }
}
private var defaultMeta: Meta? = null
final override val meta: ObservableMutableMeta = SchemeMeta(Name.EMPTY)
final override var descriptor: MetaDescriptor? = null
internal set
internal fun wrap(
items: MutableItemProvider,
default: ItemProvider? = null,
descriptor: NodeDescriptor? = null,
newMeta: MutableMeta,
preserveDefault: Boolean = false
) {
//use properties in the init block as default
this.default = this.items.withDefault(default)
//reset values, defaults are already saved
this.items = items
this.descriptor = descriptor
}
private fun getDefaultItem(name: Name): MetaItem? {
return default?.get(name) ?: descriptor?.get(name)?.defaultItem()
if (preserveDefault) {
defaultMeta = targetMeta.seal()
}
targetMeta = newMeta
}
/**
* Get a property with default
* Check if property with given [name] could be assigned to [meta]
*/
override fun getItem(name: Name): MetaItem? = items[name] ?: getDefaultItem(name)
/**
* Check if property with given [name] could be assigned to [item]
*/
public open fun validateItem(name: Name, item: MetaItem?): Boolean {
public open fun validate(name: Name, meta: Meta?): Boolean {
val descriptor = descriptor?.get(name)
return descriptor?.validateItem(item) ?: true
return descriptor?.validate(meta) ?: true
}
/**
* Set a configurable property
*/
override fun setItem(name: Name, item: MetaItem?) {
val oldItem = items[name]
if (validateItem(name, item)) {
items[name] = item
listeners.forEach { it.action(name, oldItem, item) }
override fun getMeta(name: Name): MutableMeta? = meta.getMeta(name)
override fun setMeta(name: Name, node: Meta?) {
if (validate(name, meta)) {
meta.setMeta(name, node)
} else {
error("Validation failed for property $name with value $item")
error("Validation failed for node $node at $name")
}
}
override fun setValue(name: Name, value: Value?) {
val valueDescriptor = descriptor?.get(name)
if (valueDescriptor?.validate(value) != false) {
meta.setValue(name, value)
} else error("Value $value is not validated by $valueDescriptor")
}
/**
* Provide a default layer which returns items from [default] and falls back to descriptor
* values if default value is unavailable.
* Values from [default] completely replace
*/
public open val defaultLayer: Meta
get() = object : MetaBase() {
override val items: Map<NameToken, MetaItem> = buildMap {
descriptor?.items?.forEach { (key, itemDescriptor) ->
val token = NameToken(key)
val name = token.asName()
val item = default?.get(name) ?: itemDescriptor.defaultItem()
if (item != null) {
put(token, item)
}
override fun toMeta(): Laminate = Laminate(meta, descriptor?.defaultNode)
private val listeners = HashSet<MetaListener>()
private inner class SchemeMeta(val pathName: Name) : ObservableMutableMeta {
override var value: Value?
get() = targetMeta[pathName]?.value
?: defaultMeta?.get(pathName)?.value
?: descriptor?.get(pathName)?.defaultValue
set(value) {
val oldValue = targetMeta[pathName]?.value
targetMeta[pathName] = value
if (oldValue != value) {
invalidate(Name.EMPTY)
}
}
override val items: Map<NameToken, ObservableMutableMeta>
get() {
val targetKeys = targetMeta[pathName]?.items?.keys ?: emptySet()
val defaultKeys = defaultMeta?.get(pathName)?.items?.keys ?: emptySet()
return (targetKeys + defaultKeys).associateWith { SchemeMeta(pathName + it) }
}
override fun invalidate(name: Name) {
listeners.forEach { it.callback(this@Scheme.meta, pathName + name) }
}
override fun toMeta(): Laminate = Laminate(items[Name.EMPTY].node, defaultLayer)
@Synchronized
override fun onChange(owner: Any?, callback: Meta.(name: Name) -> Unit) {
listeners.add(MetaListener(owner) { changedName ->
if (changedName.startsWith(pathName)) {
this@Scheme.meta.callback(changedName.removeHeadOrNull(pathName)!!)
}
})
}
@Synchronized
override fun removeListener(owner: Any?) {
listeners.removeAll { it.owner === owner }
}
override fun toString(): String = Meta.toString(this)
override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta)
override fun hashCode(): Int = Meta.hashCode(this)
override fun setMeta(name: Name, node: Meta?) {
targetMeta.setMeta(name, node)
invalidate(name)
}
override fun getOrCreate(name: Name): ObservableMutableMeta = SchemeMeta(pathName + name)
@DFExperimental
override fun attach(name: Name, node: ObservableMutableMeta) {
TODO("Not yet implemented")
}
}
}
/**
* The scheme is considered empty only if its root item does not exist.
*/
public fun Scheme.isEmpty(): Boolean = rootItem == null
/**
* Create a new empty [Scheme] object (including defaults) and inflate it around existing [MutableItemProvider].
* Items already present in the scheme are used as defaults.
*/
public fun <T : Scheme, S : Specification<T>> S.wrap(
items: MutableItemProvider,
default: ItemProvider? = null,
descriptor: NodeDescriptor? = null,
): T = empty().apply {
wrap(items, default, descriptor)
}
/**
* Relocate scheme target onto given [MutableItemProvider]. Old provider does not get updates anymore.
* Relocate scheme target onto given [MutableMeta]. Old provider does not get updates anymore.
* Current state of the scheme used as a default.
*/
public fun <T : Scheme> T.retarget(provider: MutableItemProvider): T = apply { wrap(provider) }
public fun <T : Scheme> T.retarget(provider: MutableMeta): T = apply {
wrap(provider, true)
}
/**
* A shortcut to edit a [Scheme] object in-place
@ -140,16 +144,22 @@ public open class SchemeSpec<out T : Scheme>(
private val builder: () -> T,
) : Specification<T>, Described {
override fun empty(): T = builder()
override fun read(source: Meta): T = builder().also {
it.wrap(MutableMeta().withDefault(source))
}
override fun read(items: ItemProvider): T = wrap(Config(), items, descriptor)
override fun write(target: MutableItemProvider, defaultProvider: ItemProvider): T =
wrap(target, defaultProvider, descriptor)
override fun write(target: MutableMeta): T = empty().also {
it.wrap(target)
}
//TODO Generate descriptor from Scheme class
override val descriptor: NodeDescriptor? get() = null
override val descriptor: MetaDescriptor? get() = null
override fun empty(): T = builder().also {
it.descriptor = descriptor
}
@Suppress("OVERRIDE_BY_INLINE")
final override inline operator fun invoke(action: T.() -> Unit): T = empty().apply(action)
}

View File

@ -1,23 +1,47 @@
package space.kscience.dataforge.meta
import kotlinx.serialization.Serializable
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.asValue
/**
* The meta implementation which is guaranteed to be immutable.
*
* If the argument is possibly mutable node, it is copied on creation
*/
@Serializable
public class SealedMeta internal constructor(
override val items: Map<NameToken, TypedMetaItem<SealedMeta>>,
) : AbstractTypedMeta<SealedMeta>()
override val value: Value?,
override val items: Map<NameToken, SealedMeta>
) : TypedMeta<SealedMeta> {
override fun toString(): String = Meta.toString(this)
override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta)
override fun hashCode(): Int = Meta.hashCode(this)
}
/**
* 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.
*/
public fun Meta.seal(): SealedMeta = this as? SealedMeta ?: SealedMeta(items.mapValues { entry -> entry.value.seal() })
public fun Meta.seal(): SealedMeta = this as? SealedMeta ?: SealedMeta(
value,
items.mapValues { entry ->
entry.value.seal()
}
)
@Suppress("FunctionName")
public fun Meta(value: Value): SealedMeta = SealedMeta(value, emptyMap())
@Suppress("FunctionName")
public fun Meta(value: Number): SealedMeta = Meta(value.asValue())
@Suppress("FunctionName")
public fun Meta(value: String): SealedMeta = Meta(value.asValue())
@Suppress("FunctionName")
public fun Meta(value: Boolean): SealedMeta = Meta(value.asValue())
@Suppress("FunctionName")
public inline fun Meta(builder: MutableMeta.() -> Unit): SealedMeta =
MutableMeta(builder).seal()
@Suppress("UNCHECKED_CAST")
public fun MetaItem.seal(): TypedMetaItem<SealedMeta> = when (this) {
is MetaItemValue -> this
is MetaItemNode -> MetaItemNode(node.seal())
}

View File

@ -1,17 +1,18 @@
package space.kscience.dataforge.meta
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
public interface ReadOnlySpecification<out T : ItemProvider> {
public interface ReadOnlySpecification<out T : Any> {
/**
* Read generic read-only meta with this [Specification] producing instance of desired type.
* The source is not mutated even if it is in theory mutable
*/
public fun read(items: ItemProvider): T
public fun read(source: Meta): T
/**
* Generate an empty object
@ -30,44 +31,74 @@ public interface ReadOnlySpecification<out T : ItemProvider> {
* By convention [Scheme] companion should inherit this class
*
*/
public interface Specification<out T : MutableItemProvider>: ReadOnlySpecification<T> {
public interface Specification<out T : Any> : ReadOnlySpecification<T> {
/**
* Wrap [MutableItemProvider], using it as inner storage (changes to [Specification] are reflected on [MutableItemProvider]
* Wrap [MutableMeta], using it as inner storage (changes to [Specification] are reflected on [MutableMeta]
*/
public fun write(target: MutableItemProvider, defaultProvider: ItemProvider = ItemProvider.EMPTY): T
public fun write(target: MutableMeta): T
}
/**
* Update a [MutableItemProvider] using given specification
* Update a [MutableMeta] using given specification
*/
public fun <T : MutableItemProvider> MutableItemProvider.update(spec: Specification<T>, action: T.() -> Unit) {
spec.write(this).apply(action)
}
public fun <T : Any> MutableMeta.updateWith(
spec: Specification<T>,
action: T.() -> Unit
): T = spec.write(this).apply(action)
/**
* Update configuration using given specification
*/
public fun <C : MutableItemProvider, S : Specification<C>> Configurable.update(
spec: S,
action: C.() -> Unit,
) {
config.update(spec, action)
}
public fun <T : Any> Configurable.updateWith(
spec: Specification<T>,
action: T.() -> Unit,
): T = spec.write(meta).apply(action)
public fun <T : MutableItemProvider> TypedMetaItem<MutableMeta<*>>.withSpec(spec: Specification<T>): T? =
node?.let { spec.write(it) }
//
//public fun <M : MutableTypedMeta<M>> MutableMeta.withSpec(spec: Specification<M>): M? =
// spec.write(it)
public fun <T : Scheme> MutableItemProvider.spec(
/**
* A delegate that uses a [Specification] to wrap a child of this provider
*/
public fun <T : Scheme> MutableMeta.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 getChild(name).let { spec.write(it) }
return spec.write(getOrCreate(name))
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
val name = key ?: property.name.asName()
set(name, value.toMeta().asMetaItem())
set(name, value.toMeta())
}
}
public fun <T : Scheme> Scheme.spec(
spec: Specification<T>,
key: Name? = null,
): ReadWriteProperty<Any?, T> = meta.spec(spec, key)
/**
* A delegate that uses a [Specification] to wrap a list of child providers.
* If children are mutable, the changes in list elements are reflected on them.
* The list is a snapshot of children state, so change in structure is not reflected on its composition.
*/
@DFExperimental
public fun <T : Scheme> MutableMeta.listOfSpec(
spec: Specification<T>,
key: Name? = null,
): ReadWriteProperty<Any?, List<T>> = object : ReadWriteProperty<Any?, List<T>> {
override fun getValue(thisRef: Any?, property: KProperty<*>): List<T> {
val name = key ?: property.name.asName()
return getIndexed(name).values.map { spec.write(it as MutableMeta) }
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: List<T>) {
val name = key ?: property.name.asName()
setIndexed(name, value.map { it.toMeta() })
}
}

View File

@ -1,51 +0,0 @@
package space.kscience.dataforge.meta
import kotlinx.serialization.json.Json
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.toName
/**
* A meta node that ensures that all of its descendants has at least the same type
*/
public interface TypedMeta<out M : TypedMeta<M>> : Meta {
override val items: Map<NameToken, TypedMetaItem<M>>
@Suppress("UNCHECKED_CAST")
override fun getItem(name: Name): TypedMetaItem<M>? = super.getItem(name)?.let { it as TypedMetaItem<M> }
//Typed meta guarantees that all children have M type
}
/**
* The same as [Meta.get], but with specific node type
*/
public operator fun <M : TypedMeta<M>> M.get(name: Name): TypedMetaItem<M>? = getItem(name)
public operator fun <M : TypedMeta<M>> M.get(key: String): TypedMetaItem<M>? = this[key.toName()]
public operator fun <M : TypedMeta<M>> M.get(key: NameToken): TypedMetaItem<M>? = items[key]
/**
* Equals, hashcode and to string for any meta
*/
public abstract class MetaBase : Meta {
override fun equals(other: Any?): Boolean = if (other is Meta) {
Meta.equals(this, other)
} else {
false
}
override fun hashCode(): Int = items.hashCode()
override fun toString(): String = Json {
prettyPrint = true
useArrayPolymorphism = true
}.encodeToString(MetaSerializer, this)
}
/**
* Equals and hash code implementation for meta node
*/
public abstract class AbstractTypedMeta<M : TypedMeta<M>> : TypedMeta<M>, MetaBase()

View File

@ -4,21 +4,5 @@ package space.kscience.dataforge.meta.descriptors
* An object which provides its descriptor
*/
public interface Described {
public val descriptor: ItemDescriptor?
public companion object {
//public const val DESCRIPTOR_NODE: String = "@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) }
// }
// }
public val descriptor: MetaDescriptor?
}

View File

@ -1,47 +0,0 @@
package space.kscience.dataforge.meta.descriptors
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.names.NameToken
/**
* A [Meta] that is constructed from [NodeDescriptor]
*/
private class DescriptorMeta(val descriptor: NodeDescriptor) : Meta, MetaBase() {
override val items: Map<NameToken, MetaItem>
get() = buildMap {
descriptor.items.forEach { (token, descriptorItem) ->
val item = descriptorItem.defaultItem()
if (item != null) {
put(NameToken(token), item)
}
}
}
}
/**
* Generate a laminate representing default item set generated by this descriptor
*/
public fun NodeDescriptor.defaultMeta(): Laminate = Laminate(default, DescriptorMeta(this))
/**
* Build a default [MetaItemNode] from this node descriptor
*/
internal fun NodeDescriptor.defaultItem(): MetaItemNode<*> =
MetaItemNode(defaultMeta())
/**
* Build a default [MetaItemValue] from this descriptor
*/
internal fun ValueDescriptor.defaultItem(): MetaItemValue? {
return MetaItemValue(default ?: return null)
}
/**
* Build a default [TypedMetaItem] from descriptor.
*/
public fun ItemDescriptor.defaultItem(): MetaItem? {
return when (this) {
is ValueDescriptor -> defaultItem()
is NodeDescriptor -> defaultItem()
}
}

View File

@ -1,121 +0,0 @@
package space.kscience.dataforge.meta.descriptors
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.misc.DFBuilder
import space.kscience.dataforge.names.*
/**
* A common parent for [ValueDescriptor] and [NodeDescriptor]. Describes a single [TypedMetaItem] or a group of same-name-siblings.
*/
public sealed interface ItemDescriptor: MetaRepr {
/**
* True if same name siblings with this name are allowed
*/
public val multiple: Boolean
/**
* The item description text
*/
public val info: String?
/**
* True if the item is required
*/
public val required: Boolean
/**
* Additional attributes of an item. For example validation and widget parameters
*
* @return
*/
public val attributes: Meta?
/**
* An index field by which this node is identified in case of same name siblings construct
*/
public val indexKey: String
public companion object {
public const val DEFAULT_INDEX_KEY: String = "@index"
}
}
/**
* The builder for [ItemDescriptor]
*/
@DFBuilder
public sealed class ItemDescriptorBuilder(final override val config: Config) : Configurable, ItemDescriptor {
/**
* True if same name siblings with this name are allowed
*/
override var multiple: Boolean by config.boolean(false)
/**
* The item description text
*/
override var info: String? by config.string()
/**
* True if the item is required
*/
abstract override var required: Boolean
/**
* Additional attributes of an item. For example validation and widget parameters
*
* @return
*/
override var attributes: Config? by config.node()
/**
* An index field by which this node is identified in case of same name siblings construct
*/
override var indexKey: String by config.string(DEFAULT_INDEX_KEY)
public abstract fun build(): ItemDescriptor
override fun toMeta(): Meta = config
public companion object {
public const val DEFAULT_INDEX_KEY: String = "@index"
}
}
/**
* Configure attributes of the descriptor, creating an attributes node if needed.
*/
public inline fun ItemDescriptorBuilder.attributes(block: Config.() -> Unit) {
(attributes ?: Config().also { this.attributes = it }).apply(block)
}
/**
* Check if given item suits the descriptor
*/
public fun ItemDescriptor.validateItem(item: MetaItem?): Boolean {
if (item == null) return !required
return when (this) {
is ValueDescriptor -> isAllowedValue(item.value ?: return false)
is NodeDescriptor -> items.all { (key, d) ->
d.validateItem(item.node[key])
}
}
}
/**
* Get a descriptor item associated with given name or null if item for given name not provided
*/
public 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.firstOrNull()!!.toString()]?.get(name.cutFirst())
}
}
public operator fun ItemDescriptor.get(name: String): ItemDescriptor? = get(name.toName())

View File

@ -0,0 +1,106 @@
package space.kscience.dataforge.meta.descriptors
import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.set
import space.kscience.dataforge.names.*
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.ValueType
/**
* Restrictions on value in the node
*/
public enum class ValueRequirement {
/**
* No restrictions
*/
NONE,
/**
* The value is required
*/
REQUIRED,
/**
* The value must be null
*/
ABSENT
}
/**
* The descriptor for a meta
* @param info description text
* @param children child descriptors for this node
* @param multiple True if same name siblings with this name are allowed
* @param required The requirements for node content
* @param valueTypes list of allowed types for [Meta.value], null if all values are allowed.
* Empty list means that no value should be present in this node.
* @param indexKey An index field by which this node is identified in case of same name siblings construct
* @param defaultValue the default [Meta.value] for the node
* @param attributes additional attributes of this descriptor. For example validation and widget parameters
*/
@Serializable
public data class MetaDescriptor(
public val info: String? = null,
public val children: Map<String, MetaDescriptor> = emptyMap(),
public val multiple: Boolean = false,
public val valueRequirement: ValueRequirement = ValueRequirement.NONE,
public val valueTypes: List<ValueType>? = null,
public val indexKey: String = Meta.INDEX_KEY,
public val defaultValue: Value? = null,
public val attributes: Meta = Meta.EMPTY,
) {
/**
* A node constructed of default values for this descriptor and its children
*/
public val defaultNode: Meta by lazy {
Meta {
defaultValue?.let { defaultValue ->
this.value = defaultValue
}
children.forEach { (key, descriptor) ->
set(key, descriptor.defaultNode)
}
}
}
public companion object {
internal const val ALLOWED_VALUES_KEY = "allowedValues"
}
}
public val MetaDescriptor.required: Boolean get() = valueRequirement == ValueRequirement.REQUIRED || children.values.any { required }
public val MetaDescriptor.allowedValues: List<Value>? get() = attributes[MetaDescriptor.ALLOWED_VALUES_KEY]?.value?.list
public operator fun MetaDescriptor.get(name: Name): MetaDescriptor? = when (name.length) {
0 -> this
1 -> children[name.firstOrNull()!!.toString()]
else -> get(name.firstOrNull()!!.asName())?.get(name.cutFirst())
}
public operator fun MetaDescriptor.get(name: String): MetaDescriptor? = get(Name.parse(name))
public fun MetaDescriptor.validate(value: Value?): Boolean = if (value == null) {
valueRequirement != ValueRequirement.REQUIRED
} else {
if (valueRequirement == ValueRequirement.ABSENT) false
else {
(valueTypes == null || value.type in valueTypes) && (allowedValues?.let { value in it } ?: true)
}
}
/**
* Check if given item suits the descriptor
*/
public fun MetaDescriptor.validate(item: Meta?): Boolean {
if (item == null) return !required
if (!validate(item.value)) return false
children.forEach { (key, childDescriptor) ->
if (!childDescriptor.validate(item[key])) return false
}
return true
}

View File

@ -0,0 +1,174 @@
package space.kscience.dataforge.meta.descriptors
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.cutFirst
import space.kscience.dataforge.names.first
import space.kscience.dataforge.names.length
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.ValueType
import space.kscience.dataforge.values.asValue
import kotlin.collections.set
public class MetaDescriptorBuilder internal constructor() {
public var info: String? = null
public var children: MutableMap<String, MetaDescriptorBuilder> = linkedMapOf()
public var multiple: Boolean = false
public var valueRequirement: ValueRequirement = ValueRequirement.NONE
public var type: List<ValueType>? = null
public fun type(primaryType: ValueType, vararg otherTypes: ValueType) {
type = listOf(primaryType, *otherTypes)
}
public var indexKey: String = Meta.INDEX_KEY
public var default: Value? = null
public fun default(value: Any?) {
default = Value.of(value)
}
public var attributes: MutableMeta = MutableMeta()
public inline fun attributes(block: MutableMeta.() -> Unit) {
attributes.apply(block)
}
public fun item(name: Name, block: MetaDescriptorBuilder.() -> Unit = {}): MetaDescriptorBuilder {
return when (name.length) {
0 -> apply(block)
1 -> {
val target = MetaDescriptorBuilder().apply(block)
children[name.first().body] = target
target
}
else -> {
children.getOrPut(name.first().body) { MetaDescriptorBuilder() }.item(name.cutFirst(), block)
}
}
}
public fun node(
name: Name,
descriptor: MetaDescriptor,
block: MetaDescriptorBuilder.() -> Unit = {}
): MetaDescriptorBuilder = when (name.length) {
0 -> error("Can't set descriptor to root")
1 -> {
val item = descriptor.toBuilder().apply {
valueRequirement = ValueRequirement.ABSENT
}.apply(block)
children[name.first().body] = item
item
}
else -> children.getOrPut(name.first().body) {
MetaDescriptorBuilder()
}.node(name.cutFirst(), descriptor, block)
}
public var allowedValues: List<Value>
get() = attributes[MetaDescriptor.ALLOWED_VALUES_KEY]?.value?.list ?: emptyList()
set(value) {
attributes[MetaDescriptor.ALLOWED_VALUES_KEY] = value
}
public fun allowedValues(vararg values: Any) {
allowedValues = values.map { Value.of(it) }
}
internal fun build(): MetaDescriptor = MetaDescriptor(
info = info,
children = children.mapValues { it.value.build() },
multiple = multiple,
valueRequirement = valueRequirement,
valueTypes = type,
indexKey = indexKey,
defaultValue = default,
attributes = attributes
)
}
public fun MetaDescriptorBuilder.item(name: String, block: MetaDescriptorBuilder.() -> Unit): MetaDescriptorBuilder =
item(Name.parse(name), block)
public fun MetaDescriptor(block: MetaDescriptorBuilder.() -> Unit): MetaDescriptor =
MetaDescriptorBuilder().apply(block).build()
/**
* Create and configure child value descriptor
*/
public fun MetaDescriptorBuilder.value(
name: Name,
type: ValueType,
vararg additionalTypes: ValueType,
block: MetaDescriptorBuilder.() -> Unit = {}
): MetaDescriptorBuilder = item(name) {
type(type, *additionalTypes)
block()
}
public fun MetaDescriptorBuilder.value(
name: String,
type: ValueType,
vararg additionalTypes: ValueType,
block: MetaDescriptorBuilder.() -> Unit = {}
): MetaDescriptorBuilder = value(Name.parse(name), type, additionalTypes = additionalTypes, block)
/**
* Create and configure child value descriptor
*/
public fun MetaDescriptorBuilder.node(
name: Name, block: MetaDescriptorBuilder.() -> Unit
): MetaDescriptorBuilder = item(name) {
valueRequirement = ValueRequirement.ABSENT
block()
}
public fun MetaDescriptorBuilder.node(name: String, block: MetaDescriptorBuilder.() -> Unit) {
node(Name.parse(name), block)
}
public fun MetaDescriptorBuilder.node(
key: String,
described: Described,
block: MetaDescriptorBuilder.() -> Unit = {},
) {
described.descriptor?.let {
node(Name.parse(key), it, block)
}
}
public fun MetaDescriptorBuilder.required() {
valueRequirement = ValueRequirement.REQUIRED
}
public inline fun <reified E : Enum<E>> MetaDescriptorBuilder.enum(
key: Name,
default: E?,
crossinline modifier: MetaDescriptorBuilder.() -> Unit = {},
): MetaDescriptorBuilder = value(key, ValueType.STRING) {
default?.let {
this.default = default.asValue()
}
allowedValues = enumValues<E>().map { it.asValue() }
modifier()
}
private fun MetaDescriptor.toBuilder(): MetaDescriptorBuilder = MetaDescriptorBuilder().apply {
info = this@toBuilder.info
children = this@toBuilder.children.mapValuesTo(LinkedHashMap()) { it.value.toBuilder() }
multiple = this@toBuilder.multiple
valueRequirement = this@toBuilder.valueRequirement
type = this@toBuilder.valueTypes
indexKey = this@toBuilder.indexKey
default = defaultValue
attributes = this@toBuilder.attributes.toMutableMeta()
}
/**
* Make a deep copy of this descriptor applying given transformation [block]
*/
public fun MetaDescriptor.copy(block: MetaDescriptorBuilder.() -> Unit = {}): MetaDescriptor =
toBuilder().apply(block).build()

View File

@ -1,191 +0,0 @@
package space.kscience.dataforge.meta.descriptors
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.misc.DFBuilder
import space.kscience.dataforge.names.*
/**
* Descriptor for meta node. Could contain additional information for viewing
* and editing.
*
* @author Alexander Nozik
*/
@DFBuilder
public sealed interface NodeDescriptor: ItemDescriptor {
/**
* True if the node is required
*
* @return
*/
override val required: Boolean
/**
* The default for this node. Null if there is no default.
*
* @return
*/
public val default: Config?
/**
* The map of children item descriptors (both nodes and values)
*/
public val items: Map<String, ItemDescriptor>
/**
* The map of children node descriptors
*/
public val nodes: Map<String, NodeDescriptor>
/**
* The list of children value descriptors
*/
public val values: Map<String, ValueDescriptor>
public companion object {
internal val ITEM_KEY: Name = "item".asName()
internal val IS_NODE_KEY: Name = "@isNode".asName()
//TODO infer descriptor from spec
}
}
@DFBuilder
public class NodeDescriptorBuilder(config: Config = Config()) : ItemDescriptorBuilder(config), NodeDescriptor {
init {
config[IS_NODE_KEY] = true
}
/**
* True if the node is required
*
* @return
*/
override var required: Boolean by config.boolean { default == null }
/**
* The default for this node. Null if there is no default.
*
* @return
*/
override var default: Config? by config.node()
/**
* The map of children item descriptors (both nodes and values)
*/
override val items: Map<String, ItemDescriptor>
get() = config.getIndexed(ITEM_KEY).entries.associate { (name, item) ->
if (name == null) error("Child item index should not be null")
val node = item.node ?: error("Node descriptor must be a node")
if (node[IS_NODE_KEY].boolean == true) {
name to NodeDescriptorBuilder(node as Config)
} else {
name to ValueDescriptorBuilder(node as Config)
}
}
/**
* The map of children node descriptors
*/
@Suppress("UNCHECKED_CAST")
override val nodes: Map<String, NodeDescriptor>
get() = config.getIndexed(ITEM_KEY).entries.filter {
it.value.node[IS_NODE_KEY].boolean == true
}.associate { (name, item) ->
if (name == null) error("Child node index should not be null")
val node = item.node ?: error("Node descriptor must be a node")
name to NodeDescriptorBuilder(node as Config)
}
/**
* The list of children value descriptors
*/
override val values: Map<String, ValueDescriptor>
get() = config.getIndexed(ITEM_KEY).entries.filter {
it.value.node[IS_NODE_KEY].boolean != true
}.associate { (name, item) ->
if (name == null) error("Child value index should not be null")
val node = item.node ?: error("Node descriptor must be a node")
name to ValueDescriptorBuilder(node as Config)
}
private fun buildNode(name: Name): NodeDescriptorBuilder {
return when (name.length) {
0 -> this
1 -> {
val token = NameToken(ITEM_KEY.toString(), name.toString())
val config: Config = config[token].node ?: Config().also {
it[IS_NODE_KEY] = true
config[token] = it
}
NodeDescriptorBuilder(config)
}
else -> buildNode(name.firstOrNull()?.asName()!!).buildNode(name.cutFirst())
}
}
/**
* Define a child item descriptor for this node
*/
private fun newItem(key: String, descriptor: ItemDescriptor) {
if (items.keys.contains(key)) error("The key $key already exists in descriptor")
val token = ITEM_KEY.withIndex(key)
config[token] = descriptor.toMeta()
}
public fun item(name: Name, descriptor: ItemDescriptor) {
buildNode(name.cutLast()).newItem(name.lastOrNull().toString(), descriptor)
}
public fun item(name: String, descriptor: ItemDescriptor) {
item(name.toName(), descriptor)
}
/**
* Create and configure a child node descriptor
*/
public fun node(name: Name, block: NodeDescriptorBuilder.() -> Unit) {
item(name, NodeDescriptorBuilder().apply(block))
}
public fun node(name: String, block: NodeDescriptorBuilder.() -> Unit) {
node(name.toName(), block)
}
/**
* Create and configure child value descriptor
*/
public fun value(name: Name, block: ValueDescriptorBuilder.() -> Unit) {
require(name.length >= 1) { "Name length for value descriptor must be non-empty" }
item(name, ValueDescriptorBuilder().apply(block))
}
public fun value(name: String, block: ValueDescriptorBuilder.() -> Unit) {
value(name.toName(), block)
}
override fun build(): NodeDescriptor = NodeDescriptorBuilder(config.copy())
public companion object {
internal val ITEM_KEY: Name = "item".asName()
internal val IS_NODE_KEY: Name = "@isNode".asName()
//TODO infer descriptor from spec
}
}
public inline fun NodeDescriptor(block: NodeDescriptorBuilder.() -> Unit): NodeDescriptor =
NodeDescriptorBuilder().apply(block)
/**
* Merge two node descriptors into one using first one as primary
*/
public operator fun NodeDescriptor.plus(other: NodeDescriptor): NodeDescriptor {
return NodeDescriptorBuilder().apply {
config.update(other.toMeta())
config.update(this@plus.toMeta())
}
}

View File

@ -1,135 +0,0 @@
package space.kscience.dataforge.meta.descriptors
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.misc.DFBuilder
import space.kscience.dataforge.values.*
/**
* A descriptor for meta value
*
* Descriptor can have non-atomic path. It is resolved when descriptor is added to the node
*
* @author Alexander Nozik
*/
@DFBuilder
public sealed interface ValueDescriptor: ItemDescriptor{
/**
* True if the value is required
*
* @return
*/
override val required: Boolean
/**
* The default for this value. Null if there is no default.
*
* @return
*/
public val default: Value?
/**
* A list of allowed ValueTypes. Empty if any value type allowed
*
* @return
*/
public val type: List<ValueType>?
/**
* Check if given value is allowed for here. The type should be allowed and
* if it is value should be within allowed values
*
* @param value
* @return
*/
public fun isAllowedValue(value: Value): Boolean =
(type?.let { it.contains(ValueType.STRING) || it.contains(value.type) } ?: true)
&& (allowedValues.isEmpty() || allowedValues.contains(value))
/**
* A list of allowed values with descriptions. If empty than any value is
* allowed.
*
* @return
*/
public val allowedValues: List<Value>
}
/**
* A builder fir [ValueDescriptor]
*/
@DFBuilder
public class ValueDescriptorBuilder(config: Config = Config()) : ItemDescriptorBuilder(config), ValueDescriptor {
/**
* True if the value is required
*
* @return
*/
override var required: Boolean by config.boolean { default == null }
/**
* The default for this value. Null if there is no default.
*
* @return
*/
override var default: Value? by config.value()
public fun default(v: Any) {
this.default = Value.of(v)
}
/**
* A list of allowed ValueTypes. Empty if any value type allowed
*
* @return
*/
override var type: List<ValueType>? by config.listValue { ValueType.valueOf(it.string) }
public fun type(vararg t: ValueType) {
this.type = listOf(*t)
}
/**
* Check if given value is allowed for here. The type should be allowed and
* if it is value should be within allowed values
*
* @param value
* @return
*/
override fun isAllowedValue(value: Value): Boolean {
return (type?.let { it.contains(ValueType.STRING) || it.contains(value.type) } ?: true)
&& (allowedValues.isEmpty() || allowedValues.contains(value))
}
/**
* A list of allowed values with descriptions. If empty than any value is
* allowed.
*
* @return
*/
override var allowedValues: List<Value> by config.item().convert(
reader = {
val value = it.value
when {
value?.list != null -> value.list
type?.let { type -> type.size == 1 && type[0] === ValueType.BOOLEAN } ?: false -> listOf(True, False)
else -> emptyList()
}
},
writer = {
MetaItemValue(it.asValue())
}
)
/**
* Allow given list of value and forbid others
*/
public fun allow(vararg v: Any) {
this.allowedValues = v.map { Value.of(it) }
}
override fun build(): ValueDescriptor = ValueDescriptorBuilder(config.copy())
}

View File

@ -1,18 +0,0 @@
package space.kscience.dataforge.meta.descriptors
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.values.ValueType
import space.kscience.dataforge.values.asValue
public inline fun <reified E : Enum<E>> NodeDescriptorBuilder.enum(
key: Name,
default: E?,
crossinline modifier: ValueDescriptor.() -> Unit = {},
): Unit = value(key) {
type(ValueType.STRING)
default?.let {
default(default)
}
allowedValues = enumValues<E>().map { it.asValue() }
modifier()
}

View File

@ -0,0 +1,46 @@
package space.kscience.dataforge.meta.descriptors
import space.kscience.dataforge.meta.Scheme
import space.kscience.dataforge.meta.SchemeSpec
import space.kscience.dataforge.values.ValueType
import kotlin.reflect.KProperty1
import kotlin.reflect.typeOf
public inline fun <S : Scheme, reified T> MetaDescriptorBuilder.value(
property: KProperty1<S, T>,
noinline block: MetaDescriptorBuilder.() -> Unit = {},
): MetaDescriptorBuilder = when (typeOf<T>()) {
typeOf<Number>(), typeOf<Int>(), typeOf<Double>(), typeOf<Short>(), typeOf<Long>(), typeOf<Float>() ->
value(property.name, ValueType.NUMBER) {
block()
}
typeOf<Number?>(), typeOf<Int?>(), typeOf<Double?>(), typeOf<Short?>(), typeOf<Long?>(), typeOf<Float?>() ->
value(property.name, ValueType.NUMBER) {
block()
}
typeOf<Boolean>() -> value(property.name, ValueType.BOOLEAN) {
block()
}
typeOf<List<Number>>(), typeOf<List<Int>>(), typeOf<List<Double>>(), typeOf<List<Short>>(), typeOf<List<Long>>(), typeOf<List<Float>>(),
typeOf<IntArray>(), typeOf<DoubleArray>(), typeOf<ShortArray>(), typeOf<LongArray>(), typeOf<FloatArray>(),
-> value(property.name, ValueType.NUMBER) {
multiple = true
block()
}
typeOf<String>() -> value(property.name, ValueType.STRING) {
block()
}
typeOf<List<String>>(), typeOf<Array<String>>() -> value(property.name, ValueType.STRING) {
multiple = true
block()
}
else -> item(property.name, block)
}
public inline fun <S : Scheme, reified T : Scheme> MetaDescriptorBuilder.scheme(
property: KProperty1<S, T>,
spec: SchemeSpec<T>,
noinline block: MetaDescriptorBuilder.() -> Unit = {},
) {
node(property.name, spec, block)
}

View File

@ -1,47 +1,54 @@
package space.kscience.dataforge.meta
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.get
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.toName
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.values.ListValue
import space.kscience.dataforge.values.Value
/**
* Convert meta to map of maps
*/
public fun Meta.toMap(descriptor: NodeDescriptor? = null): Map<String, Any?> {
return items.entries.associate { (token, item) ->
token.toString() to when (item) {
is MetaItemNode -> item.node.toMap()
is MetaItemValue -> item.value.value
public fun Meta.toMap(descriptor: MetaDescriptor? = null): Map<String, Any?> = buildMap {
items.forEach { (key, child) ->
if (child.items.isEmpty()) {
//single-value meta is considered a leaf
put(key.toString(), child.value)
} else {
//if child contains both children and value, then value will be placed into `@value` key
put(key.toString(), child.toMap(descriptor?.get(key.body)))
}
}
if (value != null) {
put(Meta.VALUE_KEY, value)
}
}
/**
* Convert map of maps to meta. This method will recognize [TypedMetaItem], [Map]<String,Any?> and [List] of all mentioned above as value.
* All other values will be converted to values.
* Convert map of maps to meta. This method will recognize [Meta], [Map]<String,Any?> and [List] of all mentioned above as value.
* All other values will be converted to [Value].
*/
@DFExperimental
public fun Map<String, Any?>.toMeta(descriptor: NodeDescriptor? = null): Meta = Meta {
public fun Map<String, Any?>.toMeta(@Suppress("UNUSED_PARAMETER") descriptor: MetaDescriptor? = null): Meta = Meta {
@Suppress("UNCHECKED_CAST")
fun toItem(value: Any?): MetaItem = when (value) {
is MetaItem -> value
is Meta -> MetaItemNode(value)
is Map<*, *> -> MetaItemNode((value as Map<String, Any?>).toMeta())
else -> MetaItemValue(Value.of(value))
fun toMeta(value: Any?): Meta = when (value) {
is Meta -> value
is Map<*, *> -> (value as Map<String, Any?>).toMeta()
else -> Meta(Value.of(value))
}
entries.forEach { (key, value) ->
if (value is List<*>) {
val items = value.map { toItem(it) }
if (items.all { it is MetaItemValue }) {
val items = value.map { toMeta(it) }
if (items.all { it.isLeaf }) {
set(key, ListValue(items.map { it.value!! }))
} else {
setIndexedItems(key.toName(), value.map { toItem(it) })
setIndexed(Name.parse(key), value.map { toMeta(it) })
}
} else {
set(key, toItem(value))
set(key, toMeta(value))
}
}
}

View File

@ -1,124 +1,89 @@
package space.kscience.dataforge.meta.transformations
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.values.*
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.asValue
/**
* A converter of generic object to and from [TypedMetaItem]
* A converter of generic object to and from [Meta]
*/
public interface MetaConverter<T : Any> {
public fun itemToObject(item: MetaItem): T
public fun objectToMetaItem(obj: T): MetaItem
public interface MetaConverter<T> {
public fun metaToObject(meta: Meta): T?
public fun objectToMeta(obj: T): Meta
public companion object {
public val item: MetaConverter<MetaItem> = object : MetaConverter<MetaItem> {
override fun itemToObject(item: MetaItem): MetaItem = item
override fun objectToMetaItem(obj: MetaItem): MetaItem = obj
}
public val meta: MetaConverter<Meta> = object : MetaConverter<Meta> {
override fun itemToObject(item: MetaItem): Meta = when (item) {
is MetaItemNode -> item.node
is MetaItemValue -> item.value.toMeta()
}
override fun objectToMetaItem(obj: Meta): MetaItem = MetaItemNode(obj)
override fun metaToObject(meta: Meta): Meta = meta
override fun objectToMeta(obj: Meta): Meta = obj
}
public val value: MetaConverter<Value> = object : MetaConverter<Value> {
override fun itemToObject(item: MetaItem): Value = when (item) {
is MetaItemNode -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value")
is MetaItemValue -> item.value
}
override fun objectToMetaItem(obj: Value): MetaItem = MetaItemValue(obj)
override fun metaToObject(meta: Meta): Value? = meta.value
override fun objectToMeta(obj: Value): Meta = Meta(obj)
}
public val string: MetaConverter<String> = object : MetaConverter<String> {
override fun itemToObject(item: MetaItem): String = when (item) {
is MetaItemNode -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value")
is MetaItemValue -> item.value
}.string
override fun objectToMetaItem(obj: String): MetaItem = MetaItemValue(obj.asValue())
override fun metaToObject(meta: Meta): String? = meta.string
override fun objectToMeta(obj: String): Meta = Meta(obj.asValue())
}
public val boolean: MetaConverter<Boolean> = object : MetaConverter<Boolean> {
override fun itemToObject(item: MetaItem): Boolean = when (item) {
is MetaItemNode -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value")
is MetaItemValue -> item.value
}.boolean
override fun objectToMetaItem(obj: Boolean): MetaItem = MetaItemValue(obj.asValue())
override fun metaToObject(meta: Meta): Boolean? = meta.boolean
override fun objectToMeta(obj: Boolean): Meta = Meta(obj.asValue())
}
public val number: MetaConverter<Number> = object : MetaConverter<Number> {
override fun itemToObject(item: MetaItem): Number = when (item) {
is MetaItemNode -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value")
is MetaItemValue -> item.value
}.number
override fun objectToMetaItem(obj: Number): MetaItem = MetaItemValue(obj.asValue())
override fun metaToObject(meta: Meta): Number? = meta.number
override fun objectToMeta(obj: Number): Meta = Meta(obj.asValue())
}
public val double: MetaConverter<Double> = object : MetaConverter<Double> {
override fun itemToObject(item: MetaItem): Double = when (item) {
is MetaItemNode -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value")
is MetaItemValue -> item.value
}.double
override fun metaToObject(meta: Meta): Double? = meta.double
override fun objectToMetaItem(obj: Double): MetaItem = MetaItemValue(obj.asValue())
override fun objectToMeta(obj: Double): Meta = Meta(obj.asValue())
}
public val float: MetaConverter<Float> = object : MetaConverter<Float> {
override fun itemToObject(item: MetaItem): Float = when (item) {
is MetaItemNode -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value")
is MetaItemValue -> item.value
}.float
override fun metaToObject(meta: Meta): Float? = meta.float
override fun objectToMetaItem(obj: Float): MetaItem = MetaItemValue(obj.asValue())
override fun objectToMeta(obj: Float): Meta = Meta(obj.asValue())
}
public val int: MetaConverter<Int> = object : MetaConverter<Int> {
override fun itemToObject(item: MetaItem): Int = when (item) {
is MetaItemNode -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value")
is MetaItemValue -> item.value
}.int
override fun metaToObject(meta: Meta): Int? = meta.int
override fun objectToMetaItem(obj: Int): MetaItem = MetaItemValue(obj.asValue())
override fun objectToMeta(obj: Int): Meta = Meta(obj.asValue())
}
public val long: MetaConverter<Long> = object : MetaConverter<Long> {
override fun itemToObject(item: MetaItem): Long = when (item) {
is MetaItemNode -> item.node[Meta.VALUE_KEY].value ?: error("Can't convert node to a value")
is MetaItemValue -> item.value
}.long
override fun metaToObject(meta: Meta): Long? = meta.long
override fun objectToMetaItem(obj: Long): MetaItem = MetaItemValue(obj.asValue())
override fun objectToMeta(obj: Long): Meta = Meta(obj.asValue())
}
public inline fun <reified E : Enum<E>> enum(): MetaConverter<E> = object : MetaConverter<E> {
@Suppress("USELESS_CAST")
override fun itemToObject(item: MetaItem): E = item.enum<E>() as? E ?: error("The Item is not a Enum")
override fun metaToObject(meta: Meta): E = meta.enum<E>() as? E ?: error("The Item is not a Enum")
override fun objectToMetaItem(obj: E): MetaItem = MetaItemValue(obj.asValue())
override fun objectToMeta(obj: E): Meta = Meta(obj.asValue())
}
public fun <T> valueList(writer: (T) -> Value = { Value.of(it)}, reader: (Value) -> T): MetaConverter<List<T>> =
public fun <T> valueList(
writer: (T) -> Value = { Value.of(it) },
reader: (Value) -> T
): MetaConverter<List<T>> =
object : MetaConverter<List<T>> {
override fun itemToObject(item: MetaItem): List<T> =
item.value?.list?.map(reader) ?: error("The item is not a value list")
override fun metaToObject(meta: Meta): List<T> =
meta.value?.list?.map(reader) ?: error("The item is not a value list")
override fun objectToMetaItem(obj: List<T>): MetaItem =
MetaItemValue(obj.map(writer).asValue())
override fun objectToMeta(obj: List<T>): Meta = Meta(obj.map(writer).asValue())
}
}
}
public fun <T : Any> MetaConverter<T>.nullableItemToObject(item: MetaItem?): T? = item?.let { itemToObject(it) }
public fun <T : Any> MetaConverter<T>.nullableObjectToMetaItem(obj: T?): MetaItem? = obj?.let { objectToMetaItem(it) }
public fun <T : Any> MetaConverter<T>.nullableMetaToObject(item: Meta?): T? = item?.let { metaToObject(it) }
public fun <T : Any> MetaConverter<T>.nullableObjectToMeta(obj: T?): Meta? = obj?.let { objectToMeta(it) }
public fun <T : Any> MetaConverter<T>.metaToObject(meta: Meta): T = itemToObject(MetaItemNode(meta))
public fun <T : Any> MetaConverter<T>.valueToObject(value: Value): T = itemToObject(MetaItemValue(value))
public fun <T> MetaConverter<T>.valueToObject(value: Value): T? = metaToObject(Meta(value))

View File

@ -13,7 +13,7 @@ public interface TransformationRule {
/**
* Check if this transformation should be applied to a node with given name and value
*/
public fun matches(name: Name, item: MetaItem?): Boolean
public fun matches(name: Name, item: Meta?): Boolean
/**
* Select all items to be transformed. Item could be a value as well as node
@ -21,12 +21,12 @@ public interface TransformationRule {
* @return a sequence of item paths to be transformed
*/
public fun selectItems(meta: Meta): Sequence<Name> =
meta.itemSequence().filter { matches(it.first, it.second) }.map { it.first }
meta.nodeSequence().filter { matches(it.first, it.second) }.map { it.first }
/**
* Apply transformation for a single item (Node or Value) to the target
*/
public fun <M : MutableMeta<M>> transformItem(name: Name, item: MetaItem?, target: M): Unit
public fun transformItem(name: Name, item: Meta?, target: MutableMeta): Unit
}
/**
@ -34,15 +34,15 @@ public interface TransformationRule {
*/
public data class KeepTransformationRule(val selector: (Name) -> Boolean) :
TransformationRule {
override fun matches(name: Name, item: MetaItem?): Boolean {
override fun matches(name: Name, item: Meta?): Boolean {
return selector(name)
}
override fun selectItems(meta: Meta): Sequence<Name> =
meta.itemSequence().map { it.first }.filter(selector)
meta.nodeSequence().map { it.first }.filter(selector)
override fun <M : MutableMeta<M>> transformItem(name: Name, item: MetaItem?, target: M) {
if (selector(name)) target.set(name, item)
override fun transformItem(name: Name, item: Meta?, target: MutableMeta) {
if (selector(name)) target.setMeta(name, item)
}
}
@ -51,15 +51,15 @@ public data class KeepTransformationRule(val selector: (Name) -> Boolean) :
*/
public data class SingleItemTransformationRule(
val from: Name,
val transform: MutableMeta<*>.(Name, MetaItem?) -> Unit,
val transform: MutableMeta.(Name, Meta?) -> Unit,
) : TransformationRule {
override fun matches(name: Name, item: MetaItem?): Boolean {
override fun matches(name: Name, item: Meta?): Boolean {
return name == from
}
override fun selectItems(meta: Meta): Sequence<Name> = sequenceOf(from)
override fun <M : MutableMeta<M>> transformItem(name: Name, item: MetaItem?, target: M) {
override fun transformItem(name: Name, item: Meta?, target: MutableMeta) {
if (name == this.from) {
target.transform(name, item)
}
@ -68,13 +68,13 @@ public data class SingleItemTransformationRule(
public data class RegexItemTransformationRule(
val from: Regex,
val transform: MutableMeta<*>.(name: Name, MatchResult, MetaItem?) -> Unit,
val transform: MutableMeta.(name: Name, MatchResult, Meta?) -> Unit,
) : TransformationRule {
override fun matches(name: Name, item: MetaItem?): Boolean {
override fun matches(name: Name, item: Meta?): Boolean {
return from.matches(name.toString())
}
override fun <M : MutableMeta<M>> transformItem(name: Name, item: MetaItem?, target: M) {
override fun transformItem(name: Name, item: Meta?, target: MutableMeta) {
val match = from.matchEntire(name.toString())
if (match != null) {
target.transform(name, match, item)
@ -105,7 +105,7 @@ public value class MetaTransformation(private val transformations: Collection<Tr
* Generate an observable configuration that contains only elements defined by transformation rules and changes with the source
*/
@DFExperimental
public fun generate(source: Config): ObservableItemProvider = Config().apply {
public fun generate(source: ObservableMeta): ObservableMeta = MutableMeta().apply {
transformations.forEach { rule ->
rule.selectItems(source).forEach { name ->
rule.transformItem(name, source[name], this)
@ -131,8 +131,9 @@ public value class MetaTransformation(private val transformations: Collection<Tr
/**
* Listens for changes in the source node and translates them into second node if transformation set contains a corresponding rule.
*/
public fun <M : MutableMeta<M>> bind(source: Config, target: M) {
source.onChange(target) { name, _, newItem ->
public fun bind(source: ObservableMeta, target: MutableMeta) {
source.onChange(target) { name ->
val newItem = source[name]
transformations.forEach { t ->
if (t.matches(name, newItem)) {
t.transformItem(name, newItem, target)
@ -172,18 +173,18 @@ public class MetaTransformationBuilder {
*/
public fun keep(regex: String) {
transformations.add(
RegexItemTransformationRule(regex.toRegex()) { name, _, metaItem ->
set(name, metaItem)
RegexItemTransformationRule(regex.toRegex()) { name, _, Meta ->
setMeta(name, Meta)
})
}
/**
* Move an item from [from] to [to], optionally applying [operation] it defined
*/
public fun move(from: Name, to: Name, operation: (MetaItem?) -> Any? = { it }) {
public fun move(from: Name, to: Name, operation: (Meta?) -> Meta? = { it }) {
transformations.add(
SingleItemTransformationRule(from) { _, item ->
set(to, operation(item))
setMeta(to, operation(item))
}
)
}

View File

@ -0,0 +1,3 @@
package space.kscience.dataforge.misc
public expect inline fun <T> Any?.unsafeCast(): T

View File

@ -47,6 +47,66 @@ public class Name(public val tokens: List<NameToken>) {
public val MATCH_ALL_TOKEN: NameToken = NameToken("**")
public val EMPTY: Name = Name(emptyList())
/**
* Convert a list of strings to a [Name] interpreting all arguments as token bodies without indices
*/
public fun of(vararg strings: String): Name = Name(strings.map { NameToken(it) })
/**
* Convert a [String] to name parsing it and extracting name tokens and index syntax.
* This operation is rather heavy so it should be used with care in high performance code.
*/
public fun parse(string: String): Name{
if (string.isBlank()) return Name.EMPTY
val tokens = sequence {
var bodyBuilder = StringBuilder()
var queryBuilder = StringBuilder()
var bracketCount: Int = 0
var escape: Boolean = false
fun queryOn() = bracketCount > 0
for (it in string) {
when {
escape -> {
if (queryOn()) {
queryBuilder.append(it)
} else {
bodyBuilder.append(it)
}
escape = false
}
it == '\\' -> {
escape = true
}
queryOn() -> {
when (it) {
'[' -> bracketCount++
']' -> bracketCount--
}
if (queryOn()) queryBuilder.append(it)
}
else -> when (it) {
'.' -> {
val query = if (queryBuilder.isEmpty()) null else queryBuilder.toString()
yield(NameToken(bodyBuilder.toString(), query))
bodyBuilder = StringBuilder()
queryBuilder = StringBuilder()
}
'[' -> bracketCount++
']' -> error("Syntax error: closing bracket ] not have not matching open bracket")
else -> {
if (queryBuilder.isNotEmpty()) error("Syntax error: only name end and name separator are allowed after index")
bodyBuilder.append(it)
}
}
}
}
val query = if (queryBuilder.isEmpty()) null else queryBuilder.toString()
yield(NameToken(bodyBuilder.toString(), query))
}
return Name(tokens.toList())
}
}
}
@ -74,61 +134,11 @@ public fun Name.lastOrNull(): NameToken? = tokens.lastOrNull()
*/
public fun Name.firstOrNull(): NameToken? = tokens.firstOrNull()
/**
* Convert a [String] to name parsing it and extracting name tokens and index syntax.
* This operation is rather heavy so it should be used with care in high performance code.
* First token or throw exception
*/
public fun String.toName(): Name {
if (isBlank()) return Name.EMPTY
val tokens = sequence {
var bodyBuilder = StringBuilder()
var queryBuilder = StringBuilder()
var bracketCount: Int = 0
var escape: Boolean = false
fun queryOn() = bracketCount > 0
public fun Name.first(): NameToken = tokens.first()
for (it in this@toName) {
when {
escape -> {
if (queryOn()) {
queryBuilder.append(it)
} else {
bodyBuilder.append(it)
}
escape = false
}
it == '\\' -> {
escape = true
}
queryOn() -> {
when (it) {
'[' -> bracketCount++
']' -> bracketCount--
}
if (queryOn()) queryBuilder.append(it)
}
else -> when (it) {
'.' -> {
val query = if (queryBuilder.isEmpty()) null else queryBuilder.toString()
yield(NameToken(bodyBuilder.toString(), query))
bodyBuilder = StringBuilder()
queryBuilder = StringBuilder()
}
'[' -> bracketCount++
']' -> error("Syntax error: closing bracket ] not have not matching open bracket")
else -> {
if (queryBuilder.isNotEmpty()) error("Syntax error: only name end and name separator are allowed after index")
bodyBuilder.append(it)
}
}
}
}
val query = if (queryBuilder.isEmpty()) null else queryBuilder.toString()
yield(NameToken(bodyBuilder.toString(), query))
}
return Name(tokens.toList())
}
/**
* Convert the [String] to a [Name] by simply wrapping it in a single name token without parsing.
@ -140,7 +150,7 @@ public operator fun NameToken.plus(other: Name): Name = Name(listOf(this) + othe
public operator fun Name.plus(other: Name): Name = Name(this.tokens + other.tokens)
public operator fun Name.plus(other: String): Name = this + other.toName()
public operator fun Name.plus(other: String): Name = this + Name.parse(other)
public operator fun Name.plus(other: NameToken): Name = Name(tokens + other)
@ -169,8 +179,8 @@ public fun Name.withIndex(index: String): Name {
* Fast [String]-based accessor for item map
*/
public operator fun <T> Map<NameToken, T>.get(body: String, query: String? = null): T? = get(NameToken(body, query))
public operator fun <T> Map<Name, T>.get(name: String): T? = get(name.toName())
public operator fun <T> MutableMap<Name, T>.set(name: String, value: T): Unit = set(name.toName(), value)
public operator fun <T> Map<Name, T>.get(name: String): T? = get(Name.parse(name))
public operator fun <T> MutableMap<Name, T>.set(name: String, value: T): Unit = set(Name.parse(name), value)
/* Name comparison operations */
@ -185,7 +195,7 @@ public fun Name.endsWith(name: Name): Boolean =
this.length >= name.length && (this == name || tokens.subList(length - name.length, length) == name.tokens)
/**
* if [this] starts with given [head] name, returns the reminder of the name (could be empty). Otherwise returns null
* if [this] starts with given [head] name, returns the reminder of the name (could be empty). Otherwise, returns null
*/
public fun Name.removeHeadOrNull(head: Name): Name? = if (startsWith(head)) {
Name(tokens.subList(head.length, length))

View File

@ -12,7 +12,7 @@ public object NameSerializer : KSerializer<Name> {
PrimitiveSerialDescriptor("space.kscience.dataforge.names.Name", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): Name {
return decoder.decodeString().toName()
return Name.parse(decoder.decodeString())
}
override fun serialize(encoder: Encoder, value: Name) {
@ -25,7 +25,7 @@ public object NameTokenSerializer: KSerializer<NameToken> {
PrimitiveSerialDescriptor("space.kscience.dataforge.names.NameToken", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): NameToken {
return decoder.decodeString().toName().firstOrNull()!!
return Name.parse(decoder.decodeString()).firstOrNull()!!
}
override fun serialize(

View File

@ -44,4 +44,4 @@ public fun Name.matches(pattern: Name): Boolean = when {
}
@OptIn(DFExperimental::class)
public fun Name.matches(pattern: String): Boolean = matches(pattern.toName())
public fun Name.matches(pattern: String): Boolean = matches(Name.parse(pattern))

View File

@ -1,6 +1,7 @@
package space.kscience.dataforge.values
import kotlinx.serialization.Serializable
import kotlin.jvm.JvmInline
/**
@ -153,16 +154,10 @@ public class NumberValue(public val number: Number) : Value {
override fun hashCode(): Int = numberOrNull.hashCode()
}
public class StringValue(public val string: String) : Value {
@JvmInline
public value class StringValue(public val string: String) : Value {
override val value: Any get() = string
override val type: ValueType get() = ValueType.STRING
override fun equals(other: Any?): Boolean {
return this.string == (other as? Value)?.string
}
override fun hashCode(): Int = string.hashCode()
override fun toString(): String = string
}
@ -199,11 +194,14 @@ public class ListValue(override val list: List<Value>) : Value, Iterable<Value>
return list.hashCode()
}
public companion object{
public companion object {
public val EMPTY: ListValue = ListValue(emptyList())
}
}
public fun ListValue(vararg numbers: Number): ListValue = ListValue(numbers.map { it.asValue() })
public fun ListValue(vararg strings: String): ListValue = ListValue(strings.map { it.asValue() })
public fun Number.asValue(): Value = NumberValue(this)
public fun Boolean.asValue(): Value = if (this) True else False

View File

@ -0,0 +1,23 @@
package space.kscience.dataforge.values
import space.kscience.dataforge.names.Name
/**
* An object that could provide values
*/
public fun interface ValueProvider {
public fun getValue(name: Name): Value?
}
public fun ValueProvider.getValue(key: String): Value? = getValue(Name.parse(key))
/**
* An object that could consume values
*/
public interface MutableValueProvider : ValueProvider {
public fun setValue(name: Name, value: Value?)
}
public fun MutableValueProvider.setValue(key: String, value: Value?) {
setValue(Name.parse(key), value)
}

View File

@ -1,7 +1,6 @@
package space.kscience.dataforge.values
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.MetaBuilder
/**
* Check if value is null
@ -25,6 +24,13 @@ public val Value.float: Float get() = number.toFloat()
public val Value.short: Short get() = number.toShort()
public val Value.long: Long get() = number.toLong()
public inline fun <reified E : Enum<E>> Value.enum(): E = if (this is EnumValue<*>) {
value as E
} else {
enumValueOf<E>(string)
}
public val Value.stringList: List<String> get() = list.map { it.string }
public val Value.doubleArray: DoubleArray
@ -35,4 +41,4 @@ public val Value.doubleArray: DoubleArray
}
public fun Value.toMeta(): MetaBuilder = Meta { Meta.VALUE_KEY put this }
public fun Value.toMeta(): Meta = Meta(this)

View File

@ -6,7 +6,7 @@ import kotlin.test.assertEquals
class ConfigTest {
@Test
fun testIndexedWrite(){
val config = Config()
val config = MutableMeta()
config["a[1].b"] = 1
assertEquals(null, config["a.b"].int)
assertEquals(1, config["a[1].b"].int)

View File

@ -1,7 +1,8 @@
package space.kscience.dataforge.meta
import kotlinx.serialization.json.*
import space.kscience.dataforge.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.meta.descriptors.item
import kotlin.test.Test
import kotlin.test.assertEquals
@ -30,8 +31,8 @@ class JsonMetaTest {
})
}
val descriptor = NodeDescriptor{
node("nodeArray"){
val descriptor = MetaDescriptor {
item("nodeArray") {
indexKey = "index"
}
}
@ -43,8 +44,17 @@ class JsonMetaTest {
//println(meta)
val reconstructed = meta.toJson(descriptor)
println(reconstructed)
assertEquals(2,
reconstructed["nodeArray"]?.jsonArray?.get(1)?.jsonObject?.get("index")?.jsonPrimitive?.int)
assertEquals(json,reconstructed)
assertEquals(
2,
reconstructed
.jsonObject["nodeArray"]
?.jsonArray
?.get(1)
?.jsonObject
?.get("index")
?.jsonPrimitive
?.int
)
assertEquals(json, reconstructed)
}
}

View File

@ -1,5 +1,6 @@
package space.kscience.dataforge.meta
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.asValue
import kotlin.test.Test
import kotlin.test.assertEquals
@ -10,7 +11,7 @@ class MetaBuilderTest {
fun testBuilder() {
val meta = Meta {
"a" put 22
"b" put listOf(1, 2, 3)
"b" put Value.of(listOf(1, 2, 3))
this["c"] = "myValue".asValue()
"node" put {
"e" put 12.2

View File

@ -1,6 +1,5 @@
package space.kscience.dataforge.meta
import space.kscience.dataforge.values.asValue
import kotlin.test.Test
import kotlin.test.assertEquals
@ -11,17 +10,17 @@ class MetaDelegateTest {
NO
}
class InnerSpec : Scheme() {
class InnerScheme : Scheme() {
var innerValue by string()
companion object : SchemeSpec<InnerSpec>(::InnerSpec)
companion object : SchemeSpec<InnerScheme>(::InnerScheme)
}
class TestScheme : Scheme() {
var myValue by string()
var safeValue by double(2.2)
var enumValue by enum(TestEnum.YES)
var inner by spec(InnerSpec)
var inner by spec(InnerScheme)
companion object : SchemeSpec<TestScheme>(::TestScheme)
}
@ -30,10 +29,10 @@ class MetaDelegateTest {
fun delegateTest() {
val testObject = TestScheme.empty()
testObject.set("myValue","theString".asValue())
testObject.meta["myValue"] = "theString"
testObject.enumValue = TestEnum.NO
testObject.inner = InnerSpec { innerValue = "ddd" }
testObject.inner = InnerScheme { innerValue = "ddd" }
assertEquals("theString", testObject.myValue)
assertEquals(TestEnum.NO, testObject.enumValue)

View File

@ -40,7 +40,7 @@ class MetaTest {
"b" put {
"c" put "ddd"
}
"list" put (0..4).map {
"list" putIndexed (0..4).map {
Meta {
"value" put it
}

View File

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

View File

@ -7,27 +7,27 @@ import kotlin.test.assertEquals
@DFExperimental
class SchemeTest {
@Test
fun testSchemeWrappingBeforeEdit(){
val config = Config()
val scheme = TestScheme.wrap(config)
fun testSchemeWrappingBeforeEdit() {
val config = MutableMeta()
val scheme = TestScheme.write(config)
scheme.a = 29
assertEquals(29, config["a"].int)
}
@Test
fun testSchemeWrappingAfterEdit(){
fun testSchemeWrappingAfterEdit() {
val scheme = TestScheme.empty()
scheme.a = 29
val config = Config()
val config = MutableMeta()
scheme.retarget(config)
assertEquals(29, scheme.a)
}
@Test
fun testSchemeSubscription(){
fun testSchemeSubscription() {
val scheme = TestScheme.empty()
var flag: Int? = null
scheme.useProperty(TestScheme::a){a->
scheme.useProperty(TestScheme::a) { a ->
flag = a
}
scheme.a = 2

View File

@ -9,16 +9,7 @@ internal class TestScheme : Scheme() {
var a by int()
var b by string()
companion object : Specification<TestScheme> {
override fun empty(): TestScheme = TestScheme()
override fun read(items: ItemProvider): TestScheme =
wrap(Config(), items)
override fun write(target: MutableItemProvider, defaultProvider: ItemProvider): TestScheme =
wrap(target, defaultProvider)
}
companion object : SchemeSpec<TestScheme>(::TestScheme)
}
class SpecificationTest {
@ -58,8 +49,8 @@ class SpecificationTest {
@Test
fun testChildModification() {
val config = Config()
val child = config.getChild("child")
val config = MutableMeta()
val child = config.getOrCreate("child")
val scheme = TestScheme.write(child)
scheme.a = 22
scheme.b = "test"
@ -69,9 +60,9 @@ class SpecificationTest {
@Test
fun testChildUpdate() {
val config = Config()
val child = config.getChild("child")
child.update(TestScheme) {
val config = MutableMeta()
val child = config.getOrCreate("child")
child.updateWith(TestScheme) {
a = 22
b = "test"
}

View File

@ -9,16 +9,14 @@ import kotlin.test.assertNotNull
class DescriptorTest {
val descriptor = NodeDescriptor {
val descriptor = MetaDescriptor {
node("aNode") {
info = "A root demo node"
value("b") {
value("b", ValueType.NUMBER) {
info = "b number value"
type(ValueType.NUMBER)
}
node("otherNode") {
value("otherValue") {
type(ValueType.BOOLEAN)
value("otherValue", ValueType.BOOLEAN) {
default(false)
info = "default value"
}
@ -30,13 +28,13 @@ class DescriptorTest {
fun testAllowedValues() {
val child = descriptor["aNode.b"]
assertNotNull(child)
val allowed = descriptor.nodes["aNode"]?.values?.get("b")?.allowedValues
assertEquals(emptyList(), allowed)
val allowed = descriptor["aNode"]?.get("b")?.allowedValues
assertEquals(null, allowed)
}
@Test
fun testDefaultMetaNode(){
val meta = descriptor.defaultMeta()
fun testDefaultMetaNode() {
val meta = descriptor.defaultNode
assertEquals(false, meta["aNode.otherNode.otherValue"].boolean)
}
}

View File

@ -10,7 +10,7 @@ import kotlin.test.assertTrue
class NameMatchTest {
@Test
fun matchWildCards() {
val theName = "a.b.c.d".toName()
val theName = Name.parse("a.b.c.d")
assertTrue { theName.matches("a.b.**") }
assertTrue { theName.matches("a.*.c.**") }
assertTrue { theName.matches("**.d") }
@ -22,7 +22,7 @@ class NameMatchTest {
@Test
fun matchPattern() {
val theName = "a[dd+2].b[13].c.d[\"d\"]".toName()
val theName = Name.parse("a[dd+2].b[13].c.d[\"d\"]")
assertTrue { theName.matches("a[.*].b[.*].c[.*].d[.*]") }
assertTrue { theName.matches("a[.*].b[.*].c.d[.*]") }
assertFalse { theName.matches("a[.*].b[.*].*.d") }

View File

@ -9,7 +9,7 @@ class NameSerializationTest {
@Test
fun testNameSerialization() {
val name = "aaa.bbb.ccc".toName()
val name = Name.parse("aaa.bbb.ccc")
val json = Json.encodeToJsonElement(Name.serializer(), name)
println(json)
val reconstructed = Json.decodeFromJsonElement(Name.serializer(), json)

View File

@ -8,22 +8,22 @@ import kotlin.test.assertTrue
class NameTest {
@Test
fun simpleName() {
val name = "token1.token2.token3".toName()
val name = Name.parse("token1.token2.token3")
assertEquals("token2", name[1].toString())
}
@Test
fun equalityTest() {
val name1 = "token1.token2[2].token3".toName()
val name2 = "token1".toName() + "token2[2].token3"
val name1 = Name.parse("token1.token2[2].token3")
val name2 = "token1".asName() + "token2[2].token3"
assertEquals(name1, name2)
}
@Test
fun comparisonTest(){
val name1 = "token1.token2.token3".toName()
val name2 = "token1.token2".toName()
val name3 = "token3".toName()
val name1 = Name.parse("token1.token2.token3")
val name2 = Name.parse("token1.token2")
val name3 = Name.parse("token3")
assertTrue { name1.startsWith(name2) }
assertTrue { name1.endsWith(name3) }
assertFalse { name1.startsWith(name3) }
@ -31,11 +31,11 @@ class NameTest {
@Test
fun escapeTest(){
val escapedName = "token\\.one.token2".toName()
val escapedName = Name.parse("token\\.one.token2")
val unescapedName = "token\\.one.token2".asName()
assertEquals(2, escapedName.length)
assertEquals(1, unescapedName.length)
assertEquals(escapedName, escapedName.toString().toName())
assertEquals(escapedName, Name.parse(escapedName.toString()))
}
}

View File

@ -1,13 +1,12 @@
package space.kscience.dataforge.meta
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.values.Null
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.asValue
import space.kscience.dataforge.values.isList
//TODO add Meta wrapper for dynamic
public fun Value.toDynamic(): dynamic {
return if (isList()) {
list.map { it.toDynamic() }.toTypedArray().asDynamic()
@ -21,14 +20,10 @@ public fun Value.toDynamic(): dynamic {
*/
public fun Meta.toDynamic(): dynamic {
if (this is DynamicMeta) return this.obj
fun MetaItem.toDynamic(): dynamic = when (this) {
is MetaItemValue -> this.value.toDynamic()
is MetaItemNode -> this.node.toDynamic()
}
if(items.isEmpty()) return value?.toDynamic()
val res = js("{}")
this.items.entries.groupBy { it.key.body }.forEach { (key, value) ->
items.entries.groupBy { it.key.body }.forEach { (key, value) ->
val list = value.map { it.value }
res[key] = when (list.size) {
1 -> list.first().toDynamic()
@ -38,46 +33,60 @@ public fun Meta.toDynamic(): dynamic {
return res
}
public class DynamicMeta(internal val obj: dynamic) : MetaBase() {
private fun keys(): Array<String> = js("Object").keys(obj)
public class DynamicMeta(internal val obj: dynamic) : TypedMeta<DynamicMeta> {
private fun keys(): Array<String> = js("Object").keys(obj) as Array<String>
private fun isArray(@Suppress("UNUSED_PARAMETER") obj: dynamic): Boolean =
js("Array.isArray(obj)") as Boolean
js("Array").isArray(obj) as Boolean
private fun isPrimitive(obj: dynamic): Boolean =
(jsTypeOf(obj) != "object")
@Suppress("UNCHECKED_CAST", "USELESS_CAST")
private fun asItem(obj: dynamic): TypedMetaItem<DynamicMeta>? {
return when {
obj == null -> MetaItemValue(Null)
isArray(obj) && (obj as Array<Any?>).all { isPrimitive(it) } -> MetaItemValue(Value.of(obj as Array<Any?>))
else -> when (jsTypeOf(obj)) {
"boolean" -> MetaItemValue(Value.of(obj as Boolean))
"number" -> MetaItemValue(Value.of(obj as Number))
"string" -> MetaItemValue(Value.of(obj as String))
"object" -> MetaItemNode(DynamicMeta(obj))
else -> null
}
@Suppress("USELESS_CAST")
override val value: Value?
get() = if (isArray(obj) && (obj as Array<Any?>).all { isPrimitive(it) }) Value.of(obj as Array<Any?>)
else when (jsTypeOf(obj)) {
"boolean" -> (obj as Boolean).asValue()
"number" -> (obj as Number).asValue()
"string" -> (obj as String).asValue()
else -> null
}
}
override val items: Map<NameToken, TypedMetaItem<DynamicMeta>>
get() = keys().flatMap<String, Pair<NameToken, TypedMetaItem<DynamicMeta>>> { key ->
override val items: Map<NameToken, DynamicMeta>
get() = if (isPrimitive(obj)) {
emptyMap()
} else if (isArray(obj)) {
if((obj as Array<Any?>).all { isPrimitive(it) }){
emptyMap()
} else{
(obj as Array<dynamic>).mapIndexed{ index: Int, b: dynamic ->
val indexString = b[Meta.INDEX_KEY]?.toString() ?: index.toString()
NameToken(Meta.JSON_ARRAY_KEY, indexString) to DynamicMeta(b)
}.toMap()
}
} else keys().flatMap { key ->
val value = obj[key] ?: return@flatMap emptyList()
if (isArray(value)) {
val array = value as Array<Any?>
return@flatMap if (array.all { isPrimitive(it) }) {
listOf(NameToken(key) to MetaItemValue(Value.of(array)))
} else {
array.mapIndexedNotNull { index, it ->
val item = asItem(it) ?: return@mapIndexedNotNull null
NameToken(key, index.toString()) to item
when {
isArray(value) -> {
val array = value as Array<Any?>
if (array.all { isPrimitive(it) }) {
//primitive value
listOf(NameToken(key) to DynamicMeta(value))
} else {
array.mapIndexedNotNull { index, it ->
val item = DynamicMeta(it)
NameToken(key, index.toString()) to item
}
}
}
} else {
val item = asItem(value) ?: return@flatMap emptyList()
listOf(NameToken(key) to item)
else -> {
val item = DynamicMeta(value)
listOf(NameToken(key) to item)
}
}
}.associate { it }
}.toMap()
override fun toString(): String = Meta.toString(this)
override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta)
override fun hashCode(): Int = Meta.hashCode(this)
}

View File

@ -0,0 +1,5 @@
package space.kscience.dataforge.misc
import kotlin.js.unsafeCast as unsafeCastJs
@Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE")
public actual inline fun <T> Any?.unsafeCast(): T = this.unsafeCastJs<T>()

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