0.5.0 #70
21
.github/workflows/build.yml
vendored
21
.github/workflows/build.yml
vendored
@ -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
|
||||
|
29
.github/workflows/pages.yml
vendored
29
.github/workflows/pages.yml
vendored
@ -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
|
||||
|
38
.github/workflows/publish.yml
vendored
38
.github/workflows/publish.yml
vendored
@ -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 }}
|
||||
|
25
CHANGELOG.md
25
CHANGELOG.md
@ -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
|
||||
|
@ -35,12 +35,6 @@
|
||||
> **Maturity**: PROTOTYPE
|
||||
<hr/>
|
||||
|
||||
* ### [dataforge-tables](dataforge-tables)
|
||||
>
|
||||
>
|
||||
> **Maturity**: PROTOTYPE
|
||||
<hr/>
|
||||
|
||||
* ### [dataforge-workspace](dataforge-workspace)
|
||||
>
|
||||
>
|
||||
|
@ -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")
|
||||
|
@ -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 {
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ internal class TestScheme : Scheme() {
|
||||
}
|
||||
|
||||
@DFExperimental
|
||||
class ItemPropertiesTest {
|
||||
class MetaPropertiesTest {
|
||||
@Test
|
||||
fun testBinding() {
|
||||
val scheme = TestScheme.empty()
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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))
|
@ -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)
|
||||
|
||||
|
@ -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))
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
// }
|
||||
//}
|
@ -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)
|
||||
|
||||
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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")
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
@ -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()
|
@ -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) }
|
@ -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)
|
@ -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())
|
@ -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"
|
||||
// }
|
||||
//}
|
@ -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 }
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
@ -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)
|
@ -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() }
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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))
|
@ -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)
|
||||
}
|
@ -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)
|
||||
|
@ -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) }
|
||||
)
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
@ -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())
|
@ -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)
|
||||
|
||||
}
|
@ -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())
|
||||
}
|
@ -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() })
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
@ -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?
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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())
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
@ -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())
|
||||
}
|
||||
}
|
@ -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())
|
||||
}
|
@ -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()
|
||||
}
|
@ -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)
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
@ -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))
|
||||
|
@ -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))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -0,0 +1,3 @@
|
||||
package space.kscience.dataforge.misc
|
||||
|
||||
public expect inline fun <T> Any?.unsafeCast(): T
|
@ -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))
|
||||
|
@ -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(
|
||||
|
@ -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))
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
@ -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)
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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") }
|
||||
|
@ -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)
|
||||
|
@ -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()))
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
Loading…
Reference in New Issue
Block a user